Главная » C++, C++ Builder » Создание базы данных на пустом месте

0

После того как вы разобрались, как связаны поля в базе данных, логично было бы научиться объединять поля в базу данных, не так ли? Конечно, именно этого вы и добиваетесь. В конце концов, все, что вам надо сделать, — это выбрать каталог базы данных, установить корректное имя, добавить несколько полей и установить свойство Active в true, правда? Действительно, вы не так далеки от правды. Наверняка случится ситуация, когда вам понадобится создавать базу данных в соответствии с требованиями пользователя, которые нельзя узнать заранее. Отлов ошибок, проверка баз данных и другие типы приложений требуют от вас предоставления пользователю права определять базы данных в том виде, в котором они должны фигурировать в приложении, вместо того вида, который постулировал какой-то программист (или, не дай Бог, проектировщик баз данных). Конечно, вам по-прежнему придется позже писать код для загрузки полей с данными, но, по крайней мере, сам процесс создания CBuilder может сделать молниеносным.

В новом примере мы создадим приложение, которое позволит вам, выступающему в роли пользователя, определять схему базы данных. Поля будут добавлять ся в таблицу, имя и каталог устанавливаться, база данных создаваться — и все в нескольких десятках строках кода. При этом большая часть этих строк относится скорее к интерфейсу с пользователем, чем к созданию базы.

На рис. 7.3 показана форма, которую мы будем использовать для создания баз данных. Обратите внимание на несколько важных моментов. Во-первых, на форме нет компонентов, воспринимающих данные (если вам так уж интересно, отмечу, что сетка, лежащая на форме, относится к TStringGrid). Нет их потому, что мы не собираемся использовать основополагающие управляющие элементы для баз данных CBuilder в интерфейсе этой формы. Все, что мы будем делать, будет делаться на низком уровне доступа к базам данных. Кроме того, как можно видеть, на форме нет объекта TTable. Мы будем использовать TTable для создания базы данных, но в этом примере мы его создадим динамически, чтобы вы поняли, как это делается.

Мы должны добавить в код формы ту же самую таблицу описание/код, что и в предыдущем примере. В том последнем примере, если вы помните, объект field definitions использовался для указания пользователю на описание полей. В теперешнем примере мы собираемся использовать описание полей для того, чтобы пользователь мог выбрать из них тип поля, а мы потом могли поставить ему в соответствие тип действительного (физического) поля для непосредственного описания поля в таблице. Итак, добавьте следующий код в начало исходного файла Unit1.cpp.

Рис. 7.3. Форма приложения создания баз данных

typedef struct

{

Db::TFieldType nCode; char *strDesc;

} DbFieldType;

DbFieldType sFieldTypes[] =

{

{ftUnknown, "Неизвестно или не определено"},

{ftString, "Символьное или строковое поле"},

{ftSmallint, "16-битное целое поле"},

{ftInteger, "32-битное целое поле"},

{ftWord, "16-битное беззнаковое целое поле"},

{ftBoolean, "Логическое поле"},

{ftFloat, "Поле чисел с плавающей точкой"},

{ftCurrency, "Денежное поле"},

{ftBCD, "Двоично-кодированное десятичное поле"},

{ftDate, "Поле даты"},

{ftTime, "Поле времени"},  Глава 7•Работа с базами данных

{ftDateTime, "Поле даты и времени"},

{ftBytes, "Фиксированное количество байт (двоичное представление)"},

{ftVarBytes, "Переменное количество байт (двоичное представление"},

{ftAutoInc, "Автоматически увеличивающееся 32-битное целое поле счетчика"},

{ftBlob, "Поле Binary Large Object (большой двоичный объект)"},

{ftMemo, "Поле memo (строка неограниченной длины)"},

{ftGraphic, "Поле растрового рисунка"},

{ftFmtMemo, "Поле форматированного memo"},

{ftParadoxOle, "Поле Paradox OLE"},

{ftDBaseOle, "Поле dBase OLE"},

{ftTypedBinary, "Типизированное двоичное поле"},

};

В добавление к списку мы также должны знать, сколько же в нем элементов, для того чтобы иметь возможность добавить его в комбинированный список ниже в коде. Мы могли бы просто использовать числовое значение, равное количеству элементов в списке (22), но это приведет к возможности появления в будущем ошибок, связанных с добавлением или удалением элементов из списка при неизменном числовом значении. На наше счастье препроцессор CBuilder позволяет нам применить полезную хитрость. Добавьте следующую строку кода прямо после массива элементов структуры:

#define NumberOfFieldTypes (sizeof(sFieldTypes)/sizeof\ (sFieldTypes[0]))

Возможно, вам доводилось видеть подобные строки в коде, с которым вы работали. Возможно, вы когда-то сами писали что-то подобное. Оператор sizeof возвращает размер, в байтах, переменной, переданной ему в качестве аргумента.

В случае массива элементов структуры каждый элемент имеет свой специфичес кий размер (обычно 8 байт, но это зависит от компилятора). Размер специфичес кого элемента тем не менее, если поделить на него произведение размеров, позволит получить общее количество  элементов. Формула будет выглядеть примерно так:

