Перенос унаследованных данных с использованием CASE-средств
Рассмотрим альтернативный способ переноса данных на сервер, более дорогой и сложный, но приводящий в целом к более качественному результату. Этот способ базируется на использовании CASE-средств (CASE расшифровывается как Computer-Aided System Engineering) для восстановления схемы базы данных по имеющимся таблицам (так называемого обратного проектирования), замены платформы и описания связей между таблицами с точки зрения реакции сервера на попытки того или иного изменения данных со стороны клиентского приложения. В качестве такого средства рассмотрим, например, ERwin 3.0 - CASE-средство компании Logic Works, предназначенное для проектирования баз данных и на сегодняшний день являющееся одним из наиболее простых и доступных по цене средств такого класса.
Прежде чем заняться обратным проектированием, следует описать ODBC-источник, соответствующий каталогу, в котором хранятся исходные dBase-таблицы (это делается с помощью панели управления Windows).
Рис.18. Описание ODBC-источника с помощью панели управления Windows
Попробуем осуществить обратное проектирование с помощью ERwin , используя созданный ODBC-источник. Для этого из меню главного окна ERwin выберем опцию Tasks/Reverse Engineering .
Первая проблема, с которой при этом можно столкнуться, заключается в том, что Erwin не поддерживает формат данных dBase 5.0 (с прежними версиями dBase такой проблемы нет), и при обратном проектировании структура таблиц, содержащих графические поля, не всегда восстанавливается. Обычно эта проблема решается путем выбора сходной по структуре платформы (dBase III, Clipper, FoxPro) и последующей коррекции результатов обратного проектирования. Особое внимание при этом следует обращать на специфические типы данных (например, BLOB-поля), так как различия между платформами заключаются, в частности, в способах хранения подобных типов данных (но, разумеется, не только в этом).
Выберем в качестве исходной платформы Clipper, ответим на вопросы, предлагаемые в последующем диалоге (можно оставить то, что предложено по умолчанию) и в результате получим модель данных, похожую на изображенную на рис.19 (она содержит описание всех dBase-таблиц из каталога, содержащего данные для примеров, поставляемых с C++Builder):
Рис.19. Результат обратного проектирования каталога CBUILDER\EXAMPLES\DATA
Отредактируем полученную модель данных, убрав все таблицы, кроме CLIENTS и HOLDINGS, определив ACC_NBR первичный ключ таблицы CLIENTS и изменив тип данных поля IMAGE на МЕМО (опция Column Editor контекстного меню таблицы). Создадим также неидентифицирующую связь "один-ко-многим" между таблицами CLIENTS и HOLDINGS, выбрав для этой цели соответствующую пиктограмму на "плавающей" инструментальной панели.
Далее следует выбрать другую целевую платформу (в нашем случае Oracle). В результате получим примерно следующий вид модели данных:
Рис.20. Примерный вид модели данных для генерации БД в Oracle
Теперь можно описать свойства имеющейся связи между таблицами. Так как это связь "один-ко-многим", это следует явно указать в диалоге, вызываемом с помощью опции Relationship Editor контекстного меню связи. В том же диалоге на другой странице трехстраничного блокнота следует выбрать из предлагаемых выпадающих списков возможную реакцию сервера на попытки нарушения ссылочной целостности со стороны клиента. Например, при попытке удалить запись из таблицы CLIENTS можно либо совершить каскадное удаление (то есть удалить все соответствующие записи из таблицы HOLDINGS), либо запретить удаление, если имеются соответствующие записи в дочерней таблице, с выдачей диагностического сообщения.
Рис.21. Определение реакции сервера на попытки нарушения ссылочной целостности
После этого можно выбрать из меню опцию Tasks/Forward Engineer/Schema Generation и после установки соединения с Oracle сгенерировать базу данных, выбрав в появившейся диалоговой панели опции для генерации структуры. Можно также просмотреть и сохранить скрипт на языке PL/SQL (это процедурное расширение SQL, используемое для написания триггеров и хранимых процедур Oracle), называемый также DDL-сценарием (DDL расшифровывается как Data Definition Language).
Рис.22. DDL-сценарий генерации схемы базы данных
Далее можно попытаться снова воспользоваться Data Migration Wizard для переноса данных, отказываясь при этом от удаления уже сгенерированных таблиц. Однако в ряде случаев удобнее создать приложение для переноса данных из старой БД в новую. Как было отмечено выше, унаследованные данные могут не удовлетворять правилам ссылочной целостности, установленным на сервере. В этом случае от приложения, используемого для переноса данных, требуется некоторая гибкость (например, предоставление возможности редактирования исходных данных или создание дополнительных таблиц, содержащих записи, не удовлетворяющие бизнес-правилам новой базы данных, для последующего анализа).
Создадим простейшее приложение для переноса данных. Для этого создадим форму следующего вида (рис.23):
Рис.23. Форма приложения для переноса данных на сервер
Для переноса данных с одной платформы на другую обычно используется компонент TBatchMove. Этот компонент обеспечивает копирование данных из одной таблицы в другую. Основные свойства этого компонента следующие: Source – таблица (или запрос), откуда копируются данные, Destination – таблица, куда копируются данные, Mapping – определяет соответствие между колонками исходной и результирующей таблиц (для идентичных таблиц это свойство определять не обязательно), Mode – тип перемещения (batAppend – добавляет новые строки в результирующую таблицу, batUpdate – заменяет строки в результирующей таблице на соответствующие строки оригинала, batCopy – копирует строки в результирующую таблицу, переписывая ее, batDelete – удаляет записи в результирующей таблице, соответствующие записям оригинала), KeyViolTableName и ProblemTableName – имена дополнительных таблиц для помещения записей, чье копирование запрещено правилами ссылочной целостности или по каким-либо причинам невозможно (например, из-за несоответствия типов данных), ChangedTableName – имя таблицы для помещения измененных записей. Копирование данных происходит при выполнении метода Execute(). Отметим, что этот метод может быть вызван непосредственно из среды разработки с помощью контекстного меню компонента TBatchMove.
Установим следующие значения свойств используемых компонентов:
Компонент | Свойство | Значение |
Table1 | DatabaseName | Oracle7 |
Exclusive | true | |
TableName | CLIENTS | |
Active | true | |
Table2 | DatabaseName | BCDEMOS |
TableName | ClLIENTS.DBF | |
Active | true | |
DataSource1 | DataSet | Table1 |
DataSource2 | DataSet | Table2 |
DBGrid1 | DataSource | DataSource1 |
DBGrid2 | DataSource | DataSource2 |
DBNavigator1 | DataSource | DataSource1 |
DBNavigator2 | DataSource | DataSource2 |
BatchMove1 | Source | Table2 |
Destination | Table1 | |
Mode | batAppend | |
Button1 | Caption | CLIENTS |
Button2 | Caption | HOLDINGS |
Button3 | Caption | Добавить |
Button4 | Caption | Очистить |
Button5 | Caption | Выход |
Создадим также обработчики событий для кнопок:
//--------------------------------------------------------#include <vcl\vcl.h>#pragma hdrstop#include "upsize1.h"//--------------------------------------------------------#pragma link "Grids"#pragma resource "*.dfm"TForm1 *Form1;//--------------------------------------------------------__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner){}//--------------------------------------------------------void __fastcall TForm1::Button1Click(TObject *Sender){Table1->Close();Table2->Close();Table1->TableName="CLIENTS";Table2->TableName="CLIENTS.DBF";Table1->Open();Table2->Open();}//--------------------------------------------------------void __fastcall TForm1::Button2Click(TObject *Sender){Table1->Close();Table2->Close();Table1->TableName="HOLDINGS";Table2->TableName="HOLDINGS.DBF";Table1->Open();Table2->Open();}//--------------------------------------------------------void __fastcall TForm1::Button5Click(TObject *Sender){Table1->EmptyTable();}//--------------------------------------------------------void __fastcall TForm1::Button3Click(TObject *Sender){BatchMove1->Execute();}//--------------------------------------------------------void __fastcall TForm1::Button4Click(TObject *Sender){Close();}//--------------------------------------------------------Скомпилируем и запустим приложение. Кнопки CLIENTS и HOLDINGS осуществляют выбор между той или иной парой таблиц. При нажатии кнопки Добавить происходит перенос данных из активной в данный момент таблицы dBase (CLIENTS или HOLDINGS) в соответствующую таблицу Oracle. Отметим, что наличие поддержки ссылочной целостности можно проверить, попытавшись перенести данные из таблицы HOLDINGS до того, как перенесены данные из таблицы CLIENTS. При этом перенос осуществлен не будет, и в процессе выполнения приложения появится диагностическое сообщение примерно следующего вида (рис.24):
Рис.24. Диагностическое сообщение при попытке добавления записей в detail-таблицу при пустой master-таблице.
Примерно такие же последствия будет иметь попытка очистить таблицу CLIENTS после того, как перенесены записи в таблицу HOLDINGS.
После переноса данных на сервер можно вернуться к созданному ранее приложению с формой master-detail и попробовать снова проделать действия, приводящие к нарушению ссылочной целостности.
Теперь, если мы попытаемся удалить запись из таблицы CLIENTS при наличии связанных с ней записей в таблице HOLDINGS, нам это не удастся. При этом клиентским приложением будет выдано диагностическое сообщение о наличии записей в дочерней таблице (рис. 25):
Рис.25. Диагностическое сообщение при попытке удаления записей из master-таблицы при наличии связанных с ней записей в detail-таблице.
Точно так же окажется невозможным добавить запись с произвольным значением поля ACC_NBR в таблицу HOLDINGS. Причина такого поведения созданной информационной системы очевидна: при проектировании базы данных с помощью ERwin помимо самих таблиц и индексов были созданы также специальные объекты базы данных, называемые триггерами. Триггер - это специальная процедура, выполняющаяся при наступлении определенного события, например, при попытке удаления записи в таблице CLIENTS. При описании свойств связи между таблицами мы выбирали, как сервер будет реагировать на подобные события, и в соответствии с нашим выбором были сгенерированы триггеры для выполнения соответствующих действий (в данном случае - для передачи клиентскому приложению диагностического сообщения).
Разумеется, пользователь приложения должен видеть нечто более вразумительное, нежели англоязычное сообщение с именем триггера и словами про "integrity constraint" и "key violation". Можно сделать это путем перехвата исключения в клиентском приложении, но более предпочтительно делать это на сервере, так как тогда будет исключена необходимость в повторении кода в случае, когда с одной и той же базой данных работают несколько приложений. Современные CASE-средства позволяют это сделать, и интересующиеся этой проблемой могут найти ее решение в документации по используемому CASE-средству и в документации, прилагаемой к соответствующему серверу баз данных.