Поделиться через


Как настроить и выполнить синхронизацию совместной работы (не SQL Server)

Примечание

В этом разделе документации Синхронизация других баз данных, совместимых с ADO.NET демонстрируется, как можно выполнить синхронизацию баз данных, отличных от SQL Server, посредством Sync Framework. В этой версии в примерах кода используется SQL Server, но сам код можно использовать для других, совместимых с ADO.NET, баз данных с некоторыми изменениями специфических объектов SQL Server (например, SqlConnection) и отображаемых SQL-запросов. Дополнительные сведения о синхронизации SQL Server см. в разделе Как настроить и выполнить синхронизацию базы данных (SQL Server).

В этом разделе описываются ключевые компоненты приложения, используемые платформой Sync Framework для синхронизации нескольких баз данных. Код этого приложения построен на следующих классах Sync Framework:

Сведения о запуске образца кода см. в подразделе «Образцы приложений в разделах инструкций» раздела Синхронизация других баз данных, совместимых с ADO.NET.

В разделе Архитектура и классы для синхронизации базы данных упоминается, что синхронизация может выполняться между двумя экземплярами DbSyncProvider, двумя экземплярами SqlCeSyncProvider либо между экземплярами разных поставщиков. Пример кода в этом разделе взят из двухуровневого приложения, и поэтому в нем не показана синхронизация двух экземпляров SqlCeSyncProvider (для такой синхронизации требуется многоуровневая конфигурация). Пример многоуровневой конфигурации см. в образце WebSharingAppDemo-CEProviderEndToEnd, который входит в пакет SDK для Sync Framework.

Настройка синхронизации состоит из следующих этапов.

  1. Создание таблиц отслеживания для хранения метаданных и хранимых процедур для обновления данных и метаданных в каждой базе данных сервера. Дополнительные сведения см. в разделе Как подготовить серверную базу данных для синхронизации совместной работы (не SQL Server).

  2. Инициализация каждой базы данных сервера со схемой, данными и инфраструктурой отслеживания изменений.

Выполнение синхронизации состоит из следующих этапов.

  1. Создание адаптеров синхронизации и службы синхронизации для сервера, а также службы синхронизации для клиента.

  2. Инициализация каждой клиентской базы данных.

  3. Создание модуля взаимодействия синхронизации и синхронизация узлов.

Инициализация базы данных сервера

В ходе инициализации базы данных выполняется копирование схемы таблицы и инфраструктуры отслеживания изменений, а также всех необходимых исходных данных в каждую базу. Для баз данных, которые синхронизируются с помощью DbSyncProvider, Sync Framework не создает схему таблиц и инфраструктуру отслеживания изменений автоматически для каждой базы данных. Приложение должно обеспечить наличие этих объектов до синхронизации узлов. Для копирования объектов на каждый узел можно использовать технологию резервного копирования и восстановления или другую технологию, однако так следует делать только в случае, если в базе данных, для которой создается резервная копия, не происходят изменения. Если первая база данных находится в использовании и обновляется, настоятельно рекомендуется копировать на каждый узел только схему и инфраструктуру отслеживания изменений, а для копирования данных использовать средства Sync Framework. Если данные копировать другим способом, то на узле могут оказаться пропущены изменения, зафиксированные на первом узле. Платформа Sync Framework может инициализировать данные на каждом узле при условии, что данные находятся хотя бы на одном из узлов. Такой подход используется в примере кода из этого раздела: каждая база данных содержит две таблицы, однако данные содержатся только в таблицах на узле SyncSamplesDb_Peer1. Данные копируются на остальные узлы в ходе первого сеанса синхронизации.

Сведения о параметрах инициализации для SqlCeSyncProvider см. в подразделе Инициализация клиентской базы данных далее в этом разделе.

Выполнение синхронизации

Примеры кода в этом разделе подразделяются на следующие категории:

  • объект DbSyncAdapter и команды для каждой таблицы, которая будет синхронизироваться в базе данных сервера;

  • объект DbSyncProvider и команды для сервера;

  • объект SqlCeSyncProvider для клиента;

  • объект SyncSchema и метод GenerateSnapshot, с помощью которых инициализируется клиентская база данных;

  • объект SyncOrchestrator, который используется для синхронизации серверной и клиентской базы данных.

Адаптер синхронизации

В объекте DbSyncAdapter для каждой таблицы задаются следующие свойства.

Свойство адаптера синхронизации

Использование

RowIdColumns

Задает столбцы первичного ключа для таблицы. Например, если таблица Customer содержит первичный ключ CustomerId, то код будет иметь следующий вид: adapter.RowIdColumns.Add(CustomerId).

SelectIncrementalChangesCommand

Выберите все изменения с момента предыдущего сеанса синхронизации, соединив базовую таблицу и относящуюся к ней таблицу отслеживания изменений. Это максимальный набор изменений, который может быть синхронизирован. Этот набор изменений можно сократить, если учесть набор знаний синхронизации.

InsertCommand, UpdateCommand и DeleteCommand

Примените к узлу операции вставки, обновления и удаления, выбранные с другого узла. Эти изменения выбираются с помощью запроса или хранимой процедуры, заданной для свойства SelectIncrementalChangesCommand.

InsertMetadataCommand, UpdateMetadataCommand и DeleteMetadataCommand

Обновите таблицы отслеживания изменений на узле в соответствии с изменениями, выбранными с этого узла и примененными на другом узле. Эти обновления позволяют платформ Sync Framework время и место происхождения изменений.

SelectRowCommand

Выберите метаданные для строки, которая участвует в конфликте во время синхронизации.

SelectMetadataForCleanupCommand

Выберите метаданные, которые можно очистить на узле. Очистка обычно выполняется на основании срока хранения: метаданные хранятся в течение определенного времени. Но приложение может использовать другую логику при очистке метаданных. Дополнительные сведения см. в разделе Как очищать метаданные для синхронизации совместной работы (Non-SQL Server).

В примерах кода данного раздела создаются команды, которые объект DbSyncAdapter выполняет во время синхронизации. Эти команды вызывают хранимые процедуры, описанные в разделе Как подготовить серверную базу данных для синхронизации совместной работы (не SQL Server). Полный набор команд приводится в полном примере кода в конце этого раздела. Параметры кода, заданные в виде "@" + DbSyncSession, являются переменными сеанса. Например, в результате разрешения переменной "@" + DbSyncSession.SyncMinTimestamp получается значение @sync\_min\_timestamp. Дополнительные сведения см. в разделе Как использовать переменные сеанса для синхронизации совместной работы (не SQL Server).

Код приложения для команды SelectIncrementalChangesCommand

SqlCommand chgsCustomerCmd = new SqlCommand();
chgsCustomerCmd.CommandType = CommandType.StoredProcedure;
chgsCustomerCmd.CommandText = "Sync.sp_Customer_SelectChanges";
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMetadataOnly, SqlDbType.Int);
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncInitialize, SqlDbType.Int);

adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd;
Dim chgsCustomerCmd As New SqlCommand()
With chgsCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_SelectChanges"
    .Parameters.Add("@" & DbSyncSession.SyncMetadataOnly, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncInitialize, SqlDbType.Int)
End With

adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd
Dim chgsCustomerCmd As New SqlCommand()
With chgsCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_SelectChanges"
    .Parameters.Add("@" & DbSyncSession.SyncMetadataOnly, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncInitialize, SqlDbType.Int)
End With

adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd

Код приложения для команды UpdateCommand

SqlCommand updCustomerCmd = new SqlCommand();
updCustomerCmd.CommandType = CommandType.StoredProcedure;
updCustomerCmd.CommandText = "Sync.sp_Customer_ApplyUpdate";
updCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
updCustomerCmd.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
updCustomerCmd.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
updCustomerCmd.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;
updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncForceWrite, SqlDbType.Int);

adapterCustomer.UpdateCommand = updCustomerCmd;
Dim updCustomerCmd As New SqlCommand()
With updCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_ApplyUpdate"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
    .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
    .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
    .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
    .Parameters.Add("@" & DbSyncSession.SyncForceWrite, SqlDbType.Int)
End With

adapterCustomer.UpdateCommand = updCustomerCmd
Dim updCustomerCmd As New SqlCommand()
With updCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_ApplyUpdate"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
    .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
    .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
    .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
    .Parameters.Add("@" & DbSyncSession.SyncForceWrite, SqlDbType.Int)
End With

adapterCustomer.UpdateCommand = updCustomerCmd

Код приложения для команды UpdateMetadataCommand

SqlCommand updMetadataCustomerCmd = new SqlCommand();
updMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
updMetadataCustomerCmd.CommandText = "Sync.sp_Customer_UpdateMetadata";
updMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerKey, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowIsTombstone, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd;
Dim updMetadataCustomerCmd As New SqlCommand()
With updMetadataCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_UpdateMetadata"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncCreatePeerKey, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncRowIsTombstone, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
End With

adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd
Dim updMetadataCustomerCmd As New SqlCommand()
With updMetadataCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_UpdateMetadata"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncCreatePeerKey, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncRowIsTombstone, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
End With

adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd

Код приложения для команды SelectRowCommand

SqlCommand selRowCustomerCmd = new SqlCommand();
selRowCustomerCmd.CommandType = CommandType.StoredProcedure;
selRowCustomerCmd.CommandText = "Sync.sp_Customer_SelectRow";
selRowCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
selRowCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);

adapterCustomer.SelectRowCommand = selRowCustomerCmd;
Dim selRowCustomerCmd As New SqlCommand()
With selRowCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_SelectRow"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
End With

adapterCustomer.SelectRowCommand = selRowCustomerCmd
Dim selRowCustomerCmd As New SqlCommand()
With selRowCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_SelectRow"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
End With

adapterCustomer.SelectRowCommand = selRowCustomerCmd

После определения всех команд адаптер добавляется в поставщик с помощью следующего кода.

sampleProvider.SyncAdapters.Add(adapterCustomer);

Служба синхронизации сервера

В объекте DbSyncProvider для сервера задаются следующие свойства.

Свойство службы синхронизации

Использование

ScopeName

Имя областей, синхронизируемых для заданного сеанса. Каждая область должна существовать в таблице сведений области в базе данных сервера.

Connection

Соединение, по которому Sync Framework выбирает и применяет изменения в базе данных сервера.

SelectNewTimestampCommand

Возвращает значение отметки времени, которое используется для выбора и применения изменений в каждой базе данных. При выполнении текущего сеанса синхронизации команда создает новое значение отметки времени. Синхронизируются изменения, сделанные после отметки времени предыдущего сеанса синхронизации и до новой отметки времени. Затем новое значение сохраняется и используется как начальная точка для следующего сеанса.

SelectTableMaxTimestampsCommand

Выбирает максимальную отметку времени из каждой базовой таблицы или таблицы отслеживания, чтобы определить, находятся ли в назначении все изменения из источника для каждой таблицы. Если в назначении уже имеются изменения, платформа Sync Framework зачастую может избежать выполнения запросов перечисления, что повышает производительность.

Это свойство является необязательным. Если команда не указана, то запросы перечисления выполняются для всех таблиц.

SelectScopeInfoCommand

Возвращает сведения из таблицы сведений области, например набор знаний синхронизации и набор знаний очистки, которые требуются Sync Framework.

UpdateScopeInfoCommand

Обновляет данные в таблице сведений области.

SelectOverlappingScopesCommand

Возвращает имя области и имя таблицы для всех таблиц в указанной области, которые также входят в другие области.

UpdateScopeCleanupTimestampCommand

Обновляет столбец scope_cleanup_timestamp для заданной области в таблице сведений области, чтобы отметить момент, до которого выполнена очистка для этой области. Дополнительные сведения см. в разделе Как очищать метаданные для синхронизации совместной работы (Non-SQL Server).

В примерах кода данного раздела создаются команды, которые объект DbSyncProvider выполняет во время синхронизации. Команды для свойств SelectScopeInfoCommand и UpdateScopeInfoCommand включены в полный пример кода в конце этого раздела.

Код приложения для имени ScopeName и соединения

В следующем примере кода задается имя синхронизируемой области и соединение, по которому выполняется синхронизация. В полном примере кода объект DbSyncProvider содержит объект DbSyncAdapter для таблицы Customer. При первой синхронизации таблицы задается определение области Sales. После первой синхронизации области данную область нельзя изменять. Изменение таблиц в области или фильтрация предложений для этих таблиц может привести к расхождению данных.

SqlConnection peerConnection = new SqlConnection(peerConnString);
sampleDbProvider.Connection = peerConnection;
sampleDbProvider.ScopeName = "Sales";
Dim peerConnection As New SqlConnection(peerConnString)
sampleDbProvider.Connection = peerConnection
sampleDbProvider.ScopeName = "Sales"
Dim peerConnection As New SqlConnection(peerConnString)
sampleDbProvider.Connection = peerConnection
sampleDbProvider.ScopeName = "Sales"

Дополнительные сведения об областях см. в подразделе «Определение областей для синхронизации» раздела Как подготовить серверную базу данных для синхронизации совместной работы (не SQL Server).

Код приложения для команды SelectNewTimestampCommand

В следующем примере кода создается команда для свойства SelectNewTimestampCommand.

SqlCommand selectNewTimestampCommand = new SqlCommand();
string newTimestampVariable = "@" + DbSyncSession.SyncNewTimestamp;
selectNewTimestampCommand.CommandText = "SELECT " + newTimestampVariable + " = min_active_rowversion() - 1";
selectNewTimestampCommand.Parameters.Add(newTimestampVariable, SqlDbType.Timestamp);
selectNewTimestampCommand.Parameters[newTimestampVariable].Direction = ParameterDirection.Output;

sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand;
Dim selectNewTimestampCommand As New SqlCommand()
Dim newTimestampVariable As String = "@" & DbSyncSession.SyncNewTimestamp
With selectNewTimestampCommand
    .CommandText = "SELECT " & newTimestampVariable & " = min_active_rowversion() - 1"
    .Parameters.Add(newTimestampVariable, SqlDbType.Timestamp)
    .Parameters(newTimestampVariable).Direction = ParameterDirection.Output
End With

sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand
Dim selectNewTimestampCommand As New SqlCommand()
Dim newTimestampVariable As String = "@" & DbSyncSession.SyncNewTimestamp
With selectNewTimestampCommand
    .CommandText = "SELECT " & newTimestampVariable & " = min_active_rowversion() - 1"
    .Parameters.Add(newTimestampVariable, SqlDbType.Timestamp)
    .Parameters(newTimestampVariable).Direction = ParameterDirection.Output
End With

sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand

Код приложения для команды SelectTableMaxTimestampsCommand

В следующем примере кода создается команда для свойства SelectTableMaxTimestampsCommand. В полный пример кода входят сеансы синхронизации, в которых новые изменения не выполняются ни на одном из узлов. В таких сеансах вызов команды SelectTableMaxTimestampsCommand показывает, что для синхронизации недоступны изменения данных, и команда SelectIncrementalChangesCommand не вызывается.

SqlCommand selTableMaxTsCmd = new SqlCommand();
selTableMaxTsCmd.CommandType = CommandType.Text;
selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " +
                               "MAX(local_update_peer_timestamp) AS max_timestamp " +
                               "FROM Sync.Customer_Tracking";
sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd;
Dim selTableMaxTsCmd As New SqlCommand()
selTableMaxTsCmd.CommandType = CommandType.Text
selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " _
                             & "MAX(local_update_peer_timestamp) AS max_timestamp " _
                             & "FROM Sync.Customer_Tracking"
sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd
Dim selTableMaxTsCmd As New SqlCommand()
selTableMaxTsCmd.CommandType = CommandType.Text
selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " _
                             & "MAX(local_update_peer_timestamp) AS max_timestamp " _
                             & "FROM Sync.Customer_Tracking"
sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd

Служба синхронизации клиента

В следующем примере кода создается объект SqlCeSyncProvider, задается область и соединение, а также выполняется регистрация обработчиков событий. Адаптеры для SqlCeSyncProvider являются закрытыми, и их команды создаются автоматически на основании схемы, которая инициализируется в клиентской базе данных.

public SqlCeSyncProvider ConfigureCeSyncProvider(string sqlCeConnString)
{

    SqlCeSyncProvider sampleCeProvider = new SqlCeSyncProvider();

    //Set the scope name
    sampleCeProvider.ScopeName = "Sales";

    //Set the connection
    sampleCeProvider.Connection = new SqlCeConnection(sqlCeConnString);

    //Register event handlers

    //Register the BeginSnapshotInitialization event handler. 
    //It is called when snapshot initialization is about to begin
    //for a particular scope in a Compact database.
    sampleCeProvider.BeginSnapshotInitialization += new EventHandler<DbBeginSnapshotInitializationEventArgs>(sampleCeProvider_BeginSnapshotInitialization);

    //Register the EndSnapshotInitialization event handler. 
    //It is called when snapshot initialization has completed
    //for a particular scope in a Compact database.
    sampleCeProvider.EndSnapshotInitialization += new EventHandler<DbEndSnapshotInitializationEventArgs>(sampleCeProvider_EndSnapshotInitialization);

    return sampleCeProvider;
}
Public Function ConfigureCeSyncProvider(ByVal sqlCeConnString As String) As SqlCeSyncProvider

    Dim sampleCeProvider As New SqlCeSyncProvider()

    'Set the scope name 
    sampleCeProvider.ScopeName = "Sales"

    'Set the connection 
    sampleCeProvider.Connection = New SqlCeConnection(sqlCeConnString)

    'Register event handlers 

    'Register the BeginSnapshotInitialization event handler. 
    'It is called when snapshot initialization is about to begin 
    'for a particular scope in a Compact database. 
    AddHandler sampleCeProvider.BeginSnapshotInitialization, AddressOf sampleCeProvider_BeginSnapshotInitialization

    'Register the EndSnapshotInitialization event handler. 
    'It is called when snapshot initialization has completed 
    'for a particular scope in a Compact database. 
    AddHandler sampleCeProvider.EndSnapshotInitialization, AddressOf sampleCeProvider_EndSnapshotInitialization

    Return sampleCeProvider
