Данная статья посвящена кэшированию записей. Она основана на официальной документации
Введение
Кэширование записей позволяет отложить отправку обновлений в базу, в отличие от методов Post / Delete. Это позволяет отправлять множественные обновления в одной группе, опционально закрытой в транзакции.
Включите свойство CachedUpdates в True. Тогда множество будет следить за всеми изменениями, сделанными после последнего установки в True или последними вызовами CancelUpdates /CommitUpdates. Эти изменения включаются в журнал изменений, где изменения упорядочены по времени. FireDAC не отслеживает множественные изменения одной записи. Последние изменения переписывают предыдущие и перемещают запись в конец списка изменений.
FireDAC поддерживает децентрализованные и централизованные обновления кэша.
- Decentralized Cached Updates – это когда каждое множество отслеживает изменения независимо от других.
- Centralized Cached Updates mode– это когда несколько множеств делят один лог изменений в историческом порядке.
Централизованные кэш-обновления (Centralized Cached Updates)
Когда приложению нужно залогировать и применить множественные обновления в хронологическом порядке, лучше подойдет централизованное обновление.Для этой цели используется класс TFDSchemaAdapter. Экземпляр этого класса должен быть ассоциирован со свойством SchemaAdapter датасетов. TFDSchemaAdapter обслуживает центральное хранилище для рядов и их изменений нескольких датасетов.
Централизованное обновление полезно в режиме master-detail relations, где главное множество распространяет изменения каскадным множествами, включая автоинкрементные поля. Чтобы включить распространение для detail датасета, FetchOptions.DetailCascade должно быть поставлено в True. Это позволяет делать следующее
-Синхронизировать master-detail изменения, таким образом, что изменения в master и detail датасетах будет записано и применено в хронологическом порядке.
-Каскакдное удаление записей в подчиненных таблицах, когда они удаляются в мастер-таблице,
-Каскадное изменение данных в подчиненных таблицах, когда они изменены в главной таблице
Изменения будут выполнены при следующих условиях
-датасеты в master-detail отношениях
-Используется режим range based M/D
-Главное множество (master) это множество FireDAC (TFDQuery, TFDStoredProc, TFDMemTable)
-Подчиненное множество не TFDTable в режиме Live Data Window
Вы также можете использовать DetailServerCascade чтобы контролировать отправляет ли FireDAC изменения в базу. DetailServerCascade нужно использовать вместе с FetchOptions.DetailCascade
Чтобы включить централизованные обновления с влиянием на подчиненные таблицы, сделайте следующие шаги (сначала включите режим range based M/D и включите режим CachedUpdates в True )
1. Добавьте на форму TFDSchemaAdapter
2. Установите свойство главного множества SchemaAdapter, выбрав компонент TFDSchemaAdapter
3. Установите свойство подчиненного множества SchemaAdapter, выбрав компонент TFDSchemaAdapter
4. Установите FetchOptions.DetailCascade в True
В тоже самое время это включает ограничение для множества detail. Это аналогично SQL команде
1 2 3 4 5 |
ALTER TABLE <detail> ADD CONSTRAINT FOREIGN KEY (<detail fields>) REFERENCES <master> (<master fields>) ON DELETE CASCADE ON UPDATE CASCADE |
Чтобы применить обновления, приложение должно использовать метод TFDSchemaAdapter.ApplyUpdates, вместо ApplyUpdates. Чтобы отлавливать ошибки, используйте метод TFDSchemaAdapter.Reconcile, вместо Reconcile.
Можно посмотреть демку, которая идет вместе с установкой Delphi.
FireDAC\Samples\Comp Layer\TFDQuery\CachedUpdates\Centralized
Отслеживание обновлений (Tracking Updates)
Когда ваше приложение работает в режиме кэширования обновлений, вы можете следить за изменениями и опционально отменять обновления для каждого датасета. Чтобы следить за обновлениями, пользуйтесь следующими свойствами
- UpdatesPending – возвращает True, если лог изменений не пустой
- ChangeCount – возвращает число изменений
- UpdateStatus – возвращает тип изменений для конкретной записи
- FilterChanges – позволяет фильтровать записи по типам изменений
Чтобы отменить изменения можно использовать следующие методы
- SavePoint–устанавливает / удаляет текущее состояние лога
- RevertRecord–отменяет текущую запись к предыдущему состоянию
- UndoLastChange— перемещается к последней измененной записи и возвращает её в прежнее состояние
- CancelUpdates–отменяет изменения всех записей в логе изменений.
Например, чтобы сделать кнопки “Назад”, вы можете создать действие actUndo и использовать следующие обработчики
1 2 3 4 5 6 7 8 9 |
procedure TForm1.actUndoUpdate(Sender: TObject); begin actUndo.Enabled := FDQuery1.UpdatesPending; end; procedure TForm1.actUndoExecute(Sender: TObject); begin FDQuery1.UndoLastChange(True); end; |
Для другого примера, чтобы применить транзакции внутри памяти, с возможностью отменить групповые изменения, вы можете сделать следующее
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
FDQuery1.CachedUpdates := True; iSavePoint := FDQuery1.SavePoint; try FDQuery1.Append; ... FDQuery1.Post; FDQuery1.Append; ... FDQuery1.Post; FDQuery1.Append; ... FDQuery1.Post; except FDQuery.SavePoint := iSavePoint; end; |
Следующие методы и свойства работают с журналом обновлений
- The Data свойство включает все записи, даже удаленные и их изменения
- The Delta свойство возвращает удаленные, вставленные или обновленные записи в журнале обновление
- The CopyRecord and CopyDataSet методы создают новые изменения и не копируют журнал изменений
- LoadFromStream, LoadFromFile, SaveToStream, and SaveToFile загружают сохраняют данные с журналом обновлений
В режиме кэширования обновлений некоторые методы или свойство вызывают исключение, если есть изменения в журнале обновлений. Они должны быть подтверждены или отменены.
In cached updates mode, some method or property settings will raise an exception, if there are changes in the updates journal. They must be committed or canceled. Это…
- Refresh;
- setting CachedUpdates to False.
Применение обновлений (Applying Updates)
Для отправки обновлений в базу используйте метод ApplyUpdates. Если запись вызвала исключение, то оно будет ассоциирована с записью. Учтите, что метод ApplyUpdates
-не вызывает исключений, вместо этого, он отправляет число вызванных исключений;
-не оборачивает обновления в транзакции, вместо этого приложение может сделать это самостоятельно
-использует ту же логику обновлений, что и немедленные обновления
После применения обновлений измененные записи по прежнему остаются в логе изменений. Чтобы их убрать оттуда, нужно вызвать метод CommitUpdates
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
FDQuery1.CachedUpdates := True; FDQuery1.Append; ... FDQuery1.Post; FDQuery1.Append; ... FDQuery1.Post; FDQuery1.Append; ... FDQuery1.Post; FDConnection1.StartTransaction; iErrors := FDQuery1.ApplyUpdates; if iErrors = 0 then begin FDQuery1.CommitUpdates; FDConnection1.Commit; end else FDConnection1.Rollback; |
Просмотр ошибок при ApplyUpdates (Reviewing Errors)
Если ошибка происходит внутри ApplyUpdates, тогда метод записывает ошибку во внутреннюю структуру данных и продолжает процесс обновления пока число ошибок не будет равно AMaxErrors. ApplyUpdates не вызывает исключений. Чтобы обработать все ошибочные записи после вызова ApplyUpdates, используйте reconciling процесс, отфильтроваф ошибочные записи.
Чтобы согласовать записи, свяжите обработчик OnReconcileError и метод Reconcile. Событие OnReconcileError позволяет анализировать ошибки, читать, изменять текущие значения полей. На выходе, оно должно получать действие. После вызова метода Reconcile, метод ApplyUpdates может быть вызван снова, чтобы попробовать отправить изменения ошибочных записей снова.
Чтобы отфильтровать ошибочные записи, включите rtHasErrors в FilterChanges. Потом загрузите множество и прочитайте свойство RowError,чтобы получить объект исключения, ассоциированный с текущей записью. Например
To filter erroneous records, include rtHasErrors into FilterChanges. Then navigate through the dataset and read the RowError property to get an exception object associated with the current record. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var oErr: EFDException; ... if FDQuery1.ApplyUpdates > 0 then begin FDQuery1.FilterChanges := [rtModified, rtInserted, rtDeleted, rtHasErrors]; try FDQuery1.First; while not FDQuery1.Eof do begin oErr := FDQuery1.RowError; if oErr <> nil then begin // process exception object ... end; FDQuery1.Next; end; finally FDQuery1.FilterChanges := [rtUnmodified, rtModified, rtInserted]; end; end; |