int nTotalEntries = (nNumberOfEntries * nSizeOfOneEntry) / nSizeOfOneEntry;

Этот код, как могут засвидетельствовать те из вас, кто силен в математике, сводится к тождеству nTotalEntries = nTotalEntries. Это все, что мы используем в блоке #define, описанном выше, не заботясь о том, чему равны nNumberOfEntries и nSizeOfOneEntry. Чудесная штука, этот оператор sizeof.

Следующее, что нам надо сделать, — это навести порядок в системе. Нам надо изменить конструктор формы, вставив туда объект TTable, который мы будем использовать, а также кое-что убрать из этого объекта, чтобы его действительно можно было использовать для создания таблицы. Добавьте следующий код в конструктор объекта Form1.

Разбираемся во внутреннем устройстве базданных

__fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{

Table = new Table(this); Table->Active = false; Table->FieldDefs->Clear(); Table->IndexDefs->Clear();

}

Как видно из приведенного выше кода, мы создаем объект TTable в конструкто ре формы. Тогда это позволит нам позже использовать его напрямую. Строго говоря, вызовы Clear для массивов описаний полей и индексов не являются здесь необходимыми, поскольку эти объекты при создании по умолчанию являются пустыми. Однако хорошей практикой будет не полагаться ни на что при работе с объектами. Точно так же объект TTable создается со свойством Active, установлен ным в false. Все же, чтобы подстраховаться и не зависеть от предпосылок о верности инициируемых по  умолчанию значений, мы сами устанавливаем все, что нужно в конструкторе.

инициализируйте все сами.

Следующим шагом написания нашей программы будет инициализация сетки и комбинированного списка, чтобы они выглядели корректно прямо с момента запуска программы. Для этого добавьте новый обработчик для события формы OnCreate и в соответствующий метод FormCreate добавьте следующий код:

void __fastcall TForm1::FormCreate(TObject *Sender)

{

StringGrid->ColCount = 5; StringGrid->Cells[0][0] = "Поле"; StringGrid->Cells[1][0] = "Имя"; StringGrid->Cells[2][0] = "Тип"; StringGrid->Cells[3][0] = "Длина";

StringGrid->Cells[4][0] = "Обязательное"; StringGrid->RowCount = 1;

// Загружаем комбинированный список

for (int i=0; i<NumberOfFieldTypes; ++i) ComboBox->Items->Add(sFieldTypes[i].strDesc);

}

}

Все, что мы здесь делаем, — это устанавливаем правильные заголовки столбцов, количество столбцов и строк (1 — для верхней строки) для сетки. После этого мы добавляем описания типов полей в комбинированный список типов полей. Описания мы берем из структуры, которую добавили в исходный файл чуть раньше.

Итак,  мы  можем  видеть  перед  собой  форму,  содержащую  пустую  сетку  и  комбинированный список описаний типов полей, поддерживаемых системой. Следу

ющее,  что  нам надо  сделать,  — это  предоставить пользователю  возможность добавлять поля  в базу данных (и отображать их при помощи сетки).

Добавляем поля в таблицу

Процесс добавления нового поля в таблицу выглядит примерно следующим образом. Сначала пользователь вводит имя поля и его размер в соответствующих полях ввода и выбирает тип поля из комбинированного списка. После этого пользователь нажимает кнопку Добавить, чтобы поле было добавлено в таблицу. На самом деле с базой данных ничего не происходит до тех пор, пока пользователь не введет каталог базы данных и имя таблицы и не нажмет кнопку Создать. Однако, как мы скоро увидим, закулисные события происходят и тогда, когда пользователь добавляет единичное поле в новую базу.

Для добавления нового поля в базу  нам надо дождаться момента, когда  пользователь  нажмет кнопку Добавить. Как только это произойдет, мы начинаем действовать. Добавьте обработчик события для кнопки Добавить (событие — нажатие кнопки). В соответствующий метод формы Button2Click добавьте следующие строки:

//————————————————————————————

void __fastcall TForm1::Button2Click(TObject *Sender)

{

char szBuffer[ 80 ];

strcpy (szBuffer, Edit2->Text.c_str());

unsigned short nSize = (unsigned short)atoi(szBuffer);

// Проверяем, нужно ли задавать размер

if (sFieldTypes[ComboBox1->ItemIndex].nCode != ftString) nSize = 0;

// Устанавливаем описания полей

Table1->FieldDefs->Add(Edit1->Text, sFieldTypes[ComboBox1->ItemIndex], nCode, nSize, CheckBox1->Checked);

// Переходим к следующей строке

StringGrid1->RowCount++;

// Теперь заполняем сетку данными

int nRow = StringGrid1->RowCount-1; StringGrid1->Cells[0][nRow] = AnsiString(nRow); StringGrid1->Cells[1][nRow] = Edit1->Text; StringGrid1->Cells[2][nRow] = sFieldTypes[ComboBox1->ItemIndex].strDesc; StringGrid1->Cells[3][nRow] = Edit2->Text;

if (CheckBox1->Checked) StringGrid1->Cells[4][nRow] = "Yes"; else

StringGrid1->Cells[4][nRow] = "No";

}