End Function

Инициализация клиентской базы данных

Перед тем как клиентская база данных SQL Server Compact сможет принимать изменения из другой базы данных, в ней должна находиться схема таблиц и исходные данные, а также инфраструктура отслеживания изменений, необходимая Sync Framework. Платформа Sync Framework предоставляет два способа для инициализации базы данных.

  • Полная инициализация клиентской базы данных с использованием базы данных сервера

    Эта операция позволяет создавать таблицы в клиентской базе данных с использованием таблиц, столбцов и таблиц описания типов, доступных с помощью объекта DbSyncProvider, который подключается к экземпляру базы данных сервера. Затем клиентская база данных готовится к синхронизации, и все строки из базы данных сервера загружается в виде добавочных операций вставки. Операции вставки выбираются с сервера с помощью запроса или процедуры, указанных в свойстве SelectIncrementalChangesCommand.

  • Инициализация моментального снимка клиентской базы данных с использованием существующей клиентской базы данных

    Инициализация моментальных снимков используется для уменьшения времени, необходимого для инициализации клиентской базы данных. После того как одна клиентская база данных инициализирована путем полной инициализации, другие базы данных можно инициализировать с использованием моментального снимка первой клиентской базы данных. Моментальный снимок — это специально подготовленная база данных SQL Server Compact, содержащая схему таблиц, данные (необязательно) и инфраструктуру отслеживания изменений. Скопируйте этот моментальный снимок на каждый клиент, где требуется снимок. В ходе первого сеанса синхронизации для клиента обновляются метаданные, относящиеся к клиенту, и все изменения, произошедшие с момента создания моментального снимка, загружаются в клиентскую базу данных.

Важное примечаниеВажно!

Моментальные снимки следует создавать только во время, когда в базе данных SQL Server Compact не выполняются никакие действия. Выполнение операций любого типа параллельно с созданием моментального снимка не поддерживается.

В следующем примере кода выполняется полная инициализация. Каждый раз, когда выполняется синхронизация, код проверяет, требуется ли схема для клиентской базы данных. Если поставщик располагает соединением с клиентской базой данных, которая уже инициализирована или является моментальным снимком, то метод ScopeExists возвращает значение true. Если этот метод возвращает значение false, то метод GetScopeDescription получает схему и применяет ее к клиентской базе данных.

private void CheckIfProviderNeedsSchema(SqlCeSyncProvider providerToCheck, DbSyncProvider providerWithSchema)
{

    //If one of the providers is a SqlCeSyncProvider and it needs
    //to be initialized, retrieve the schema from the other provider
    //if that provider is a DbSyncProvider; otherwise configure a
    //DbSyncProvider, connect to the server, and retrieve the schema.
    if (providerToCheck != null)
    {
        SqlCeConnection ceConn = (SqlCeConnection)providerToCheck.Connection;
        SqlCeSyncScopeProvisioning ceConfig = new SqlCeSyncScopeProvisioning(ceConn);
        string scopeName = providerToCheck.ScopeName;
        if (!ceConfig.ScopeExists(scopeName))
        {
            DbSyncScopeDescription scopeDesc = providerWithSchema.GetScopeDescription();
            ceConfig.PopulateFromScopeDescription(scopeDesc);
            ceConfig.Apply();
        }
    }

}
private void CheckIfProviderNeedsSchema(SqlCeSyncProvider providerToCheck, DbSyncProvider providerWithSchema)
{

    //If one of the providers is a SqlCeSyncProvider and it needs
    //to be initialized, retrieve the schema from the other provider
    //if that provider is a DbSyncProvider; otherwise configure a
    //DbSyncProvider, connect to the server, and retrieve the schema.
    if (providerToCheck != null)
    {
        SqlCeConnection ceConn = (SqlCeConnection)providerToCheck.Connection; 
        SqlCeSyncScopeProvisioning ceConfig = new SqlCeSyncScopeProvisioning(ceConn);
        string scopeName = providerToCheck.ScopeName;
        if (!ceConfig.ScopeExists(scopeName))
        {
            DbSyncScopeDescription scopeDesc = providerWithSchema.GetScopeDescription();
            ceConfig.PopulateFromScopeDescription(scopeDesc);
            ceConfig.Apply();
        }
    }

 }
    Private Sub CheckIfProviderNeedsSchema(ByVal providerToCheck As SqlCeSyncProvider, _
                                           ByVal providerWithSchema As DbSyncProvider)

        'If one of the providers is a SqlCeSyncProvider and it needs 
        'to be initialized, retrieve the schema from the other provider 
        'if that provider is a DbSyncProvider; otherwise configure a 
        'DbSyncProvider, connect to the server, and retrieve the schema. 
        If providerToCheck IsNot Nothing Then
            Dim ceConn As SqlCeConnection = DirectCast(providerToCheck.Connection, SqlCeConnection)
            Dim ceConfig As New SqlCeSyncScopeProvisioning(ceConn)
            Dim scopeName As String = providerToCheck.ScopeName
            If Not ceConfig.ScopeExists(scopeName) Then
                Dim scopeDesc As DbSyncScopeDescription = providerWithSchema.GetScopeDescription()
                ceConfig.PopulateFromScopeDescription(scopeDesc)
                ceConfig.Apply()
            End If

        End If

    End Sub
End Class
    Private Sub CheckIfProviderNeedsSchema(ByVal providerToCheck As SqlCeSyncProvider, _
                                           ByVal providerWithSchema As DbSyncProvider)

        'If one of the providers is a SqlCeSyncProvider and it needs 
        'to be initialized, retrieve the schema from the other provider 
        'if that provider is a DbSyncProvider; otherwise configure a 
        'DbSyncProvider, connect to the server, and retrieve the schema. 
        If providerToCheck IsNot Nothing Then
            Dim ceConn As SqlCeConnection = DirectCast(providerToCheck.Connection, SqlCeConnection)
            Dim ceConfig As New SqlCeSyncScopeProvisioning(ceConn)
            Dim scopeName As String = providerToCheck.ScopeName
            If Not ceConfig.ScopeExists(scopeName) Then
                Dim scopeDesc As DbSyncScopeDescription = providerWithSchema.GetScopeDescription()
                ceConfig.PopulateFromScopeDescription(scopeDesc)
                ceConfig.Apply()
            End If

        End If

    End Sub
End Class

В следующем примере кода создается моментальный снимок. В коде устанавливается соединение с базой данных SQL Server Compact, которая только что была синхронизирована с помощью метода ConfigureCESyncProvider из образца. После соединения вызывается метод GenerateSnapshot, который создает копию исходной базы данных. Когда поставщик клиента в следующий раз подключается к этой новой копии и выполняется синхронизация, база данных инициализируется.

//Second session: Synchronize two databases by using one instance of 
//DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
//database is initialized, it is copied by using GenerateSnapshot and then
//used for the third session.
sampleSyncAgent = new SampleSyncAgent(
                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
syncStatistics = sampleSyncAgent.Synchronize();
sampleStats.DisplayStats(syncStatistics, "initial");

//Copy the Compact database and save it as SyncSampleClient2.sdf.
SqlCeSyncStoreSnapshotInitialization snapshotInit = new SqlCeSyncStoreSnapshotInitialization();
snapshotInit.GenerateSnapshot(new SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf");

//Make a change that is synchronized during the third session.
Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer");

//Third session: Synchronize the new Compact database. The five rows  
//from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
//change is now downloaded to bring SyncSampleClient2.sdf up to date.
//SyncSampleClient2.sdf will get this row during the next round of
//synchronization sessions.
sampleSyncAgent = new SampleSyncAgent(
                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
syncStatistics = sampleSyncAgent.Synchronize();
sampleStats.DisplayStats(syncStatistics, "initial");
'Second session: Synchronize two databases by using one instance of 
'DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
'database is initialized, it is copied by using GenerateSnapshot and then 
'used for the third session. 
sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                      sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
syncStatistics = sampleSyncAgent.Synchronize()
sampleStats.DisplayStats(syncStatistics, "initial")

'Copy the Compact database and save it as SyncSampleClient2.sdf. 
Dim snapshotInit As New SqlCeSyncStoreSnapshotInitialization()
snapshotInit.GenerateSnapshot(New SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf")

'Make a change that is synchronized during the third session. 
Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer")

'Third session: Synchronize the new Compact database. The five rows 
'from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
'change is now downloaded to bring SyncSampleClient2.sdf up to date. 
'SyncSampleClient2.sdf will get this row during the next round of 
'synchronization sessions. 
sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                      sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
syncStatistics = sampleSyncAgent.Synchronize()
sampleStats.DisplayStats(syncStatistics, "initial")

Примечание

В этом примере базы данных моментальных снимков создается во время работы главного приложения. Во многих случаях моментальные снимки создаются в часы пониженной нагрузки, отдельно от других действий по синхронизации.

Создание модуля взаимодействия синхронизации и синхронизация баз данных

В коде, описанном в предыдущих подразделах, показано, как задать свойства, необходимые для синхронизации. Теперь нужно выполнить синхронизацию узлов. Узлы всегда синхронизируются парами, например SyncSamplesDb_Peer1 и SyncSampleCe2. В рабочей среде копия каждого приложения обычно развертывается на каждом узле, чтобы синхронизацию можно было запускать с любого узла.

В следующем примере кода создается класс SampleSyncAgent, производный от SyncOrchestrator. Конструктор SampleSyncAgent принимает два объекта RelationalSyncProvider. Поскольку классы DbSyncProvider и SqlCeSyncProvider являются производными от RelationalSyncProvider, объект SampleSyncAgent может синхронизировать любое сочетание этих двух типов поставщиков. Код указывает, какой поставщик является локальным, а какой — удаленным. Затем он указывает, что изменения следует сначала передать из удаленной базы данных в локальную, а затем в обратном направлении. Код проверяет, является ли один из поставщиков объектом SqlCeSyncProvider, для которого требуется инициализировать схему для клиентской базы данных.

public class SampleSyncAgent : SyncOrchestrator
{
    public SampleSyncAgent(RelationalSyncProvider localProvider, RelationalSyncProvider remoteProvider)
    {

        this.LocalProvider = localProvider;
        this.RemoteProvider = remoteProvider;
        this.Direction = SyncDirectionOrder.UploadAndDownload;

        //Check to see if any provider is a SqlCe provider and if it needs to
        //be initialized.
        CheckIfProviderNeedsSchema(localProvider as SqlCeSyncProvider, remoteProvider as DbSyncProvider);
        CheckIfProviderNeedsSchema(remoteProvider as SqlCeSyncProvider, localProvider as DbSyncProvider);

    }

    //For Compact databases that are not initialized with a snapshot,
    //get the schema and initial data from a server database.
    private void CheckIfProviderNeedsSchema(SqlCeSyncProvider providerToCheck, DbSyncProvider providerWithSchema)
    {

        //If one of the providers is a SqlCeSyncProvider and it needs
        //to be initialized, retrieve the schema from the other provider
        //if that provider is a DbSyncProvider; otherwise configure a
        //DbSyncProvider, connect to the server, and retrieve the schema.
        if (providerToCheck != null)
        {
            SqlCeConnection ceConn = (SqlCeConnection)providerToCheck.Connection; 
            SqlCeSyncScopeProvisioning ceConfig = new SqlCeSyncScopeProvisioning(ceConn);
            string scopeName = providerToCheck.ScopeName;
            if (!ceConfig.ScopeExists(scopeName))
            {
                DbSyncScopeDescription scopeDesc = providerWithSchema.GetScopeDescription();
                ceConfig.PopulateFromScopeDescription(scopeDesc);
                ceConfig.Apply();
            }
        }

     }
}
Public Class SampleSyncAgent
    Inherits SyncOrchestrator
    Public Sub New(ByVal localProvider As RelationalSyncProvider, ByVal remoteProvider As RelationalSyncProvider)

        Me.LocalProvider = localProvider
        Me.RemoteProvider = remoteProvider
        Me.Direction = SyncDirectionOrder.UploadAndDownload

        'Check to see if any provider is a SqlCe provider and if it needs to 
        'be initialized. 
        CheckIfProviderNeedsSchema(TryCast(localProvider, SqlCeSyncProvider), _
                                   TryCast(remoteProvider, DbSyncProvider))
        CheckIfProviderNeedsSchema(TryCast(remoteProvider, SqlCeSyncProvider), _
                                   TryCast(localProvider, DbSyncProvider))

    End Sub

    'For Compact databases that are not initialized with a snapshot, 
    'get the schema and initial data from a server database. 
    Private Sub CheckIfProviderNeedsSchema(ByVal providerToCheck As SqlCeSyncProvider, _
                                           ByVal providerWithSchema As DbSyncProvider)

        'If one of the providers is a SqlCeSyncProvider and it needs 
        'to be initialized, retrieve the schema from the other provider 
        'if that provider is a DbSyncProvider; otherwise configure a 
        'DbSyncProvider, connect to the server, and retrieve the schema. 
        If providerToCheck IsNot Nothing Then
            Dim ceConn As SqlCeConnection = DirectCast(providerToCheck.Connection, SqlCeConnection)
            Dim ceConfig As New SqlCeSyncScopeProvisioning(ceConn)
            Dim scopeName As String = providerToCheck.ScopeName
            If Not ceConfig.ScopeExists(scopeName) Then
                Dim scopeDesc As DbSyncScopeDescription = providerWithSchema.GetScopeDescription()
                ceConfig.PopulateFromScopeDescription(scopeDesc)
                ceConfig.Apply()
            End If

        End If

    End Sub
End Class

В следующем коде настраиваются сеансы синхронизации путем вызова метода ConfigureDbSyncProvider или ConfigureCeSyncProvider, которые используются в примере приложения для задания необходимых свойств поставщика в каждом сеансе. Затем вызывается метод Synchronize агента SampleSyncAgent для синхронизации каждой пары баз данных.

//The SampleStats class handles information from the SyncStatistics
//object that the Synchronize method returns.
SampleStats sampleStats = new SampleStats();

try
{
    //Initial synchronization. Instantiate the SyncOrchestrator
    //and call Synchronize.
    SampleSyncProvider sampleSyncProvider = new SampleSyncProvider();
    SyncOrchestrator sampleSyncAgent;
    SyncOperationStatistics syncStatistics;

    //First session: Synchronize two databases by using two instances 
    //of DbSyncProvider.
    sampleSyncAgent = new SampleSyncAgent(
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2));
    syncStatistics = sampleSyncAgent.Synchronize();
    sampleStats.DisplayStats(syncStatistics, "initial");

    //Second session: Synchronize two databases by using one instance of 
    //DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
    //database is initialized, it is copied by using GenerateSnapshot and then
    //used for the third session.
    sampleSyncAgent = new SampleSyncAgent(
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                            sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
    syncStatistics = sampleSyncAgent.Synchronize();
    sampleStats.DisplayStats(syncStatistics, "initial");

    //Copy the Compact database and save it as SyncSampleClient2.sdf.
    SqlCeSyncStoreSnapshotInitialization snapshotInit = new SqlCeSyncStoreSnapshotInitialization();
    snapshotInit.GenerateSnapshot(new SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf");

    //Make a change that is synchronized during the third session.
    Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer");

    //Third session: Synchronize the new Compact database. The five rows  
    //from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
    //change is now downloaded to bring SyncSampleClient2.sdf up to date.
    //SyncSampleClient2.sdf will get this row during the next round of
    //synchronization sessions.
    sampleSyncAgent = new SampleSyncAgent(
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                            sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
    syncStatistics = sampleSyncAgent.Synchronize();
    sampleStats.DisplayStats(syncStatistics, "initial");
}


catch (DbOutdatedSyncException ex)
{
    Console.WriteLine("Outdated Knowledge: " + ex.OutdatedPeerSyncKnowledge.ToString() +
                      " Clean up knowledge: " + ex.MissingCleanupKnowledge.ToString());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
'The SampleStats class handles information from the SyncStatistics 
'object that the Synchronize method returns. 
Dim sampleStats As New SampleStats()

Try
    'Initial synchronization. Instantiate the SyncOrchestrator 
    'and call Synchronize. 
    Dim sampleSyncProvider As New SampleSyncProvider()
    Dim sampleSyncAgent As SyncOrchestrator
    Dim syncStatistics As SyncOperationStatistics

    'First session: Synchronize two databases by using two instances 
    'of DbSyncProvider. 
    sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                          sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2))
    syncStatistics = sampleSyncAgent.Synchronize()
    sampleStats.DisplayStats(syncStatistics, "initial")

    'Second session: Synchronize two databases by using one instance of 
    'DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
    'database is initialized, it is copied by using GenerateSnapshot and then 
    'used for the third session. 
    sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                          sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
    syncStatistics = sampleSyncAgent.Synchronize()
    sampleStats.DisplayStats(syncStatistics, "initial")

    'Copy the Compact database and save it as SyncSampleClient2.sdf. 
    Dim snapshotInit As New SqlCeSyncStoreSnapshotInitialization()
    snapshotInit.GenerateSnapshot(New SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf")

    'Make a change that is synchronized during the third session. 
    Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer")

    'Third session: Synchronize the new Compact database. The five rows 
    'from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
    'change is now downloaded to bring SyncSampleClient2.sdf up to date. 
    'SyncSampleClient2.sdf will get this row during the next round of 
    'synchronization sessions. 
    sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                          sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
    syncStatistics = sampleSyncAgent.Synchronize()
    sampleStats.DisplayStats(syncStatistics, "initial")
Catch ex As DbOutdatedSyncException


    Console.WriteLine(("Outdated Knowledge: " & ex.OutdatedPeerSyncKnowledge.ToString() & " Clean up knowledge: ") _
                      & ex.MissingCleanupKnowledge.ToString())
Catch ex As Exception
    Console.WriteLine(ex.Message)
End Try

Полный пример кода

В следующий полный пример кода входят примеры, описанные ранее, а также дополнительный код, который выводит статистику синхронизации и сведения о событиях. Для работы примеру необходим класс Utility, который можно найти в разделе Инструкции по классу Utility для поставщика базы данных.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.SqlServerCe;

namespace Microsoft.Samples.Synchronization
{
    class Program
    {
        static void Main(string[] args)
        {

            //The Utility class handles all functionality that is not
            //directly related to synchronization, such as holding peerConnection 
            //string information and making changes to the server database.
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, true);
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync2, false);

            //The SampleStats class handles information from the SyncStatistics
            //object that the Synchronize method returns.
            SampleStats sampleStats = new SampleStats();

            try
            {
                //Initial synchronization. Instantiate the SyncOrchestrator
                //and call Synchronize.
                SampleSyncProvider sampleSyncProvider = new SampleSyncProvider();
                SyncOrchestrator sampleSyncAgent;
                SyncOperationStatistics syncStatistics;

                //First session: Synchronize two databases by using two instances 
                //of DbSyncProvider.
                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "initial");

                //Second session: Synchronize two databases by using one instance of 
                //DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
                //database is initialized, it is copied by using GenerateSnapshot and then
                //used for the third session.
                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "initial");

                //Copy the Compact database and save it as SyncSampleClient2.sdf.
                SqlCeSyncStoreSnapshotInitialization snapshotInit = new SqlCeSyncStoreSnapshotInitialization();
                snapshotInit.GenerateSnapshot(new SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf");

                //Make a change that is synchronized during the third session.
                Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer");

                //Third session: Synchronize the new Compact database. The five rows  
                //from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
                //change is now downloaded to bring SyncSampleClient2.sdf up to date.
                //SyncSampleClient2.sdf will get this row during the next round of
                //synchronization sessions.
                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "initial");
            }


            catch (DbOutdatedSyncException ex)
            {
                Console.WriteLine("Outdated Knowledge: " + ex.OutdatedPeerSyncKnowledge.ToString() +
                                  " Clean up knowledge: " + ex.MissingCleanupKnowledge.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            //Make a change in one of the databases.
            Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync2, "Customer");

            try
            {
                //Subsequent synchronization. Changes are now synchronized between all
                //nodes.
                SampleSyncProvider sampleSyncProvider = new SampleSyncProvider();
                SyncOrchestrator sampleSyncAgent;
                SyncOperationStatistics syncStatistics;

                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "subsequent");

                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "subsequent");

                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "subsequent");

            }


            catch (DbOutdatedSyncException ex)
            {
                Console.WriteLine("Outdated Knowledge: " + ex.OutdatedPeerSyncKnowledge.ToString() +
                                  " Clean up knowledge: " + ex.MissingCleanupKnowledge.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            //Return data back to its original state.
            Utility.CleanUpNode(Utility.ConnStr_DbSync1);
            Utility.CleanUpNode(Utility.ConnStr_DbSync2);

            //Exit.
            Console.Write("\nPress Enter to close the window.");
            Console.ReadLine();
        }

        //Create a class that is derived from 
        //Microsoft.Synchronization.SyncOrchestrator.
        public class SampleSyncAgent : SyncOrchestrator
        {
            public SampleSyncAgent(RelationalSyncProvider localProvider, RelationalSyncProvider remoteProvider)
            {

                this.LocalProvider = localProvider;
                this.RemoteProvider = remoteProvider;
                this.Direction = SyncDirectionOrder.UploadAndDownload;

                //Check to see if any provider is a SqlCe provider and if it needs to
                //be initialized.
                CheckIfProviderNeedsSchema(localProvider as SqlCeSyncProvider, remoteProvider as DbSyncProvider);
                CheckIfProviderNeedsSchema(remoteProvider as SqlCeSyncProvider, localProvider as DbSyncProvider);

            }

            //For Compact databases that are not initialized with a snapshot,
            //get the schema and initial data from a server database.
            private void CheckIfProviderNeedsSchema(SqlCeSyncProvider providerToCheck, DbSyncProvider providerWithSchema)
            {

                //If one of the providers is a SqlCeSyncProvider and it needs
                //to be initialized, retrieve the schema from the other provider
                //if that provider is a DbSyncProvider; otherwise configure a
                //DbSyncProvider, connect to the server, and retrieve the schema.
                if (providerToCheck != null)
                {
                    SqlCeConnection ceConn = (SqlCeConnection)providerToCheck.Connection; 
                    SqlCeSyncScopeProvisioning ceConfig = new SqlCeSyncScopeProvisioning(ceConn);
                    string scopeName = providerToCheck.ScopeName;
                    if (!ceConfig.ScopeExists(scopeName))
                    {
                        DbSyncScopeDescription scopeDesc = providerWithSchema.GetScopeDescription();
                        ceConfig.PopulateFromScopeDescription(scopeDesc);
                        ceConfig.Apply();
                    }
                }

             }
        }


        public class SampleSyncProvider
        {

            public SqlCeSyncProvider ConfigureCeSyncProvider(string sqlCeConnString)
            {

                SqlCeSyncProvider sampleCeProvider = new SqlCeSyncProvider();

                //Set the scope name
                sampleCeProvider.ScopeName = "Sales";

                //Set the connection
                sampleCeProvider.Connection = new SqlCeConnection(sqlCeConnString);

                //Register event handlers

                //Register the BeginSnapshotInitialization event handler. 
                //It is called when snapshot initialization is about to begin
                //for a particular scope in a Compact database.
                sampleCeProvider.BeginSnapshotInitialization += new EventHandler<DbBeginSnapshotInitializationEventArgs>(sampleCeProvider_BeginSnapshotInitialization);

                //Register the EndSnapshotInitialization event handler. 
                //It is called when snapshot initialization has completed
                //for a particular scope in a Compact database.
                sampleCeProvider.EndSnapshotInitialization += new EventHandler<DbEndSnapshotInitializationEventArgs>(sampleCeProvider_EndSnapshotInitialization);

                return sampleCeProvider;
            }

            public void sampleCeProvider_CreatingSchema(object sender, CreatingSchemaEventArgs e)
            {
                Console.WriteLine("Full Initialization Process Started.....");
                Console.WriteLine(
                    string.Format("CreatingSchame Event fired for Database {0}", e.Connection.Database)
                    );
            }

            public void sampleCeProvider_BeginSnapshotInitialization(object sender, DbBeginSnapshotInitializationEventArgs e)
            {
                Console.WriteLine("");
                Console.WriteLine("Snapshot initialization process started");
                Console.WriteLine(
                    string.Format("BeginSnapshotInitialization event fired for scope {0}", e.ScopeName)
                    );
            }

            public void sampleCeProvider_EndSnapshotInitialization(object sender, DbEndSnapshotInitializationEventArgs e)
            {
                Console.WriteLine("EndSnapshotInitialization event fired");

                Dictionary<string, DbSnapshotInitializationTableStatistics> tableStats = 
                    e.TableInitializationStatistics;

                foreach (string tableName in tableStats.Keys)
                {

                    DbSnapshotInitializationTableStatistics ts = tableStats[tableName];

                    Console.WriteLine("\tTable Name: " + tableName);

                    Console.WriteLine("\tTotal Rows: " + ts.TotalRows);

                    Console.WriteLine("\tRows Intialized: " + ts.RowsInitialized);

                    Console.WriteLine("\tStart Time: " + ts.StartTime);

                    Console.WriteLine("\tEnd Time: " + ts.EndTime);

                }

                Console.WriteLine("Snapshot initialization process completed");
                Console.WriteLine("");
            }

            public DbSyncProvider ConfigureDbSyncProvider(string peerConnString)
            {

                DbSyncProvider sampleDbProvider = new DbSyncProvider();

                SqlConnection peerConnection = new SqlConnection(peerConnString);
                sampleDbProvider.Connection = peerConnection;
                sampleDbProvider.ScopeName = "Sales";

                //Create a DbSyncAdapter object for the Customer table and associate it 
                //with the DbSyncProvider. Following the DataAdapter style in ADO.NET, 
                //DbSyncAdapter is the equivalent for synchronization. The commands that 
                //are specified for the DbSyncAdapter object call stored procedures
                //that are created in each peer database.
                DbSyncAdapter adapterCustomer = new DbSyncAdapter("Customer");


                //Specify the primary key, which Sync Framework uses
                //to identify each row during synchronization.
                adapterCustomer.RowIdColumns.Add("CustomerId");


                //Specify the command to select incremental changes.
                //In this command and other commands, session variables are
                //used to pass information at runtime. DbSyncSession.SyncMetadataOnly 
                //and SyncMinTimestamp are two of the string constants that
                //the DbSyncSession class exposes. You could also include 
                //@sync_metadata_only and @sync_min_timestamp directly in your 
                //queries:
                //*  sync_metadata_only is used by Sync Framework as an optimization
                //   in some queries.
                //* The value of the sync_min_timestamp session variable is compared to
                //   values in the sync_row_timestamp column in the tracking table to 
                //   determine which rows to select.
                SqlCommand chgsCustomerCmd = new SqlCommand();
                chgsCustomerCmd.CommandType = CommandType.StoredProcedure;
                chgsCustomerCmd.CommandText = "Sync.sp_Customer_SelectChanges";
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMetadataOnly, SqlDbType.Int);
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncInitialize, SqlDbType.Int);

                adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd;

                //Specify the command to insert rows.
                //The sync_row_count session variable is used in this command 
                //and other commands to return a count of the rows affected by an operation. 
                //A count of 0 indicates that an operation failed.
                SqlCommand insCustomerCmd = new SqlCommand();
                insCustomerCmd.CommandType = CommandType.StoredProcedure;
                insCustomerCmd.CommandText = "Sync.sp_Customer_ApplyInsert";
                insCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                insCustomerCmd.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
                insCustomerCmd.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
                insCustomerCmd.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
                insCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.InsertCommand = insCustomerCmd;


                //Specify the command to update rows.
                //The value of the sync_min_timestamp session variable is compared to
                //values in the sync_row_timestamp column in the tracking table to 
                //determine which rows to update.
                SqlCommand updCustomerCmd = new SqlCommand();
                updCustomerCmd.CommandType = CommandType.StoredProcedure;
                updCustomerCmd.CommandText = "Sync.sp_Customer_ApplyUpdate";
                updCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                updCustomerCmd.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
                updCustomerCmd.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
                updCustomerCmd.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
                updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
                updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;
                updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncForceWrite, SqlDbType.Int);

                adapterCustomer.UpdateCommand = updCustomerCmd;


                //Specify the command to delete rows.
                //The value of the sync_min_timestamp session variable is compared to
                //values in the sync_row_timestamp column in the tracking table to 
                //determine which rows to delete.
                SqlCommand delCustomerCmd = new SqlCommand();
                delCustomerCmd.CommandType = CommandType.StoredProcedure;
                delCustomerCmd.CommandText = "Sync.sp_Customer_ApplyDelete";
                delCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                delCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
                delCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;
                delCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncForceWrite, SqlDbType.Int);

                adapterCustomer.DeleteCommand = delCustomerCmd;

                //Specify the command to select any conflicting rows.
                SqlCommand selRowCustomerCmd = new SqlCommand();
                selRowCustomerCmd.CommandType = CommandType.StoredProcedure;
                selRowCustomerCmd.CommandText = "Sync.sp_Customer_SelectRow";
                selRowCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                selRowCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);

                adapterCustomer.SelectRowCommand = selRowCustomerCmd;


                //Specify the command to insert metadata rows.
                //The session variables in this command relate to columns in
                //the tracking table.
                SqlCommand insMetadataCustomerCmd = new SqlCommand();
                insMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
                insMetadataCustomerCmd.CommandText = "Sync.sp_Customer_InsertMetadata";
                insMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);          
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerKey, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowIsTombstone, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.InsertMetadataCommand = insMetadataCustomerCmd;


                //Specify the command to update metadata rows.
                SqlCommand updMetadataCustomerCmd = new SqlCommand();
                updMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
                updMetadataCustomerCmd.CommandText = "Sync.sp_Customer_UpdateMetadata";
                updMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerKey, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowIsTombstone, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd;

                //Specify the command to delete metadata rows.
                SqlCommand delMetadataCustomerCmd = new SqlCommand();
                delMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
                delMetadataCustomerCmd.CommandText = "Sync.sp_Customer_DeleteMetadata";
                delMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                delMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                delMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
                delMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.DeleteMetadataCommand = delMetadataCustomerCmd;


                //Add the adapter to the provider.
                sampleDbProvider.SyncAdapters.Add(adapterCustomer);


                // Configure commands that relate to the provider itself rather 
                // than the DbSyncAdapter object for each table:
                // * SelectNewTimestampCommand: Returns the new high watermark for 
                //   the current synchronization session.
                // * SelectScopeInfoCommand: Returns sync knowledge, cleanup knowledge, 
                //   and a scope version (timestamp).
                // * UpdateScopeInfoCommand: Sets new values for sync knowledge and cleanup knowledge.            
                // * SelectTableMaxTimestampsCommand (optional): Returns the maximum timestamp from each base table 
                //   or tracking table, to determine whether for each table the destination already 
                //   has all of the changes from the source. If a destination table has all the changes,
                //   SelectIncrementalChangesCommand is not called for that table.
                // There are additional commands related to metadata cleanup that are not 
                // included in this application.


                //Select a new timestamp.
                //During each synchronization, the new value and
                //the last value from the previous synchronization
                //are used: the set of changes between these upper and
                //lower bounds is synchronized.
                SqlCommand selectNewTimestampCommand = new SqlCommand();
                string newTimestampVariable = "@" + DbSyncSession.SyncNewTimestamp;
                selectNewTimestampCommand.CommandText = "SELECT " + newTimestampVariable + " = min_active_rowversion() - 1";
                selectNewTimestampCommand.Parameters.Add(newTimestampVariable, SqlDbType.Timestamp);
                selectNewTimestampCommand.Parameters[newTimestampVariable].Direction = ParameterDirection.Output;

                sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand;

                //Specify the command to select local replica metadata.
                SqlCommand selReplicaInfoCmd = new SqlCommand();
                selReplicaInfoCmd.CommandType = CommandType.Text;
                selReplicaInfoCmd.CommandText = "SELECT " +
                                                "scope_id, " +
                                                "scope_local_id, " +
                                                "scope_sync_knowledge, " +
                                                "scope_tombstone_cleanup_knowledge, " +
                                                "scope_timestamp " +
                                                "FROM Sync.ScopeInfo " +
                                                "WHERE scope_name = @" + DbSyncSession.SyncScopeName;
                selReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100);

                sampleDbProvider.SelectScopeInfoCommand = selReplicaInfoCmd;


                //Specify the command to update local replica metadata. 
                SqlCommand updReplicaInfoCmd = new SqlCommand();
                updReplicaInfoCmd.CommandType = CommandType.Text;
                updReplicaInfoCmd.CommandText = "UPDATE  Sync.ScopeInfo SET " +
                                                "scope_sync_knowledge = @" + DbSyncSession.SyncScopeKnowledge + ", " +
                                                "scope_id = @" + DbSyncSession.SyncScopeId + ", " +
                                                "scope_tombstone_cleanup_knowledge = @" + DbSyncSession.SyncScopeCleanupKnowledge + " " +
                                                "WHERE scope_name = @" + DbSyncSession.SyncScopeName + " AND " +
                                                " ( @" + DbSyncSession.SyncCheckConcurrency + " = 0 OR scope_timestamp = @" + DbSyncSession.SyncScopeTimestamp + "); " +
                                                "SET @" + DbSyncSession.SyncRowCount + " = @@rowcount";
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeKnowledge, SqlDbType.VarBinary, 10000);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeCleanupKnowledge, SqlDbType.VarBinary, 10000);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeId, SqlDbType.UniqueIdentifier);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeTimestamp, SqlDbType.BigInt);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                sampleDbProvider.UpdateScopeInfoCommand = updReplicaInfoCmd;


                //Return the maximum timestamp from the Customer_Tracking table.
                //If more tables are synchronized, the query should UNION
                //all of the results. The table name is not schema-qualified
                //in this case because the name was not schema qualified in the
                //DbSyncAdapter constructor.
                SqlCommand selTableMaxTsCmd = new SqlCommand();
                selTableMaxTsCmd.CommandType = CommandType.Text;
                selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " +
                                               "MAX(local_update_peer_timestamp) AS max_timestamp " +
                                               "FROM Sync.Customer_Tracking";
                sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd;

                return sampleDbProvider;
            }
        }

        //Handle the statistics that are returned by the SyncAgent.
        public class SampleStats
        {
            public void DisplayStats(SyncOperationStatistics syncStatistics, string syncType)
            {
                Console.WriteLine(String.Empty);
                if (syncType == "initial")
                {
                    Console.WriteLine("****** Initial Synchronization ******");
                }
                else if (syncType == "subsequent")
                {
                    Console.WriteLine("***** Subsequent Synchronization ****");
                }

                Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
                Console.WriteLine("Total Changes Uploaded: " + syncStatistics.UploadChangesTotal);
                Console.WriteLine("Total Changes Downloaded: " + syncStatistics.DownloadChangesTotal);
                Console.WriteLine("Complete Time: " + syncStatistics.SyncEndTime);
                Console.WriteLine(String.Empty);
            }
        }
    }
}
Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Text
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlServerCe
Imports Microsoft.Synchronization
Imports Microsoft.Synchronization.Data
Imports Microsoft.Synchronization.Data.SqlServerCe

Class Program

    Shared Sub Main(ByVal args As String())

        'The Utility class handles all functionality that is not 
        'directly related to synchronization, such as holding connection 
        'string information and making changes to the server database.
        Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, True)
        Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync2, False)

        'The SampleStats class handles information from the SyncStatistics 
        'object that the Synchronize method returns. 
        Dim sampleStats As New SampleStats()

        Try
            'Initial synchronization. Instantiate the SyncOrchestrator 
            'and call Synchronize. 
            Dim sampleSyncProvider As New SampleSyncProvider()
            Dim sampleSyncAgent As SyncOrchestrator
            Dim syncStatistics As SyncOperationStatistics

            'First session: Synchronize two databases by using two instances 
            'of DbSyncProvider. 
            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "initial")

            'Second session: Synchronize two databases by using one instance of 
            'DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
            'database is initialized, it is copied by using GenerateSnapshot and then 
            'used for the third session. 
            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "initial")

            'Copy the Compact database and save it as SyncSampleClient2.sdf. 
            Dim snapshotInit As New SqlCeSyncStoreSnapshotInitialization()
            snapshotInit.GenerateSnapshot(New SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf")

            'Make a change that is synchronized during the third session. 
            Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer")

            'Third session: Synchronize the new Compact database. The five rows 
            'from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
            'change is now downloaded to bring SyncSampleClient2.sdf up to date. 
            'SyncSampleClient2.sdf will get this row during the next round of 
            'synchronization sessions. 
            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "initial")
        Catch ex As DbOutdatedSyncException


            Console.WriteLine(("Outdated Knowledge: " & ex.OutdatedPeerSyncKnowledge.ToString() & " Clean up knowledge: ") _
                              & ex.MissingCleanupKnowledge.ToString())
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try

        'Make a change in one of the databases. 
        Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync2, "Customer")

        Try
            'Subsequent synchronization. Changes are now synchronized between all 
            'nodes. 
            Dim sampleSyncProvider As New SampleSyncProvider()
            Dim sampleSyncAgent As SyncOrchestrator
            Dim syncStatistics As SyncOperationStatistics

            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "subsequent")

            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "subsequent")

            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "subsequent")

        Catch ex As DbOutdatedSyncException


            Console.WriteLine(("Outdated Knowledge: " & ex.OutdatedPeerSyncKnowledge.ToString() & " Clean up knowledge: ") _
                              & ex.MissingCleanupKnowledge.ToString())
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try

        'Return data back to its original state. 
        Utility.CleanUpNode(Utility.ConnStr_DbSync1)
        Utility.CleanUpNode(Utility.ConnStr_DbSync2)

        'Exit. 
        Console.Write(vbLf & "Press Enter to close the window.")
        Console.ReadLine()
    End Sub

    'Create a class that is derived from 
    'Microsoft.Synchronization.SyncOrchestrator. 
    Public Class SampleSyncAgent
        Inherits SyncOrchestrator
        Public Sub New(ByVal localProvider As RelationalSyncProvider, ByVal remoteProvider As RelationalSyncProvider)

            Me.LocalProvider = localProvider
            Me.RemoteProvider = remoteProvider
            Me.Direction = SyncDirectionOrder.UploadAndDownload

            'Check to see if any provider is a SqlCe provider and if it needs to 
            'be initialized. 
            CheckIfProviderNeedsSchema(TryCast(localProvider, SqlCeSyncProvider), _
                                       TryCast(remoteProvider, DbSyncProvider))
            CheckIfProviderNeedsSchema(TryCast(remoteProvider, SqlCeSyncProvider), _
                                       TryCast(localProvider, DbSyncProvider))

        End Sub

        'For Compact databases that are not initialized with a snapshot, 
        'get the schema and initial data from a server database. 
        Private Sub CheckIfProviderNeedsSchema(ByVal providerToCheck As SqlCeSyncProvider, _
                                               ByVal providerWithSchema As DbSyncProvider)

            'If one of the providers is a SqlCeSyncProvider and it needs 
            'to be initialized, retrieve the schema from the other provider 
            'if that provider is a DbSyncProvider; otherwise configure a 
            'DbSyncProvider, connect to the server, and retrieve the schema. 
            If providerToCheck IsNot Nothing Then
                Dim ceConn As SqlCeConnection = DirectCast(providerToCheck.Connection, SqlCeConnection)
                Dim ceConfig As New SqlCeSyncScopeProvisioning(ceConn)
                Dim scopeName As String = providerToCheck.ScopeName
                If Not ceConfig.ScopeExists(scopeName) Then
                    Dim scopeDesc As DbSyncScopeDescription = providerWithSchema.GetScopeDescription()
                    ceConfig.PopulateFromScopeDescription(scopeDesc)
                    ceConfig.Apply()
                End If

            End If

        End Sub
    End Class


    Public Class SampleSyncProvider

        Public Function ConfigureCeSyncProvider(ByVal sqlCeConnString As String) As SqlCeSyncProvider

            Dim sampleCeProvider As New SqlCeSyncProvider()

            'Set the scope name 
            sampleCeProvider.ScopeName = "Sales"

            'Set the connection 
            sampleCeProvider.Connection = New SqlCeConnection(sqlCeConnString)

            'Register event handlers 

            'Register the BeginSnapshotInitialization event handler. 
            'It is called when snapshot initialization is about to begin 
            'for a particular scope in a Compact database. 
            AddHandler sampleCeProvider.BeginSnapshotInitialization, AddressOf sampleCeProvider_BeginSnapshotInitialization

            'Register the EndSnapshotInitialization event handler. 
            'It is called when snapshot initialization has completed 
            'for a particular scope in a Compact database. 
            AddHandler sampleCeProvider.EndSnapshotInitialization, AddressOf sampleCeProvider_EndSnapshotInitialization

            Return sampleCeProvider
        End Function

        Public Sub sampleCeProvider_CreatingSchema(ByVal sender As Object, ByVal e As CreatingSchemaEventArgs)
            Console.WriteLine("Full initialization process started...")
            Console.WriteLine(String.Format("CreatingSchema event fired for database {0}", e.Connection.Database))
        End Sub

        Public Sub sampleCeProvider_BeginSnapshotInitialization(ByVal sender As Object, ByVal e As DbBeginSnapshotInitializationEventArgs)
            Console.WriteLine("")
            Console.WriteLine("Snapshot initialization process started...")
            Console.WriteLine(String.Format("BeginSnapshotInitialization event fired for scope {0}", e.ScopeName))
        End Sub

        Public Sub sampleCeProvider_EndSnapshotInitialization(ByVal sender As Object, ByVal e As DbEndSnapshotInitializationEventArgs)
            Console.WriteLine("EndSnapshotInitialization event fired")

            Dim tableStats As Dictionary(Of String, DbSnapshotInitializationTableStatistics) = e.TableInitializationStatistics

            For Each tableName As String In tableStats.Keys

                Dim ts As DbSnapshotInitializationTableStatistics = tableStats(tableName)

                Console.WriteLine(vbTab & "Table Name: " & tableName)

                Console.WriteLine(vbTab & "Total Rows: " & ts.TotalRows)

                Console.WriteLine(vbTab & "Rows Intialized: " & ts.RowsInitialized)

                Console.WriteLine(vbTab & "Start Time: " & ts.StartTime)

                Console.WriteLine(vbTab & "End Time: " & ts.EndTime)

            Next

            Console.WriteLine("Snapshot initialization process completed")
            Console.WriteLine("")
        End Sub

        Public Function ConfigureDbSyncProvider(ByVal peerConnString As String) As DbSyncProvider

            Dim sampleDbProvider As New DbSyncProvider()

            Dim peerConnection As New SqlConnection(peerConnString)
            sampleDbProvider.Connection = peerConnection
            sampleDbProvider.ScopeName = "Sales"

            'Create a DbSyncAdapter object for the Customer table and associate it 
            'with the DbSyncProvider. Following the DataAdapter style in ADO.NET, 
            'DbSyncAdapter is the equivalent for synchronization. The commands that 
            'are specified for the DbSyncAdapter object call stored procedures 
            'that are created in each peer database. 
            Dim adapterCustomer As New DbSyncAdapter("Customer")


            'Specify the primary key, which Sync Framework uses 
            'to identify each row during synchronization. 
            adapterCustomer.RowIdColumns.Add("CustomerId")


            'Specify the command to select incremental changes. 
            'In this command and other commands, session variables are 
            'used to pass information at runtime. DbSyncSession.SyncMetadataOnly 
            'and SyncMinTimestamp are two of the string constants that 
            'the DbSyncSession class exposes. You could also include 
            '@sync_metadata_only and @sync_min_timestamp directly in your 
            'queries: 
            '* sync_metadata_only is used by Sync Framework as an optimization 
            ' in some queries. 
            '* The value of the sync_min_timestamp session variable is compared to 
            ' values in the sync_row_timestamp column in the tracking table to 
            ' determine which rows to select. 
            Dim chgsCustomerCmd As New SqlCommand()
            With chgsCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_SelectChanges"
                .Parameters.Add("@" & DbSyncSession.SyncMetadataOnly, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncInitialize, SqlDbType.Int)
            End With

            adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd

            'Specify the command to insert rows. 
            'The sync_row_count session variable is used in this command 
            'and other commands to return a count of the rows affected by an operation. 
            'A count of 0 indicates that an operation failed. 
            Dim insCustomerCmd As New SqlCommand()
            With insCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_ApplyInsert"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
                .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
                .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.InsertCommand = insCustomerCmd


            'Specify the command to update rows. 
            'The value of the sync_min_timestamp session variable is compared to 
            'values in the sync_row_timestamp column in the tracking table to 
            'determine which rows to update. 
            Dim updCustomerCmd As New SqlCommand()
            With updCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_ApplyUpdate"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
                .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
                .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
                .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
                .Parameters.Add("@" & DbSyncSession.SyncForceWrite, SqlDbType.Int)
            End With

            adapterCustomer.UpdateCommand = updCustomerCmd


            'Specify the command to delete rows. 
            'The value of the sync_min_timestamp session variable is compared to 
            'values in the sync_row_timestamp column in the tracking table to 
            'determine which rows to delete. 
            Dim delCustomerCmd As New SqlCommand()
            With delCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_ApplyDelete"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
                .Parameters.Add("@" & DbSyncSession.SyncForceWrite, SqlDbType.Int)
            End With

            adapterCustomer.DeleteCommand = delCustomerCmd

            'Specify the command to select any conflicting rows. 
            Dim selRowCustomerCmd As New SqlCommand()
            With selRowCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_SelectRow"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
            End With

            adapterCustomer.SelectRowCommand = selRowCustomerCmd


            'Specify the command to insert metadata rows. 
            'The session variables in this command relate to columns in 
            'the tracking table. 
            Dim insMetadataCustomerCmd As New SqlCommand()
            With insMetadataCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_InsertMetadata"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowIsTombstone, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.InsertMetadataCommand = insMetadataCustomerCmd


            'Specify the command to update metadata rows. 
            Dim updMetadataCustomerCmd As New SqlCommand()
            With updMetadataCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_UpdateMetadata"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowIsTombstone, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd

            'Specify the command to delete metadata rows. 
            Dim delMetadataCustomerCmd As New SqlCommand()
            With delMetadataCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_DeleteMetadata"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.DeleteMetadataCommand = delMetadataCustomerCmd


            'Add the adapter to the provider. 
            sampleDbProvider.SyncAdapters.Add(adapterCustomer)


            ' Configure commands that relate to the provider itself rather 
            ' than the DbSyncAdapter object for each table: 
            ' * SelectNewTimestampCommand: Returns the new high watermark for 
            ' the current synchronization session. 
            ' * SelectScopeInfoCommand: Returns sync knowledge, cleanup knowledge, 
            ' and a scope version (timestamp). 
            ' * UpdateScopeInfoCommand: Sets new values for sync knowledge and cleanup knowledge. 
            ' * SelectTableMaxTimestampsCommand (optional): Returns the maximum timestamp from each base table 
            ' or tracking table, to determine whether for each table the destination already 
            ' has all of the changes from the source. If a destination table has all the changes, 
            ' SelectIncrementalChangesCommand is not called for that table. 
            ' There are additional commands related to metadata cleanup that are not 
            ' included in this application. 


            'Select a new timestamp. 
            'During each synchronization, the new value and 
            'the last value from the previous synchronization 
            'are used: the set of changes between these upper and 
            'lower bounds is synchronized. 
            Dim selectNewTimestampCommand As New SqlCommand()
            Dim newTimestampVariable As String = "@" & DbSyncSession.SyncNewTimestamp
            With selectNewTimestampCommand
                .CommandText = "SELECT " & newTimestampVariable & " = min_active_rowversion() - 1"
                .Parameters.Add(newTimestampVariable, SqlDbType.Timestamp)
                .Parameters(newTimestampVariable).Direction = ParameterDirection.Output
            End With

            sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand

            'Specify the command to select local replica metadata. 
            Dim selReplicaInfoCmd As New SqlCommand()
            With selReplicaInfoCmd
                .CommandType = CommandType.Text
                .CommandText = "SELECT " _
                             & "scope_id, " _
                             & "scope_local_id, " _
                             & "scope_sync_knowledge, " _
                             & "scope_tombstone_cleanup_knowledge, " _
                             & "scope_timestamp " _
                             & "FROM Sync.ScopeInfo " _
                             & "WHERE scope_name = @" + DbSyncSession.SyncScopeName
                .Parameters.Add("@" & DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100)
            End With

            sampleDbProvider.SelectScopeInfoCommand = selReplicaInfoCmd


            'Specify the command to update local replica metadata. 
            Dim updReplicaInfoCmd As New SqlCommand()
            With updReplicaInfoCmd
                .CommandType = CommandType.Text
                .CommandText = "UPDATE  Sync.ScopeInfo SET " _
                             & "scope_sync_knowledge = @" + DbSyncSession.SyncScopeKnowledge + ", " _
                             & "scope_id = @" + DbSyncSession.SyncScopeId + ", " _
                             & "scope_tombstone_cleanup_knowledge = @" + DbSyncSession.SyncScopeCleanupKnowledge + " " _
                             & "WHERE scope_name = @" + DbSyncSession.SyncScopeName + " AND " _
                             & " ( @" + DbSyncSession.SyncCheckConcurrency + " = 0 OR scope_timestamp = @" + DbSyncSession.SyncScopeTimestamp + "); " _
                             & "SET @" + DbSyncSession.SyncRowCount + " = @@rowcount"
                .Parameters.Add("@" & DbSyncSession.SyncScopeKnowledge, SqlDbType.VarBinary, 10000)
                .Parameters.Add("@" & DbSyncSession.SyncScopeCleanupKnowledge, SqlDbType.VarBinary, 10000)
                .Parameters.Add("@" & DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncScopeId, SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            sampleDbProvider.UpdateScopeInfoCommand = updReplicaInfoCmd


            'Return the maximum timestamp from the Customer_Tracking table. 
            'If more tables are synchronized, the query should UNION 
            'all of the results. The table name is not schema-qualified 
            'in this case because the name was not schema qualified in the 
            'DbSyncAdapter constructor. 
            Dim selTableMaxTsCmd As New SqlCommand()
            selTableMaxTsCmd.CommandType = CommandType.Text
            selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " _
                                         & "MAX(local_update_peer_timestamp) AS max_timestamp " _
                                         & "FROM Sync.Customer_Tracking"
            sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd

            Return sampleDbProvider
        End Function
    End Class

    'Handle the statistics that are returned by the SyncAgent. 
    Public Class SampleStats
        Public Sub DisplayStats(ByVal syncStatistics As SyncOperationStatistics, ByVal syncType As String)
            Console.WriteLine([String].Empty)
            If syncType = "initial" Then
                Console.WriteLine("****** Initial Synchronization ******")
            ElseIf syncType = "subsequent" Then
                Console.WriteLine("***** Subsequent Synchronization ****")
            End If

            Console.WriteLine("Start Time: " & syncStatistics.SyncStartTime)
            Console.WriteLine("Total Changes Uploaded: " & syncStatistics.UploadChangesTotal)
            Console.WriteLine("Total Changes Downloaded: " & syncStatistics.DownloadChangesTotal)
            Console.WriteLine("Complete Time: " & syncStatistics.SyncEndTime)
            Console.WriteLine([String].Empty)
        End Sub
    End Class
End Class

См. также

Другие ресурсы

Как подготовить серверную базу данных для синхронизации совместной работы (не SQL Server)

Синхронизация других баз данных, совместимых с ADO.NET