Избранные
вопросы программирования объектов доступа к
данным. Примеры интеграции ADO с Microsoft SQL Server на VB и Delphi.
Содержание
статьи
Достоинства и
недостатки новых компонентов доступа к данным.
Как
правильно инициализировать DataEnvironment?
Пример
использования источника данных из DataEnvironment в программе.
Пример
использования объекта ADODB.
ADO или BDE? Компромиссное решение
проблемы.
Пример
использования ADO в
программе на Delphi.
Во-первых избегайте использования beta-версии продукта, т.к. при переходе на коммерческую версию необходимо будет переформатировать диск C: (!!!), об этом сказано и в readme файле, но в виде небольшого примечания.
Во-вторых не устанавливайте
английскую версию Visual Studio поверх русской, т.к.
деинсталляция продукта не вычищает полностью
системный каталог Windows, в результате чего часть
компонентов остается русскоязычными и ваша
система будет представлять «гибрид» русской и
английской версий. Если вы пользуетесь Microsoft Office
97, то будьте готовы к тому, что VS 98 заменит
некоторые общие библиотеки (mso97rt.dll и др.), но
серьезных неприятностей это не сулит. При
использовании MS Office 2000 проблемы не существует
вовсе.
Если вы ранее пользовались VB 5.0,
то при переходе на VB 6.0 возникнут проблемы при
перекомпиляции проектов, использующих компонент
DataGrid (DBGrid32.OCX) из-за несоответствия CLSID, однако
этот момент можно легко исправить, покопавшись в
исходниках своей программы. Также возможны
проблемы при компиляции проектов,
использовавших первые версии ADODC по той же самой
причине.
В Visual Studio 98 появились новые возможности по использованию удаленных источников данных, в первую очередь это — Data Environment, включающий в себя визуальное управление объектами типа Connection и Command, с возможностью подключения к СУБД без использования DSN файлов, а также визуальным конструктором запросов, как в MS Access. Запросы, созданные в Data Environment можно использовать в программе как объекты типа Recordset с одной стороны и как независимые команды SQL с другой, ниже это будет показано на примере.
Все сказанное выше, конечно
повышает скорость создания приложений и
удобство работы, однако сильно радоваться не
стоит. Если вы используете Visual Basic, то некоторые
свойства, прописанные на стадии конструирования
(design-time) в Data Envirionment, вам не удастся изменить во
время выполнения программы (run-time) имеется в виду
начальный запуск, это относится прежде всего к
строке подключения (login & password), т.к. их
переопределение возможно уже после того, как Data
Environment инициализирован на уровне входа в
соответствующую DLL и отработала строка
подключения, созданная в design-time. Такое поведение
программы заложено компилятором, но вы можете
его и не обнаружить, если работаете с SQL СУБД на
правах администратора, только при установке
программы заказчику, эта неприятность может
проявить себя.
Еще один «подводный камень»
таится в использовании визуального компонента
ADODC. Если вы захотите присоединить к ADODC локальную
базу данных (обычный *.MDB файл), то заметите, что
свойство Connection String во время выполнения программы
доступно для переопределения, когда объект уже
инициализирован и имеет возможность открыть
базу данных прописанную в design-time. Еще большее
неудобство приносит то, что Connection String не может
быть задан пустой строкой. Однако выход из
ситуации существует и заключается во временном
копировании открываемого файла в заведомо
существующий каталог (можно и сетевой) перед
инициализацией формы, использующей ADODC, а затем
переопределением Connection String на рабочий путь
программы и удалением копии локальной базы, на
который ADODC настроен в design-time. При использовании
подключения ADODC к SQL СУБД, проблема аналогична
ситуации с Data Environment и может быть разрешена
дополнительным вводом login-a и пароля при запуске
приложения, однако если подключение происходит к
нескольким базам данных, то здесь есть над чем
поработать!
Если у вас достаточно времени,
чтобы работать над проектом, то я рекомендую не
злоупотреблять использованием Data Environment, а
работать с объектами ADODB.Connection, ADODB.Command, ADODB.Recordset и
прочими ADO (ActiveX™ Data Objects). Конечный результат в
итоге окажется более надежным и мобильным.
В принципе сразу после создания
внутри проекта Data Environment и заполнения его
объектами Connection и Command, он становится доступным
во время выполнения программы, однако для
придания программе «независимости» необходимо в
процедуре обработки события Data Environment Initialize
поместить код инициализации, например:
Private Sub DataEnvironment_Initialize()
Dim ConnectTo As String
ConnectTo = BuildConnection(DBname:="DB_NSI")
DEC1.Open ConnectTo, ODBC.UserName, ODBC.Password
ConnectTo = BuildConnection(DBname:="DB_REPORTS")
DEC2.Open ConnectTo, ODBC.UserName, ODBC.Password
DECJET.ConnectionString = _
"Provider=Microsoft.Jet.OLEDB.3.51;Persist Security Info=False;Data Source=" & _
App.Path & DBname
End Sub
Этот код будет храниться
непосредственно в файле *.dsr вместе с настройками
Data Environment. Как видно из примера внутри Data Environment
открываются три объекта Connection. DEC1 и DEC2 – для
подключения к базе данных на SQL Server и DECJET – для
подключения к локальной базе данных (\Mydb.MDB
прописано в переменной DBName). Функция BuildConnection
просто возвращает строку подключения к
конкретной базе данных, например такую: «Provider=MSDASQL.1;
Password=mypass; Persist Security Info=True; User ID=Alex; Extended Properties=
"DRIVER= SQL Server; SERVER=SQL_SERVER; UID=Alex; APP= Microsoft® VS 98; WSID=
ARM_ADMIN; DATABASE= DB_NSI"; Initial Catalog= DB_NSI». Еще несколько слов скажу об
объекте ODBC – он инкапсулирует функции Win32 API,
позволяющие снять имя пользователя и компьютера.
Свойство Password нужно задать после инициализации
ODBC, предварительно запросив его у пользователя.
Кроме того объект ODBC имеет еще несколько
полезных функций, их смысл будет понятен, если
взглянуть на исходник этого класса – ODBC_SQL.CLS (9191).
Если вам лень копаться в исходнике, то предлагаю
откомпилированный вариант этого объекта – файл
ODBC_SQL.DLL (40960). Его свойства и методы можно
посмотреть через object browser в VB или соответствующий
Unit в Delphi – DAO_ODBC.PAS (6703). Инициализировать
экземпляр объекта ODBC желательно используя
модель раннего связывания и соответствующая
объектная переменная должна быть глобальной в
проекте, т.к. в процессе работы программы
переопределение свойств ODBC как правило не
требуется.
Итак, для того, чтобы использовать объект Data Environment в программе существует как минимум два варианта.
1.Можно выполнять команды Data
Environment и результирующий набор данных
использовать как обычный Recordset:
Private Sub Updatedbp()
Dim l_rs As Recordset, g_rs As Recordset, Up As Boolean
With DE1
If .Recordsets("cmdUp").State = 0 Then .cmdUp
If .Recordsets("cmdLink").State = 0 Then .cmdLink
If .Recordsets("cmdUp").BOF Then Exit Sub
Set g_rs = .Recordsets("cmdLink").Clone
g_rs.Filter = "dataop = #" & Format(DTF, SqlDateFormat) & "# AND kod_pok = '" & PL & "' "
.Recordsets("cmdUp").MoveFirst
Up = False
Do While Not .Recordsets("cmdUp").EOF
If g_rs.BOF Then
g_rs.AddNew
g_rs!dataop = DTF
g_rs!mfo = .Recordsets("cmdUp")!Kod_mfo
g_rs!Kod_pok = PL
g_rs!zn = .Recordsets("cmdUp")!Sum
g_rs.Update
DoEvents
Else
g_rs.MoveFirst
Up = True
Do While Not g_rs.EOF
If g_rs!dataop = DTF And g_rs!mfo = .Recordsets("cmdUp")!Kod_mfo _
And g_rs!Kod_pok = PL Then
Up = False
g_rs!zn = .Recordsets("cmdUp")!Sum
g_rs.Update
DoEvents
End If
g_rs.MoveNext
Loop
If Up Then
g_rs.AddNew
g_rs!dataop = DTF
g_rs!mfo = .Recordsets("cmdUp")!Kod_mfo
g_rs!Kod_pok = PL
g_rs!zn = .Recordsets("cmdUp")!Sum
g_rs.Update
DoEvents
End If
End If
.Recordsets("cmdUp").MoveNext
Loop
g_rs.Close
DoEvents
If .Recordsets("cmdUp").State <> 0 Then .rscmdUp.Close
If .Recordsets("cmdLink").State <> 0 Then .rscmdLink.Close
End With
DoEvents
End Sub
В этом примере внутри объекта Data
Environment с именем DE1 открываются наборы данных,
путем выполнения соответствующих команд (cmdUp,
cmdLink). Причем имена этих команд являются методами
DE1, а соответствующие наборы данных имеют те же
имена, но с приставкой «rs» (rscmdUp, rscmdLink) и являются
свойствами DE1 типа Recordset. На эти же свойства
ссылаются элементы коллекции DE1.Recordsets. Свойство
State для каждого элемента коллекции DE1.Recordsets
показывает была ли выполнена соответствующая
команда DE1 (State=0 означает, что набор данных не был
открыт). Операции по проверке состояния Recordset
полностью лежат на программисте, т.к. попытка
закрыть закрытый или открыть открытый набор
данных выбросит исключение. После того, как набор
данных открыт, его можно использовать в
программе классическим способом, как в VBA.
2. Можно выполнять команду Data Environment и результат сразу
записывать в базу данных. Это относится прежде
всего к таким объектам Command, у которых значение
свойства Command Text содержит SQL оператор для
удаления («DELETE … FROM …») или обновления («UPDATE … SET
…») записей базы.
Реализация этого приема работы не требует особых навыков программирования и похожа на выполнение обычной хранимой процедуры:
DE1.Commands("cmdName").Execute или
DE1.Commands(1).Execute‘ если известен
индекс выполняемой команды в коллекции Commands.
Результат выполнения метода Execute также может
быть присвоен переменной типа Recordset и
использоваться далее, однако не следует путать
набор данных типа DAO.Recordset и Recordset, возвращаемый из
Data Environment. Несоответствие типов может привести к
ошибке времени выполнения (run-time error). Подробное
описание свойств и методов объектов Command и Connection
есть в документации MSDN.
Среди объектов доступа к данным, входящих в Visual Studio 98, есть компонент не имеющий интерфейса настройки во время создания проекта, и более эффективный, чем Data Environment, это – ADODB или, если говорить точнее, Microsoft ActiveX™ Data Objects 1.0. Использование данного компонента также основано на создании экземпляров объектов типа ADODB.Connection, ADODB.Command, ADODB.Recordset, ADODB.Field. Следующий пример демонстрирует заполнение элемента управления ListBox с именем lstr, значениями набора данных с SQL СУБД, используя ADODB:
Private Sub FillList()
Dim cnn As ADODB.Connection
Dim rst As ADODB.Recordset
Dim cmd As ADODB.Command
Dim dat As String, i As Integer
Set cnn = New ADODB.Connection
cnn.Open BuildConnection("DB_REPORTS")
Set rst = New ADODB.Recordset
Set cmd = New ADODB.Command
Set cmd.ActiveConnection = cnn
dat = Format(cbDate.Text, "mm-dd-yy")
cmd.CommandText = "SELECT kod_otdel AS kod_otdel, kod_otc AS kod_otc, vid_otc AS vid_otc, razr AS razr "
cmd.CommandText = cmd.CommandText & "From dbo.v_reports1999 WHERE dataotc = '" & dat & "' GROUP BY kod_otdel, "
cmd.CommandText = cmd.CommandText & "kod_otc, vid_otc, razr ORDER BY kod_otdel, kod_otc "
Set rst = cmd.Execute
lstr.ListItems.Clear
If Not rst.BOF Then
rst.MoveFirst
i = 1
While Not rst.EOF
With lstr
.ListItems.Add , , rst!kod_otdel
.ListItems(i).ListSubItems.Add , , rst!kod_otc
.ListItems(i).ListSubItems.Add , , rst!vid_otc
.ListItems(i).ListSubItems.Add , , rst!razr
.ListItems(i).Bold = True
rst.MoveNext
i = i + 1
End With
Wend
End If
rst.Close
cnn.Close
Set rst = Nothing
Set cnn = Nothing
End
Sub
Присвоение значения Nothing,
объектным переменным после их использования в
данном случае чисто формальное, т.к. при выходе из
процедуры память освобождается и все локальные
переменные уничтожаются, однако в более сложной
ситуации, когда необходимо инициализировать ADODB
в цикле, всегда следует освобождать объектные
переменные во избежание утечки памяти. Функция
BuildConnection здесь, как и в одном из предыдущих
примеров возвращает строку подключения к
конкретной БД.
В основу эффективной работы ADODB (и
других ADO) положено прямое взаимодействие с
низкоуровневыми интерфейсами OLE DB, который в свою
очередь использует инфраструктуру модели OLE COM
(Component Object Model), что сокращает ненужное
дублирование сервисов и расширяет возможности
взаимодействия не только для разных источников
информации, но и для разных программных сред и
инструментов. OLE DB — это способ доступа к данным в
среде COM. Небольшое руководство по работе
непосредственно с OLE DB можно найти в файле Ole4Odbc.doc
(301 568) на английском языке.
Тем, кто пишет программы для работы с СУБД на Delphi, хорошо знакомы компоненты, взаимодействующие с BDE (Borland Database Engine), а также сам BDE. Но возникает вопрос, можно ли использовать в Delphi проектах другие объекты доступа к данным. Ответ — да, можно, однако здесь есть некоторые тонкости, на которых мы и остановимся далее.
Общеизвестно, что при разработке приложений под Win32, независимо от языка программирования можно использовать практически все доступные в системе компоненты, если они корректно зарегистрированы (установлены). Предположим, что мы разрабатываем приложение, осуществляющее выборку/обновление данных в БД на SQL Server, но не хотим привязываться к BDE. В таком случае у нас есть выбор: это либо ADO, либо ODBC Direct объекты. Использование ODBC Direct с удаленной СУБД несколько ограничено (тем, кто писал на VBA это знакомо) и требует привязки через DSN файлы, или драйверы ODBC, т.е. технология практически идентичная BDE, только более примитивная. Использование ADO — работа с обычной ActiveX DLL, поэтому сначала необходимо получить файл, содержащий декларации всех объектов. Этот файл ADODB_TLB.PAS (46980 для ADO версии 1.0) можно сгенерировать при помощи самой Delphi. Выберите в меню пункт Project/Import Type Library… и, если в системе установлены ADO, то в списке объектов найдите пункт Microsoft ActiveX Data Objects 2.1 Library (версия библиотеки может быть и другая) после чего, щелкнув на OK к вашему проекту добавится модуль, содержащий все необходимые определения. В модуле, объекты доступа к данным описаны следующим образом:
CoRecordset = class
class function Create: _Recordset;
class function CreateRemote(const MachineName: string): _Recordset;
end;
CoConnection = class
class function Create: _Connection;
class function CreateRemote(const MachineName: string): _Connection;
end;
CoCommand = class
class function Create: _Command;
class function CreateRemote(const MachineName: string): _Command;
end;
CoParameter = class
class function Create: _Parameter;
class function CreateRemote(const MachineName: string): _Parameter;
end;
Кроме того, в виде перечислений
реализованы все используемые константы. Из
определений следует, что можно напрямую
создавать экземпляры классов Recordset, Connection, Command,
Parameter. Остальные определения относятся
непосредственно к инкапсулированным
интерфейсам OLE DB и объектам, входящим в указанные
классы. Для использования ADO в программе
необходимо описать переменные соответствующих
типов (см. определения в файле), например:
var
Cnn: Connection;
Rst: Recordset;
Cmd: Command;
Prm:
Parameter;
Далее в теле процедуры или
функции создаем экземпляры классов, например:
Cnn:=CoConnection.Create;
Rst:=CoRecordset.Create;
После этого можно
непосредственно работать со свойствами и
методами ADO. Рекомендуется после создания
экземпляров классов проверить значение
объектной переменной на nil, а еще лучше
заключить конструкцию в блок try… except… end, т.к.
при неуспешном создании экземпляра класса будет
выброшено исключение EOleException.
Следует также отметить, что
созданные экземпляры классов не имеют
деструктора Free или Destroy и не наследуют из других
классов (см. определения), поэтому освобождать
память, занятую экземпляром класса после
использования не нужно. В процедурах и функциях
при выходе такие объектные переменные будут
самостоятельно освобождены. Использование Dispose в
такой ситуации может привести к серьезной
ошибке!
Еще несколько слов следует
сказать по поводу конструктора CreateRemote, он
позволяет создать экземпляр класса, который
работает на удаленном компьютере. На
машине-клиенте в таком случае требуется *.TLB
библиотека удаленного сервера автоматизации, а
также компоненты DCOM, в случае, если клиент
работает под Windows 95/98. При использовании Windows NT или
Windows 2000 на клиенте требуется только правильная
регистрация *.TLB, а связь с серверным приложением
будет осуществляться посредством Automation Manager.
Построение серверов автоматизации ActiveX выходит
за рамки данной статьи и для детального изучения
этой задачи следует обратиться к литературе
(ActiveX_Srv.doc 167 424).
На примере использования объекта ADODB.Connection покажем как можно записывать данные в таблицы на SQL Server, а также обновлять их. Пример взят из реального приложения, которое осуществляет одну из стадий процесса ежедневной загрузки оперативной информации в БД Минского областного управления Белагропромбанка. Важные строки кода выделены красным цветом. Не стоит обращать внимание на множество вспомогательных переменных и функций, здесь важно понять принцип:
function TfrmMain.ProcessData: integer;
var
i, j, k, c, intables, temp, total: integer;
Connect, Database, TableName, Password, SQL: string;
OtherTables: array [1..5] of string;
Cnn: Connection;
LV: OleVariant;
CancelUpdate, F: boolean;
begin
StillExecuting:=True;
Result:=-1;
if LoadDataToMemory then begin
total:=GiveTotalProgress;
c:=0;
{Главный
цикл записи в базу на SQL Server
Здесь
записываются данные только в основные таблицы}
for i:=1 to Files.Count do begin
Application.ProcessMessages;
F:=false;
CancelUpdate:=false;
Database:=GetSQLDatabase(TOFile[i].Name,
1, TableName, intables);
{intables
- содержит общее количество таблиц для записи
в
данной базе Database. 1- индекс 1-й таблицы, которая
присутствует всегда!}
OtherTables[1]:=TableName;
if intables>1 then
for k:=2 to intables do
GetSQLDatabase(TOFile[i].Name, k, OtherTables[k], temp);
Password:=ReadFromINI(AppINIFile,
'Source', 'OFilePwd');
Caption:= InternalFile+' Updating SQL DB ('+Database+
') table ('+TableName+')...';
Connect:=BuildConnection(Database,
Password);
try
Cnn:=CoConnection.Create;
if Cnn<>nil then begin
try
Cnn.Open (Connect,
PrivateUserName, Password);
Cnn.BeginTrans;
for j:=1 to TOFile[i].TotalRecords do begin
if TOFile[i].Name<>'Saldo' then begin
SQL:=GetInsertSQLStatement(TableName,
TOFile[i].Name,
TOFile[i].TotalColumns,
j);
inc(c);
if ((TOFile[i].Name='Sceta') and (TOFile[i].Matrix[j][2]='')) then
CancelUpdate:=true else CancelUpdate:=false;
end
else
begin
{Здесь
обработка сальдо - 3 таблицы}
inc(c);
for k:=1 to intables do begin
CancelUpdate:=true;
SQL:=GetInsertSQLStatement(OtherTables[k],
TOFile[i].Name,
TOFile[i].TotalColumns,
j);
if k<>1 then begin
case k of
2: F:=do_saldo_kre(j);
3: F:=do_saldo_vne(j);
end;
if F then
Cnn.Execute(SQL, LV, adCmdText);
end
else if
IsMonthChanged then Cnn.Execute(SQL, LV, adCmdText);
end;
end;
if not
CancelUpdate then Cnn.Execute(SQL, LV, adCmdText);
SetProgress(20, 80, c, total);
Application.ProcessMessages;
end;
Cnn.CommitTrans;
except
on
EOleException do Cnn.RollbackTrans;
HandleExceptions;
end;
Cnn.Close;
end;
Result:=0;
except
Result:=1;
end;
end;
end;
end;
Работа функции начинается с определения имени базы данных (переменная Database) в которую предполагается записывать данные, имени компьютера-клиента (функция PrivateUserName) и пароля (переменная Password) для доступа к БД. Далее функция BuildConnection возвращает строку подключения к СУБД SQL Server в переменную Connect. Следует отметить, что строка подключения в данном случае отличается от той, которая используется в аналогичной ситуации в программе на VB. В данном случае строка подключения содержит следующее: driver={SQL Server}; server=SQL_Server; uid=Alex; pwd=mypass; database=any_db; как видно из записи, в строке подключения отсутствует ссылка на Provider; значения для pwd и database естественно берутся из передаваемых параметров.
Далее создается экземпляр объекта Connection cnn:=CoConnection.Create) и в случае успеха открывается сеанс работы с одной из таблиц БД (Cnn.Open). Для повышения сохранности данных в процессе записи в БД используются методы для работы с транзакциями BeginTrans, CommitTrans и RollBackTrans. В случае, если одна из попыток записи в таблицу приводит к исключению, RollBackTrans (см. блок except) позволяет откатить транзакции назад и вернуть прежнее состояние данных в таблице. Фактическая запись данных в БД происходит только когда выполнится метод CommitTrans, а не Execute; Все вызовы Execute приводят лишь к передаче данных на SQL Server и помещению их во временные таблицы системной базы tempdb, которая управляется самой СУБД. Администратору базы данных следует внимательно следить за тем, чтобы не переполнился журнал транзакций для рабочей БД, в противном случае у клиента также возникнет исключение при попытке выполнить очередной Execute.
Цикл записи в таблицу БД состоит
из последовательного выполнения инструкций INSERT
INTO … VALUES …. Каждый оператор SQL конструируется на
основе данных, хранящихся в специальной
структуре в памяти, имеющей вид прообраза
таблицы в БД и при помощи функции GetInsertSQLStatement
присваивается переменной SQL, затем выполняется
Execute с данным параметром, а в качестве типа
выполняемой команды передается константа adCmdText
(при выполнении хранимых процедур передается
adCmdStoredProc). Синтаксис оператора INSERT … INTO подробно
описан в документации по MS SQL и здесь приводиться
не будет. Следует отметить, что выполнять можно
любую конструкцию SQL, это относится и к
расширенному INSERT … INTO … SELECT, и к инструкциям UPDATE
… SET, DELETE … FROM, SELECT … FROM. В случае с выборкой данных
(SELECT ... FROM) необходимо позаботиться о том, чтобы
результирующий набор данных был передан в объект
Recordset, поэтому выполнение такой инструкции
целесообразно при открытии Recordset как
динамического набора данных, либо при
использовании результирующего значения метода
Connection.Execute (см. описание).
Что касается вызова хранимой
процедуры при помощи метода Connection.Execute,
то в качестве параметров передается имя хранимой
процедуры с аргументами, переменная типа OleVariant и константа adCmdStoredProc:
Cnn.Execute(SPName, _V, adCmdStoredProc);
Аргументы хранимой процедуры
следует заключать в скобки, например “ProcedureName (arg1, arg2, …, argN)”. Если хранимая процедура
возвращает результирующий набор данных, то
результат выполнения метода Execute нужно присвоить объектной
переменной типа Recordset.
Рассмотрим некоторые принципиальные ограничения при использовании объектов ADO в Delphi проектах. В основе этих ограничений в первую очередь лежит невозможность вызова одних и тех же методов разным способом (с параметрами и без параметров, а также с ограниченным числом параметров). Это обусловлено тем, что в Delphi все внешние вызовы декларируются однозначно и более того, нельзя использовать Optional (необязательные) аргументы. Даже если функция может принять все аргументы как Optional, в Delphi обязательно должны быть переданы конкретные значения, как правило переменные типа OleVariant. Это ограничение принципиально и связано с тем, что ADO объекты ориентированы прежде всего на использование в VisualBasic и VBScript проектах, где передача Optional параметров является обычной конструкцией языка. Следует отметить, что передача в качестве Optional параметра значения nil приводит к ошибке несоответсвия типов в случае использования ADO, хотя в некоторых ActiveX™ объектах это допускается. Для пояснения ситуации рассмотрим примеры декларации некоторых методов объекта Recordset:
procedure AddNew(FieldList: OleVariant; Values: OleVariant); safecall;
procedure Update(Fields: OleVariant; Values: OleVariant); safecall;
Из описания видно, что
использование AddNew возможно не иначе, как с
параметрами FieldList
и Values, что
практически сводит на нет добавление записей
в Recordset
классическим способом: сначала AddNew без параметров, затем
обработка коллекцииFields и после этого Update также без параметров. Если же
попробовать описать метод AddNew повторно, как procedureAddNew; safecall; то это приведет к ошибке
компиляции несмотря на то, что формально такое
описание возможно, т.к. все аргументы AddNew являются Optional. Это же относится и ко всем
аналогичным методам объектов Connection, Command, Parameter и Recordset.
Одним из способов решения такой
проблемы, является инкапсуляция методов,
использующих Optional
параметры внутрь ActiveXDLL, написанной на VB и содержащей все необходимые Published методы для последующего
использования в Delphi. Ну а подключение ActiveXDLL к Delphi проекту это не проблема.
Еще одним существенным
ограничением, не позволяющим использовать всю
мощь ADO в
программе на Delphi,
является невозможность приведения к типуOleVariant, таких объектных типов,
как Parameter, Connection и др., хотя соответствующие
описания методов, импортированных из ADODLL и TLB предполагают такое
преобразование, например, посмотрим на метод Execute объекта Command:
function Execute(out RecordsAffected: OleVariant; var Parameters: OleVariant; Options: Integer): Recordset; safecall;
Здесь в качестве аргумента Parameters должен передаваться
указатель на коллекцию Parameters объекта Command, которая содержит параметры для
вызова хранимой процедуры, однако при попытке
сделать это, вы получите исключение во время
выполнения программы (EOleException), хотя компилятор в данном
случае «проглотит» ошибку. К аналогичной ошибке,
только более многозначительной (EAccessViolation) приведет и попытка
установить свойство объекта Command.ActiveConnection посредством метода Set_ActiveConnection с передачей параметра типа Connection, однако в данном случае
есть возможность установитьActiveConnection через переменную типа WideString (желающие могут
поэкспериментировать!), что видно из
соответствующих описаний:
procedure Set_ActiveConnection(const ppvObject: _Connection); overload;
procedure Set_ActiveConnection(const ppvObject: WideString); overload;
Сказанное выше практически
означает, что в Delphi невозможно использовать тесное
взаимодействие объектов ADO — Recordset, Command, Connection и Parameter без привлечения адаптированных
инкапсулирующих библиотек. Идеальным вариантом,
как уже отмечалось выше, является интеграция
компонентов, разработанных на VB и Delphi и разделяющих между собой те
возможности, которые наиболее полно можно
реализовать в каждом из языков.
В данной статье были рассмотрены некоторые аспекты программирования ADO объектов на VB и Delphi, а также показаны принципиальные подходы в их использовании на примерах. Надеюсь, что данная информация окажется полезной тем разработчикам, которые по каким-либо причинам отказываются от использования BDE, а также тем, кто желает совместно использовать ADO и BDE в своих проектах. Кстати следует заметить, что доступ к данным только через ADO позволяет не включать в дистрибутивы программ компоненты BDE, что значительно позволяет уменьшить объем дистрибутива. Важно и то, что использование ADO ни в коем случае не предполагает полный отказ от BDE, а лишь позволяет найти нетрадиционное решение проблемы, глубже понять интерфейсы OLEDB и принципы низкоуровнего доступа к базам данных. Что касается DataEnvironment, то с его помощью возможно быстрое создание приложений-клиентов для доступа к СУБД, используя в основном только стадию визуального проектирования. Такой подход может быть полезен в случае создания множества простых приложений, не требующих существенных затрат на разработку, но в то же время обладающих большими возможностями по сравнению с приложениями, созданными в рамках MicrosoftAccess.