После того, как данные скопированы в эту таблицу, мы можем спокойно, никому не мешая, обрабатывать их так, как нам захочется. А клиенту (точнее, серверу приложений) остается только их выбрать из данной таблицы.
ПРИМЕЧАНИЕНадо отметить, что данный алгоритм применим только в тех случаях, где некритично, что между перечитыванием данных клиентом они могут измениться |
Вот код процедуры, заполняющей эту таблицу:
create proc list_doc @from_date datetime, @to_date datetimeas declare @from_name varchar(224) declare @to_name varchar(224) declare @from_id int declare @to_id int declare @doc_id int declare @doc_num varchar(40) declare @doc_date datetime declare @doc_sum dsum delete from pDoc_Listwhere SPID = @@spid --очищаем временную таблицу от предыдущих данных--вставляемнужныезаписи insert into pDoc_List(SPID, doc_id, doc_num, doc_date, from_id, to_id, doc_sum, from_name, to_name ) select @@spid, doc_id, doc_num, doc_date, from_id, to_id, doc_sum, '', '' from doc_title where doc_date >= @from_date and doc_date <= @to_date --создаем наиболее быстрый курсор для обработки записейdeclare list_docs insensitive cursor for select doc_id, from_id, to_id from pDoc_List where SPID = @@spid for read only open list_docs fetch next from list_docs into @doc_id, @from_id, @to_id while @@fetch_status = 0 begin select @from_name = '', @to_name = '' exec client_full_name @from_id, @from_name output exec client_full_name @to_id, @to_name output--заполняем поля, которых нет в основной таблицеupdate pDoc_List set from_name = @from_name, to_name = @to_name where SPID = @@spid and doc_id = @doc_id fetch next from list_docs into @doc_id, @from_id, @to_id end close list_docs deallocate list_docsgo |
SQL-запрос, исполняемый на сервере приложений для передачи данных клиенту:
exec list_doc @from_date = :from_date, @to_date = :to_dateselect * from pDoc_List where SPID = @@spid | ||
ПРИМЕЧАНИЕПри написании ХП следует обратить особое внимание на одновременную работу нескольких пользователей. Необходимо минимизировать влияние "тяжелых" (отчетных) процедур на работу клиентов (один из вариантов был показан выше). |
Тут ничего сложного нет, необходимо только помнить, что триггеры в MSSQL запускаются только после действия (есть еще вместо (instead of), но это только в MSSQL2000).
Пример: предотвращение удаления клиента, если существуют документы с его участием (чтобы такой триггер не конфликтовал с ограничением ссылочной целостности, в MSSQL необходимо убрать foreign key с таблицы doc_title на client)
create trigger CLIENT_BEFORE_DELETE for CLIENTbefore deleteasbegin if (exists (select * from DOC_TITLE where FROM_ID = OLD.CLIENT_ID)) then exception EX_CLIENT_IN_DOC; if (exists (select * from DOC_TITLE where TO_ID = OLD.CLIENT_ID)) then exception EX_CLIENT_IN_DOC;end^ |
Преобразуется в
create trigger CLIENT_AFTER_DELETE on CLIENTfor deleteas if (exists (select d.CLIENT_ID from DOC_TITLE dt, deleted d where dt.FROM_ID = d.CLIENT_ID))begin --чтобы сообщение было видно на клиенте raiserror ('Существует запись в документе', 16, 1) --необходимо ручками откатить транзакцию rollback transactionend if (exists (select d.CLIENT_ID from DOC_TITLE dt, deleted d where dt.TO_ID = d.CLIENT_ID))begin raiserror ('Существует запись в документе', 16, 1) rollback transaction endgo |
Модификация сервера приложений.
Здесь основная часть переработки связана с переходом от IBX (InterBase Express) к ADO (ActiveX Data Object). Основные вещи, на которые следует обратить внимание:
Реализация транзакций на клиенте – в IBX это отдельный компонент, в ADO такая функциональность предоставляется методами TADOConnection. Еще небольшая рекомендация – аккуратно подходите к выбору уровня изоляции транзакций (чем меньше уровень изоляции, тем быстрее будет работать приложение).
Чтобы клиент работал без переделки с разными источниками данных, необходимо, чтобы типы данных полей совпадали. Например, в IBX для numeric(15, 4) по умолчанию подставляется TFloatField, а в ADO – TBCDField. Это единственное отличие, которое мне встретилось при переносе (но это не значит, что их вообще нет). Проблема решилась ручной установкой данного типа поля в TCurrencyField.
Перевод sql-выражений из синтаксиса IB в MSSQL.
Отличия, связанные с различиями структуры БД. Например, если будет реализовано каскадное удаление с помощью ХП, то придется реализовывать эту логику внутри сервера, чтобы оставить клиента нетронутым.
В качестве примера приведем перевод одной из процедур сервера приложений:
//Описание того, что делает данная процедура, читайте в статье Игнатьева.//Код, работающийс IBXfunction TrdmDoc.ApplyChanges: WideString;begin lock; try FLastUpdateErrors := ''; if FState = osInactive then raise Exception.Create('Документнебылсозданлибооткрыт'); with cdsTitle do begin Edit; FieldByName('DOC_SUM').asCurrency := CalcSum; Post; end; ibtDoc.StartTransaction; //ibtDoc – компоненттранзакции if FState = osInsert then begin if cdsTitle.ChangeCount > 0 then cdsTitle.ApplyUpdates(-1); if cdsBody.ChangeCount > 0 then cdsBody.ApplyUpdates(-1); end; if FState = osUpdate then begin if cdsBody.ChangeCount > 0 then cdsBody.ApplyUpdates(-1); if cdsTitle.ChangeCount > 0 then cdsTitle.ApplyUpdates(-1); end; Result := FLastUpdateErrors; if Result = '' then ibtDoc.Commit else begin ibtDoc.Rollback; end; finally ibtDoc.Active := False; //DefaultAction = Rollback unlock; end;end;//Код, работающийс ADOfunction TrdmDoc.ApplyChanges: WideString;begin lock; try FLastUpdateErrors := ''; if FState = osInactive then raise Exception.Create('Документнебылсозданлибооткрыт'); with cdsTitle do begin Edit; FieldByName('DOC_SUM').asCurrency := CalcSum;Post; end; adcDocs.BeginTrans; //явные транзакции задаются на уровне соединения if FState = osInsert then //а не отдельным компонентомbegin if cdsTitle.ChangeCount > 0 then cdsTitle.ApplyUpdates(-1); if cdsBody.ChangeCount > 0 then cdsBody.ApplyUpdates(-1); end; if FState = osUpdate then begin if cdsBody.ChangeCount > 0 then cdsBody.ApplyUpdates(-1); if cdsTitle.ChangeCount > 0 then cdsTitle.ApplyUpdates(-1); end; Result := FLastUpdateErrors; if Result = '' then adcDocs.CommitTrans else begin adcDocs.RollbackTrans; end; finally unlock; end;end; |
Это всего лишь пример. В реальных приложениях следует более тщательно продумывать перенос приложений. Например, переписывать лучше не весь сервер приложений, а только зависимый от источника данных код, вынося его в отдельные модули.
Данная статья не претендует на полноту освещения данного вопроса, а также и автор при изложении подходов для решения проблем не претендует на роль "истины в последней инстанции". Здесь был изложен лишь минимум сведений, необходимый для решения поставленной задачи, а также некоторые размышления, которые могут помочь при решении схожих проблем.
Все вопросы, замечания, исправления, дополнения направляйте на kapusto@mail.ru
Хочу выразить признательность Игнатьеву Роману, Павлу Шмакову за советы, критику и настойчивость.