При этом происходит откат транзакции. Сообщения об
ошибке записываются в строку. В случае возникновения ошибки клиент должен
вывести сообщение и обновить клиентские наборы данных. Как будет видно ниже, в
данном случае все проверки можно сделать заранее, и практически возможны только
ошибки, связанные с непредвиденными обстоятельствами (например, неожиданный
разрыв соединения с сервером БД).
Процедура RenumLines перенумерует строки содержимого
документа так, чтобы номера шли по порядку, причем все номера сначала делаются
отрицательными, иначе при попытке запомнить вторую запись с тем же ключем сразу
генерируется исключение Key violation, что, разумеется, совершенно не нужно
(Дело в том, что провайдер великолепно знает, какие поля составляют первичный
ключ, вот и контролирует – у ClientDataSet создается контроль первичного ключа.
Исключение генерируется сразу, при попытке вставки (до записи в БД)):
procedure TrdmDoc.RenumLines;
var
Num: Integer;
begin
cdsBody.IndexFieldNames :=
'DOC_ID;LINE_NUM';
// Чтобы избежать Key violation при перенумерации, делаем все номера
< 0
// На клиенте нужна проверка LINE_NUM >=
0
cdsBody.Last;
with cdsBody do
while FieldByName('LINE_NUM').AsInteger
> 0 do
begin
Edit;
Num :=
FieldByName('LINE_NUM').AsInteger;
FieldByName('LINE_NUM').AsInteger := -num;
Post;
Last;
end;
// перенумерация...
Num := cdsBody.RecordCount;
cdsBody.First;
with cdsBody do
while FieldByName('LINE_NUM').AsInteger
<= 0 do
begin
Edit;
FieldByName('LINE_NUM').AsInteger := num;
Post;
Dec(Num);
First;
end;
end;
|
Разумеется, и вычисление суммы документа, и
перенумерацию содержимого можно сделать на клиентской части, но этот пример
создавался именно чтобы показать перенос вычислений на сервер. При более
сложных вычислениях это гораздо выгоднее, например, если в расчетах
используются данные из дополнительных таблиц.
Остается последний модуль данных сервера, rdmReport, предназначенный для создания отчета. По сравнению с предыдущими модулями он
довольно прост (рисунок 4.).
Рисунок 4.
Здесь находится всего один компонент транзакции
ibtInOut и один компонент запроса ibqInOut, обращающийся к процедуре отчета:
select
* from REP_INOUT(:FromDate, :ToDate) order by TO_NAME
|
При этом необходимо учитывать, что данные из этой
процедуры получаются совершенно не в том виде, который нужен, и нуждаются в
дополнительной обработке. Такую дополнительную обработку лучше осуществлять на
стороне клиента, так как это потенциально позволяет передавать данные в более
компактном виде, да и само представление данных является частью презентационной
логики. Но этот пример создавался, чтобы продемонстрировать, в основном, работу
серверной стороны. Поэтому обработку данных мы будем производить на сервере.
cdsInOut – это компонент ClientDataSet, в котором формируется отчет в том виде, в котором он должен быть отображен клиенту. К этому компоненту подсоединен
провайдер dspInOut с установленным флагом poIncFieldProps. Его свойство
Exported равно false. От провайдера требуется только генерация пакета данных.
И, как обычно, ResolveToDataSet = true. cdsInOut не соединен ни с каким
провайдером (свойство ProviderName пустое), и должен создаваться явно вызовом
своего метода CreateDataSet. Для того, чтобы набор данных содержал поля, их
описания должны содержаться в свойстве FieldDefs. Но по той причине, что в
отчете-шахматке количество полей в записи заранее неизвестно, их описания
приходится создавать динамически при обработке результата запроса. Для этого
удобно создать отдельный метод, CollectInOutData: