Демо II. Авторизация SyncAdapterObjects с использованием TSQL и SProcs
В первой демонстрационной программе я показал как, используя SyncAdapterBuilder, который помогает создавать команды базы данных для двусторонней синхронизации, довольно быстро можно написать оффлайн-приложение. Для клиента нам не нужно ничего делать, кроме определения интересующих нас таблиц и соединения с локальной базой данных SQLCE. SQLCE 3.5 и SyncClientProvider сами все сделают за нас. Следует напомнить, что SyncAgent осуществляет синхронизацию между двумя хранилищами, представленными ClientSyncProvider и ServerSyncProvider. Интерфейс SyncAgent дополняет Synchronize(), который и проделывает всю работу.
Несмотря на то, что SyncAdapterBuilder представляет собой очень полезную утилиту, она скрывает некоторую логику синхронизации, которую, как мне кажется, важно понимать, для того чтобы вам стала понятна основные моменты работы синхронизации, показанные в предыдущем демо. Команды SyncAdapter расширяют возможности этой структуры, и вы можете управлять процессом более гибко.
В данном примере я хотел бы показать вам, как написать команды SyncAdapter, используя утверждения TSQL или сохраняемые процедуры, которые вы должны будете создать для серверной части базы данных. Для того чтобы показать вам оба эти момента в одном приложении, я решил написать утверждения TSQL для отметки изменений и хранимые процедуры для применения изменений.
Прежде чем мы углубимся в программирование, позвольте мне рассказать вам про интерфейс типа SyncAdapter. На нижеследующем рисунке изображено то, что вы уведите в «object browser» при навигации пространства имен Microsoft.Synchronization.Data.Server и клиента типа SyncAdapter:

Как видите, здесь присутствует восемь свойств, оканчивающихся словом 'Command'. Если вы проверите тип команды, то обнаружите, что это System.Data.IDbCommand. Это означает, что SyncAdapter – это такая база данных, в которой вы можете назначить любой объект команды ADO.NET, то есть OleDbCommand, SqlCommand, OracleCommand и т.д.
Команды синхронизации в SyncAdapter могут быть разделены на две категории:
- Команды выбора изменений
- Команды применения изменений
Вы действительно должны писать все восемь команд для осуществления синхронизации? На самом деле нет. Однако, все зависит от вашего сценария. В следующей таблице приведены команды, необходимые для каждого сценария:
Сценарий
|
Необходимые команды базы данных
|
Снять копию с данных (без приращений, всегда копируется все содержимое таблицы)
|
SelectIncrementalInsertsCommand or SelectIncrementalUpdatesCommand
|
Загрузка(Download) без удалений
|
SelectIncrementalInsertsCommand and SelectIncrementalUpdates при желании можно использовать одну команду для выбора вставок и удалений
|
Загрузка(Download) с удалениями
|
SelectIncrementalInsertsCommand and SelectIncrementalUpdatesCommand and SelectIncrementalDeletesCommand
|
Загрузка(Upload) без удалений
|
InsertCommad and UpdateCommand
|
Загрузка(Upload) с удалениями
|
InsertCommad and UpdateCommand and DeleteCommand
|
Загрузка(Upload) с удалениями и определением конфликтов
|
InsertCommad and UpdateCommand and DeleteCommand and SelectConflictUpdatedRowsCommand and SelectConflictDeletedRowsCommand
|
Двухсторонний
|
Команды для одного из сценариев загрузки(download) + Команды для одного из сценариев загрузки(upload)
|
В данной демонстрационной программе я проделал долгий путь по реализации всех команд. Команда определения конфликтов выходит за рамки этой демонстрации и будет рассмотрена позднее. Теперь давайте посмотрим на саму программу.
Команды выбора изменений
Для таблицы orders, команды выбора изменений выглядят следующим образом:
// orders table SyncAdapter adaptorOrders = new SyncAdapter("orders"); adaptorOrders.CreationOriginatorColumnName = "update_originator_id";
// select incremental inserts command SqlCommand incInsOrdersCmd = new SqlCommand(); incInsOrdersCmd.CommandType = CommandType.Text; incInsOrdersCmd.CommandText = "select order_id, order_date from [orders] " + "Where create_timestamp > @sync_last_received_anchor " + "and create_timestamp <= @sync_new_received_anchor " + "and update_originator_id <> @sync_client_id_hash " + "order by create_timestamp desc "; incInsOrdersCmd.Parameters.Add("@" + SyncSession.SyncClientIdHash, SqlDbType.Int); incInsOrdersCmd.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Binary, 8); incInsOrdersCmd.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Binary, 8);
adaptorOrders.SelectIncrementalInsertsCommand = incInsOrdersCmd;
Чтобы выбрать вставки, которые произошли со времени последней синхронизации с сервером, мне нужно следующее:
- Время последней синхронизации клиента. Это значение хранится локально и доставляется команде выбора вставок в параметре сеанса @sync_last_received_anchor (SyncSession.SyncLastReceivedAnchor). При выполнении этот параметр в команде (в коллекции параметров команды) заменяется на нужное значение.
- Время, определяющее последний момент, когда не будет превышен перебор. После следующей синхронизации это значение будет нижней границей. Новое значение указателя получается командой SelectNewAnchor, расширенной в интерфейсе provider. В нашей программе команда SelectNewAnchor очень проста: "Select @@DBTS".
- ID клиента. Это менее очевидно. Представьте, что клиент загрузил изменения на сервер, а теперь загружает изменения с сервера. Если при этом я не буду знать какие изменения внес клиент, я загружу те же самые изменения, которые только что загрузил на сервер. Поэтому, я должен получить все строки, кроме тех, которые были изменены данным клиентом. Вот где нам понадобится ID клиента. Здесь также параметры сеанса SyncSession.SyncClientIdHash будут заменены во время выполнения на символы, «#» количество которых соответствует клиентскому GUID (глобально уникальный идентификатор). Это подойдет для демонстрационной программы, так как я не думаю, что ее будут использовать слишком много клиентов, чтобы вызвать риск переполнения. Если хотите, можно использовать и сам номер GUID, используя SyncSession.SyncClientId.
SelectIncremenralUpdatesCommand и SelecctIncrementalDeletesCommand работают также. Ниже приведен код, показывающий обе команды (обратите внимание на то, что я клонировал команды вставки, так как для них используются те же параметры):
// select incremental updates command SqlCommand incUpdOrdersCmd = incInsOrdersCmd.Clone(); incUpdOrdersCmd.CommandText = "select order_id, order_date from [orders] " + "where create_timestamp <= @sync_last_received_anchor " + "and update_timestamp > @sync_last_received_anchor " + "and update_timestamp <= @sync_new_received_anchor " + "and update_originator_id <> @sync_client_id_hash " + "order by update_timestamp desc "; adaptorOrders.SelectIncrementalUpdatesCommand = incUpdOrdersCmd;
// select incremental deletes command SqlCommand incDelOrdersCmd = incInsOrdersCmd.Clone(); incDelOrdersCmd.CommandText = "select order_id from [orders_tombstone] " + "where update_timestamp > @sync_last_received_anchor " + "and update_timestamp <= @sync_new_received_anchor " + "and update_originator_id <> @sync_client_id_hash " + "order by update_timestamp desc "; adaptorOrders.SelectIncrementalDeletesCommand = incDelOrdersCmd;
Важным элементом демонстрации является новая концепция параметров сессии синхронизации. Это ключевые значения времени выполнения программы, с которыми вы должны работать, чтобы сделать возможной синхронизацию, так же как я поступил с командой выбора изменений. До сих пор вы столкнулись с параметрами указателя и ID клиента. Больше вам ничего не потребуется.
Применение команд изменения
Написать команды синхронизации как хранимые процедуры ничего не стоит. Если вы знакомы с ADO.NET, вы знаете, как это легко. Текст для хранимых процедур можно найти в файле server_procs.sql. Следующий листинг представляет процедуру InsertCommand вместе с хранимой процедурой на сервере. Во время исполнения параметр сеанса и актуальная колонка значений будут заменены на изменения, которые возвращает ClientSyncProvider из вызова GetChanges().
// insert order row command SqlCommand insOrdersCmd = new SqlCommand(); insOrdersCmd.CommandType = CommandType.StoredProcedure; insOrdersCmd.CommandText = "sp_orders_applyinsert"; insOrdersCmd.Parameters.Add("@order_id", SqlDbType.Int); insOrdersCmd.Parameters.Add("@order_date", SqlDbType.DateTime); insOrdersCmd.Parameters.Add("@" + SyncSession.SyncClientIdHash, SqlDbType.Int); insOrdersCmd.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Binary, 8); insOrdersCmd.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output; adaptorOrders.InsertCommand = insOrdersCmd;
// insert order row stored procedure create procedure dbo.sp_orders_applyinsert @sync_last_received_anchor binary(8) , @sync_client_id_hash int , @sync_rowcount int out, @order_id int = NULL , @order_date datetime = NULL) as insert into [orders] ([order_id], [order_date], [update_originator_id]) values (@order_id, @order_date, @sync_client_id_hash) set @sync_rowcount = @@rowcount go
Обратите внимание на то, что хранимые процедуры (или TSQL в этом случае) должны возвращать количество строк и выходной параметр. Это еще один параметр сеанса (SyncSession.SyncRowCount), с которым вы должны быть знакомы. Это значение указывает на строку, которая применилась (>0) или нет. Если процедура не применилась к строке из-за проверки ограничений или нарушения PK или по другой причине, во время выполнения запустится механизм отслеживания конфликтов, который я рассмотрю в следующих демонстрациях.
Команды обновления и удаления пишутся сходным образом.
Шаги инсталляции приложения OfflineAppDemo:
- Запустите SQL Server и загрузите файл demo.sql
- Запустите скрипт до маркера "test sample"
- Загрузите файл server_procs.sql и запустите его для создания процедур синхронизации на сервере
- Загрузите решение VS (OfflineAppDemo-Builder Project)
- Скомпилируйте проект
- Все готово
|