В этом коде мы сначала получаем необходимую информацию от различных элементов формы. Поле редактирования имени используется напрямую при добавлении описания поля в базе. Выбранный элемент комбинированного списка (индекс которого  можно  найти  в  свойстве ItemIndex комбинированного списка) используется для нахождения действительного типа поля в структуре, хранящей данные о соответствии типов полей их описаниям. А  вот  преобразование поля размера на данный момент является довольно смелым экспериментом.

Есть две важные вещи, которые надо отметить при работе с полем размера. Во-первых, никогда не передавайте текст для поля редактирования прямо в функцию, даже используя метод c_str() объекта TEdit. При этом небезопасно изменять строку прямо в поле объекта TEdit. По  этой причине мы копируем строку в обычный символьный буфер и используем его для получения размера поля.

Вторым важным аспектом, касающимся добавления размера полю, является дилемма — разрешен размер или нет. Для всех полей, кроме полей строкового (символьного) типа, размер не только не используется, но и не разрешается для использования. Если вы позволите пользователю ввести размер для не строкового поля и передадите его в метод Add объекта field definitions, то получите ошибку, сгенерированную объектом, и метод не будет выполнен. По этой причине мы проверяем тип поля перед тем, как добавить размер, и для не строковых типов устанавливаем размер (который, кстати, типа unsigned short, а не int) в 0.

После  того  как  поле  благополучно  добавлено  в  структуру  базы  данных,  информация  о  нем отображается в сетке, и пользователь может продолжать вводить другие поля.

Защищаем  пользователя

Мы можем несколько облегчить жизнь пользователю, проверив, допустимо для выбранного им типа поля задание размера или нет. Давайте проверим это и обработаем результат, соответственно сделав недоступным для редактирования (disable) поле ввода размера для случая выбора типа поля без размера (числовые и пр.).

Добавьте в форму новый обработчик для события OnChange (при изменении) комбинированного списка. Следующий код добавьте в метод ComboBox1Change формы:

void __fastcall TForm1::ComboBox1Change(TObject *Sender)

{

if (sFieldTypes[ComboBox1->ItemIndex].nCode != ftString)

{

Edit2->Enabled = false;

}

else

Edit2->Enabled = true;

}

Создаем базу данных

На данный момент пользователь, скорее всего, уже добавил в базу несколько  полей  и  готов создать базу данных на диске в каталоге и файле, которые он выбрал для новой таблицы. Итак, все готово — пришло время создать базу данных непосредственно из приложения. Я знаю, что именно этого кода вы и ждали, затаив дыхание, и не буду больше терзать вас ожиданием.

Добавьте следующий код в метод Button1Click (Button1 — кнопка Создать, служит для создания базы данных):

void __fastcall TForm1::Button1Click(TObject *Sender)

{

// Устанавливаем название базы данных и имя таблицы

Table1->DatabaseName = Edit3->Text; Table1->TableName = Edit4->Text;

// Устанавливаем тип таблицы в dBase Table1->TableZType = ttDBase;

// Создаем таблицу

Table1->CreateTable();

// Удаляем объект

delete Table1;

// Уведомляем пользователя MessageBox1(NULL, "База данных создана!", "Подтверждение",  MB_OK);

// Закрываем приложение

Application->Terminate();

}

Вы, должно быть, удивлены, как мало строк кода вовлечено в  процесс  создания  базы?  Я,  во всяком случае, был удивлен, когда впервые попробовал создать базу данных в своей реальной программе. Моя программа была несколько длиннее, поскольку я напичкал ее обработкой ошибок пользователя, ведь я не мог допустить, чтобы система выдала пользователю сообщение об ошибке. Но тем не менее основной код был такой же, и при первом запуске работал он просто отлично, да и при всех последующих тоже.

Первый шаг в процессе создания базы данных — это присвоение имени базы данных и таблицы. Это определяет, где же на самом деле на диске будет создана база. Вы можете ввести любое допустимое название каталога и файла, в том числе и сетевой каталог. После присвоения имен нам надо установить тип создаваемой базы данных. Система CBuilder по умолчанию умеет создавать таблицы  dBase  и  Paradox.  С  соответствующим  ODBC-драйвером  вы  можете  создавать  также

другие типы таблиц.

После того как база данных была успешно создана (обратите внимание, что в коде примера нет проверки возможных ошибок, которую вам обязательно надо будет осуществлять в ваших приложениях), объект TTable удаляется, чтобы избежать потери памяти в системе, а пользователь извещается о создании базы. После этого приложение закрывается. Теоретически, вам  не обязательно прекращать работу приложения в этот момент. Вы могли бы создать и инициализировать новый объект TTable и позволить пользователю создавать много баз данных за один запуск программы. Однако для первого примера мы применяем простейший метод выхода.

Вот и все создание базы данных во время исполнения приложения. Определите поля, присвойте путь и имя файла и позвольте объектам VCL CBuilder сделать всю остальную работу. Быстро и просто, что и требовалось.

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии