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


Как использовать переменные сеанса

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

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

Основные сведения о переменных сеанса

Sync Framework предоставляет набор переменных сеанса, с помощью которых в процессе синхронизации можно передавать значения командам SyncAdapter и DbServerSyncProvider. Эти переменные указываются в командах ADO.NET аналогично другим параметрам запросов или хранимых процедур. Во время сеанса синхронизации, когда каждый объект команды ADO.NET вызывается кодом DbServerSyncProvider, поставщик просматривает коллекцию параметров синхронизации (SyncParameters) для выяснения, можно ли сопоставить конкретный параметр с командой параметра ADO.NET исходя из имени. Если есть совпадение с сеансовой переменной либо с пользовательским параметром, такая переменная заполняется кодом Sync Framework до того, как поставщик вызовет команду.

Например, следующий запрос производит выборку изменений из таблицы Customer с использованием переменных сеанса sync_last_received_anchor, sync_new_received_anchor и sync_client_id.

"SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND
InsertTimestamp <= @sync_new_received_anchor AND InsertId <>
@sync_client_id"

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

"SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
Sales.Customer WHERE InsertTimestamp > @" +
SyncSession.SyncLastReceivedAnchor + 
" AND InsertTimestamp <= @" +
SyncSession.SyncNewReceivedAnchor +
" AND InsertId <> @"
SyncSession.SyncClientId

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

Переменная сеанса Использование

sync_client_id, sync_client_id_hash, sync_client_id_binary и sync_originator_id

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

По умолчанию платформы Sync Framework определяют каждого клиента с помощью идентификатора GUID, который возвращается функцией sync_client_id. Можно также создать хэш идентификатора и использовать функцию sync_client_id_hash в запросах. Функция sync_client_id_binary полезна при трассировке изменений с помощью отслеживания изменений SQL Server. Можно сопоставить идентификатор GUID из функции sync_client_id с целым числом и использовать sync_originator_id. Дополнительные сведения см. в подразделе «Примеры» далее в этом разделе.

sync_last_received_anchor и sync_new_received_anchor

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

sync_force_write

Используется со значением ApplyAction из RetryWithForceWrite для принудительного применения изменения в случае конфликта или ошибки. Дополнительные сведения см. в разделе Как обрабатывать конфликты и ошибки в данных.

sync_row_count

Возвращает число строк, обработанных последней операцией на сервере. В базах данных SQL Server значение этой переменной представляет функция @@ROWCOUNT. Количество строк, равное 0, указывает на то, что операцию выполнить не удалось; как правило, это происходит из-за конфликта или ошибки. Дополнительные сведения см. в разделе Как обрабатывать конфликты и ошибки в данных.

sync_initialized

Указывает, является ли синхронизация первичной (значение 0) или последующей (значение 1).

sync_table_name и sync_group_name

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

sync_batch_count, sync_batch_size и sync_max_received_anchor

Используются при пакетных изменениях. Дополнительные сведения см. в разделе Как задать порядок и размер пакета для изменений.

sync_session_id

Возвращает значение идентификатора GUID, идентифицирующего текущий сеанс синхронизации.

Пример

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

Ключевые элементы API-интерфейса

В этом разделе приведены примеры кода, которые определяют ключевые элементы API-интерфейса, связанные с переменными сеанса. Следующий пример кода задает запрос для свойства SelectNewAnchorCommand, который устанавливает значение для переменной sync_new_received_anchor. Это значение используется командами синхронизации для выборки изменений в базе данных сервера.

SqlCommand selectNewAnchorCommand = new SqlCommand();
string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
selectNewAnchorCommand.CommandText = 
    "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
selectNewAnchorCommand.Connection = serverConn;
this.SelectNewAnchorCommand = selectNewAnchorCommand;
Dim selectNewAnchorCommand As New SqlCommand()
Dim newAnchorVariable As String = "@" + SyncSession.SyncNewReceivedAnchor
With selectNewAnchorCommand
    .CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1"
    .Parameters.Add(newAnchorVariable, SqlDbType.Timestamp)
    .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
    .Connection = serverConn
End With
Me.SelectNewAnchorCommand = selectNewAnchorCommand

Следующий пример кода определяет команду выборки на сервере добавочных обновлений, которые применяются к клиенту. Команда включает переменные привязки и переменную sync_originator_id. Значение для переменной sync_originator_id предоставляется запросом, заданным в свойстве SelectClientIdCommand. Это свойство и запрос описаны в конце раздела.

SqlCommand vendorIncrUpdates = new SqlCommand();
vendorIncrUpdates.CommandText =
    "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
    "FROM Sales.Vendor " +
    "WHERE (UpdateTimestamp > @sync_last_received_anchor " +
    "AND UpdateTimestamp <= @sync_new_received_anchor " +
    "AND UpdateId <> @sync_originator_id " +
    "AND NOT (InsertTimestamp > @sync_last_received_anchor " +
    "AND InsertId <> @sync_originator_id))";
vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
vendorIncrUpdates.Connection = serverConn;
vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates;
Dim vendorIncrUpdates As New SqlCommand()
With vendorIncrUpdates
    .CommandText = _
        "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
      & "FROM Sales.Vendor " _
      & "WHERE (UpdateTimestamp > @sync_last_received_anchor " _
      & "AND UpdateTimestamp <= @sync_new_received_anchor " _
      & "AND UpdateId <> @sync_originator_id " _
      & "AND NOT (InsertTimestamp > @sync_last_received_anchor " _
      & "AND InsertId <> @sync_originator_id))"
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
    .Connection = serverConn
End With
vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates

В следующем примере кода приведена команда для применения добавочных обновлений от клиента к серверу. Кроме переменных привязки и идентификатора команда содержит sync_force_write и sync_row_count variables.

SqlCommand vendorUpdates = new SqlCommand();
vendorUpdates.CommandText =
    "UPDATE Sales.Vendor SET " + 
    "VendorName = @VendorName, CreditRating = @CreditRating, " + 
    "PreferredVendor = @PreferredVendor, " + 
    "UpdateId = @sync_originator_id " +           
    "WHERE (VendorId = @VendorId) " + 
    "AND (@sync_force_write = 1 " + 
    "OR (UpdateTimestamp <= @sync_last_received_anchor " +
    "OR UpdateId = @sync_originator_id)) " + 
    "SET @sync_row_count = @@rowcount";
vendorUpdates.Parameters.Add("@VendorName", SqlDbType.NVarChar);
vendorUpdates.Parameters.Add("@CreditRating", SqlDbType.NVarChar);
vendorUpdates.Parameters.Add("@PreferredVendor", SqlDbType.NVarChar);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
vendorUpdates.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
vendorUpdates.Connection = serverConn;
vendorSyncAdapter.UpdateCommand = vendorUpdates;
Dim vendorUpdates As New SqlCommand()
With vendorUpdates
    .CommandText = _
        "UPDATE Sales.Vendor SET " _
      & "VendorName = @VendorName, CreditRating = @CreditRating, " _
      & "PreferredVendor = @PreferredVendor, " _
      & "UpdateId = @sync_originator_id " _
      & "WHERE (VendorId = @VendorId) " _
      & "AND (@sync_force_write = 1 " _
      & "OR (UpdateTimestamp <= @sync_last_received_anchor " _
      & "OR UpdateId = @sync_originator_id)) " _
      & "SET @sync_row_count = @@rowcount"
    .Parameters.Add("@VendorName", SqlDbType.NVarChar)
    .Parameters.Add("@CreditRating", SqlDbType.NVarChar)
    .Parameters.Add("@PreferredVendor", SqlDbType.NVarChar)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
    .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
    .Connection = serverConn
End With
vendorSyncAdapter.UpdateCommand = vendorUpdates

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

SqlCommand vendorIncrDeletes = new SqlCommand();
vendorIncrDeletes.CommandText =
    "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
    "FROM Sales.Vendor_Tombstone " +
    "WHERE (@sync_initialized = 1 " +
    "AND DeleteTimestamp > @sync_last_received_anchor " +
    "AND DeleteTimestamp <= @sync_new_received_anchor " +
    "AND DeleteId <> @sync_originator_id)";
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
vendorIncrDeletes.Connection = serverConn;
vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes;
Dim vendorIncrDeletes As New SqlCommand()
With vendorIncrDeletes
    .CommandText = _
        "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
      & "FROM Sales.Vendor_Tombstone " _
      & "WHERE (@sync_initialized = 1 " _
      & "AND DeleteTimestamp > @sync_last_received_anchor " _
      & "AND DeleteTimestamp <= @sync_new_received_anchor " _
      & "AND DeleteId <> @sync_originator_id)"
    .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
    .Connection = serverConn
End With
vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes

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

SqlCommand selectClientIdCommand = new SqlCommand();
selectClientIdCommand.CommandType = CommandType.StoredProcedure;
selectClientIdCommand.CommandText = "usp_GetOriginatorId";
selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output;
selectClientIdCommand.Connection = serverConn;
this.SelectClientIdCommand = selectClientIdCommand;
Dim selectClientIdCommand As New SqlCommand()
With selectClientIdCommand
    .CommandType = CommandType.StoredProcedure
    .CommandText = "usp_GetOriginatorId"
    .Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output
    .Connection = serverConn
End With
Me.SelectClientIdCommand = selectClientIdCommand

Следующий пример кода создает таблицу сопоставления и хранимую процедуру для ее считывания и заполнения. Хранимая процедура принимает на входе идентификатор клиента (GUID) и возвращает идентификатор инициатора (целочисленное значение). На сервере вставляется строка сопоставления и добавляются дополнительные строки по мере синхронизации нового клиента. После первой синхронизации клиента таблица сопоставлений содержит одну запись для этого клиента. Поскольку команды SyncAdapter используют идентификатор инициатора, столбцы отслеживания в таблице Vendor имеют тип int, а не uniqueidentifier.

CREATE TABLE IdMapping(
    ClientId uniqueidentifier NOT NULL PRIMARY KEY, 
    OriginatorId int NOT NULL)
GO

--Insert a mapping for the server.
INSERT INTO IdMapping VALUES ('00000000-0000-0000-0000-000000000000', 0)
GO

CREATE PROCEDURE usp_GetOriginatorId
    @sync_client_id uniqueidentifier,
    @sync_originator_id int out

AS
     SELECT @sync_originator_id = OriginatorId FROM IdMapping WHERE ClientId = @sync_client_id 

     IF ( @sync_originator_id IS NULL )
     BEGIN
          SELECT @sync_originator_id = MAX(OriginatorId) + 1 FROM IdMapping
          INSERT INTO IdMapping VALUES (@sync_client_id, @sync_originator_id)
     END
GO

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

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

using System;
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.Server;
using Microsoft.Synchronization.Data.SqlServerCe;

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

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

            //Request a password for the client database, and delete
            //and re-create the database. The client synchronization
            //provider also enables you to create the client database 
            //if it does not exist.
            Utility.SetPassword_SqlCeClientSync();
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeClientSync, true);
            
            //Initial synchronization. Instantiate the SyncAgent
            //and call Synchronize.
            SampleSyncAgent sampleSyncAgent = new SampleSyncAgent();
            SyncStatistics syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "initial");

            //Make changes on the server and client.
            Utility.MakeDataChangesOnServer("Vendor");
            Utility.MakeDataChangesOnClient("Vendor");

            //Subsequent synchronization.
            syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "subsequent");

            //Return the server data back to its original state.
            Utility.CleanUpServer();

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

    //Create a class that is derived from 
    //Microsoft.Synchronization.SyncAgent.
    public class SampleSyncAgent : SyncAgent
    {
        public SampleSyncAgent()
        {            
            //Instantiate a client synchronization provider and specify it
            //as the local provider for this synchronization agent.
            this.LocalProvider = new SampleClientSyncProvider();

            //Instantiate a server synchronization provider and specify it
            //as the remote provider for this synchronization agent.
            this.RemoteProvider = new SampleServerSyncProvider();

            //Add the Vendor table: specify a synchronization direction of
            //Bidirectional, and that an existing table should be dropped.
            SyncTable vendorSyncTable = new SyncTable("Vendor");
            vendorSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            vendorSyncTable.SyncDirection = SyncDirection.Bidirectional;
            this.Configuration.SyncTables.Add(vendorSyncTable);
        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.Server.DbServerSyncProvider.
    public class SampleServerSyncProvider : DbServerSyncProvider
    {
        public SampleServerSyncProvider()
        {
            //Create a connection to the sample server database.
            Utility util = new Utility();
            SqlConnection serverConn = new SqlConnection(Utility.ConnStr_DbServerSync);
            this.Connection = serverConn;
          
            //Create a command to retrieve a new anchor value from
            //the server. In this case, we use a timestamp value
            //that is retrieved and stored in the client database.
            //During each synchronization, the new anchor value and
            //the last anchor value from the previous synchronization
            //are used: the set of changes between these upper and
            //lower bounds is synchronized.
            //
            //SyncSession.SyncNewReceivedAnchor is a string constant; 
            //you could also use @sync_new_received_anchor directly in 
            //your queries.
            SqlCommand selectNewAnchorCommand = new SqlCommand();
            string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
            selectNewAnchorCommand.CommandText = 
                "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;
            
            //Create a command that enables you to pass in a 
            //client ID (a GUID) and get back the orginator ID (an integer) 
            //that is defined in a mapping table on the server.
            SqlCommand selectClientIdCommand = new SqlCommand();
            selectClientIdCommand.CommandType = CommandType.StoredProcedure;
            selectClientIdCommand.CommandText = "usp_GetOriginatorId";
            selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output;
            selectClientIdCommand.Connection = serverConn;
            this.SelectClientIdCommand = selectClientIdCommand;

            //Create a SyncAdapter for the Vendor table, and then define
            //the commands to synchronize changes:
            //* SelectIncrementalInsertsCommand, SelectIncrementalUpdatesCommand,
            //  and SelectIncrementalDeletesCommand are used to select changes
            //  from the server that the client provider then applies to the client.
            //* InsertCommand, UpdateCommand, and DeleteCommand are used to apply
            //  to the server the changes that the client provider has selected
            //  from the client.

            //Create the SyncAdapter
            SyncAdapter vendorSyncAdapter = new SyncAdapter("Vendor");            
            
            //Select inserts from the server.
            //This command includes three session variables:
            //@sync_last_received_anchor, @sync_new_received_anchor,
            //and @sync_originator_id. The anchor variables are used with
            //SelectNewAnchorCommand to determine the set of changes to 
            //synchronize. In other example code, the commands use 
            //@sync_client_id instead of @sync_originator_id. In this case, 
            //@sync_originator_id is used because the SelectClientIdCommand 
            //is specified.
            SqlCommand vendorIncrInserts = new SqlCommand();
            vendorIncrInserts.CommandText =  
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
                "FROM Sales.Vendor " +
                "WHERE (InsertTimestamp > @sync_last_received_anchor " +
                "AND InsertTimestamp <= @sync_new_received_anchor " +
                "AND InsertId <> @sync_originator_id)";
            vendorIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrInserts.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorIncrInserts.Connection = serverConn;
            vendorSyncAdapter.SelectIncrementalInsertsCommand = vendorIncrInserts;

            //Apply inserts to the server.
            //This command includes @sync_row_count, which returns
            //a count of how many rows were affected by the
            //last database operation. In SQL Server, the variable
            //is assigned the value of @@rowcount. The count is used
            //to determine whether an operation was successful or
            //was unsuccessful due to a conflict or an error.
            SqlCommand vendorInserts = new SqlCommand();
            vendorInserts.CommandText =
                "INSERT INTO Sales.Vendor (VendorId, VendorName, CreditRating, PreferredVendor, InsertId, UpdateId) " +
                "VALUES (@VendorId, @VendorName, @CreditRating, @PreferredVendor, @sync_originator_id, @sync_originator_id) " +
                "SET @sync_row_count = @@rowcount";
            vendorInserts.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);
            vendorInserts.Parameters.Add("@VendorName", SqlDbType.NVarChar);
            vendorInserts.Parameters.Add("@CreditRating", SqlDbType.NVarChar);
            vendorInserts.Parameters.Add("@PreferredVendor", SqlDbType.NVarChar);
            vendorInserts.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorInserts.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            vendorInserts.Connection = serverConn;
            vendorSyncAdapter.InsertCommand = vendorInserts;
                                    
            
            //Select updates from the server
            SqlCommand vendorIncrUpdates = new SqlCommand();
            vendorIncrUpdates.CommandText =
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
                "FROM Sales.Vendor " +
                "WHERE (UpdateTimestamp > @sync_last_received_anchor " +
                "AND UpdateTimestamp <= @sync_new_received_anchor " +
                "AND UpdateId <> @sync_originator_id " +
                "AND NOT (InsertTimestamp > @sync_last_received_anchor " +
                "AND InsertId <> @sync_originator_id))";
            vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorIncrUpdates.Connection = serverConn;
            vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates;
            
            //Apply updates to the server.
            //This command includes @sync_force_write, which can
            //be used to apply changes in case of a conflict.
            SqlCommand vendorUpdates = new SqlCommand();
            vendorUpdates.CommandText =
                "UPDATE Sales.Vendor SET " + 
                "VendorName = @VendorName, CreditRating = @CreditRating, " + 
                "PreferredVendor = @PreferredVendor, " + 
                "UpdateId = @sync_originator_id " +           
                "WHERE (VendorId = @VendorId) " + 
                "AND (@sync_force_write = 1 " + 
                "OR (UpdateTimestamp <= @sync_last_received_anchor " +
                "OR UpdateId = @sync_originator_id)) " + 
                "SET @sync_row_count = @@rowcount";
            vendorUpdates.Parameters.Add("@VendorName", SqlDbType.NVarChar);
            vendorUpdates.Parameters.Add("@CreditRating", SqlDbType.NVarChar);
            vendorUpdates.Parameters.Add("@PreferredVendor", SqlDbType.NVarChar);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorUpdates.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            vendorUpdates.Connection = serverConn;
            vendorSyncAdapter.UpdateCommand = vendorUpdates;


            //Select deletes from the server.
            //This command includes @sync_initialized, which is
            //used to determine whether a client has been 
            //initialized already. If this variable returns 0,
            //this is the first synchronization for this client ID
            //or originator ID.
            SqlCommand vendorIncrDeletes = new SqlCommand();
            vendorIncrDeletes.CommandText =
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
                "FROM Sales.Vendor_Tombstone " +
                "WHERE (@sync_initialized = 1 " +
                "AND DeleteTimestamp > @sync_last_received_anchor " +
                "AND DeleteTimestamp <= @sync_new_received_anchor " +
                "AND DeleteId <> @sync_originator_id)";
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorIncrDeletes.Connection = serverConn;
            vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes;

            //Apply deletes to the server.            
            SqlCommand vendorDeletes = new SqlCommand();
            vendorDeletes.CommandText =
                "DELETE FROM Sales.Vendor " +
                "WHERE (VendorId = @VendorId) " + 
                "AND (@sync_force_write = 1 " + 
                "OR (UpdateTimestamp <= @sync_last_received_anchor " + 
                "OR UpdateId = @sync_originator_id)) " + 
                "SET @sync_row_count = @@rowcount " + 
                "IF (@sync_row_count > 0)  BEGIN " + 
                "UPDATE Sales.Vendor_Tombstone " + 
                "SET DeleteId = @sync_originator_id " +
                "WHERE (VendorId = @VendorId) " + 
                "END";
            vendorDeletes.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);       
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);           
            vendorDeletes.Connection = serverConn;
            vendorSyncAdapter.DeleteCommand = vendorDeletes;     


            //Add the SyncAdapter to the server synchronization provider.
            this.SyncAdapters.Add(vendorSyncAdapter);

        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider.
    //You can just instantiate the provider directly and associate it
    //with the SyncAgent, but here we use this class to handle client 
    //provider events.
    public class SampleClientSyncProvider : SqlCeClientSyncProvider
    {
            
        public SampleClientSyncProvider()
        {
            //Specify a connection string for the sample client database.
            Utility util = new Utility();
            this.ConnectionString = Utility.ConnStr_SqlCeClientSync;

            //We use the CreatingSchema event to change the schema
            //by using the API. We use the SchemaCreated event to 
            //change the schema by using SQL.
            this.CreatingSchema +=new EventHandler<CreatingSchemaEventArgs>(SampleClientSyncProvider_CreatingSchema);
            this.SchemaCreated +=new EventHandler<SchemaCreatedEventArgs>(SampleClientSyncProvider_SchemaCreated);
        }

        private void SampleClientSyncProvider_CreatingSchema(object sender, CreatingSchemaEventArgs e)
        {
            //Set the RowGuid property because it is not copied
            //to the client by default. This is also a good time
            //to specify literal defaults with .Columns[ColName].DefaultValue,
            //but we will specify defaults like NEWID() by calling
            //ALTER TABLE after the table is created.
            Console.Write("Creating schema for " + e.Table.TableName + " | ");                        
            e.Schema.Tables["Vendor"].Columns["VendorId"].RowGuid = true;
        }

        private void SampleClientSyncProvider_SchemaCreated(object sender, SchemaCreatedEventArgs e)
        {        
            //Call ALTER TABLE on the client. This must be done
            //over the same connection and within the same
            //transaction that Sync Framework uses
            //to create the schema on the client.
            Utility util = new Utility();
            Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, e.Table.TableName);
            Console.WriteLine("Schema created for " + e.Table.TableName);
        }
    }

    //Handle the statistics that are returned by the SyncAgent.
    public class SampleStats
    {
        public void DisplayStats(SyncStatistics 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.TotalChangesUploaded);
            Console.WriteLine("Total Changes Downloaded: " + syncStatistics.TotalChangesDownloaded);
            Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime);
            Console.WriteLine(String.Empty);
        }
    }
}
Imports System
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.Server
Imports Microsoft.Synchronization.Data.SqlServerCe


Class Program

    Shared Sub Main(ByVal args() As String)

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

        'Request a password for the client database, and delete
        'and re-create the database. The client synchronization
        'provider also enables you to create the client database 
        'if it does not exist.
        Utility.SetPassword_SqlCeClientSync()
        Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeClientSync, True)

        'Initial synchronization. Instantiate the SyncAgent
        'and call Synchronize.
        Dim sampleSyncAgent As New SampleSyncAgent()
        Dim syncStatistics As SyncStatistics = sampleSyncAgent.Synchronize()
        sampleStats.DisplayStats(syncStatistics, "initial")

        'Make changes on the server and client.
        Utility.MakeDataChangesOnServer("Vendor")
        Utility.MakeDataChangesOnClient("Vendor")

        'Subsequent synchronization.
        syncStatistics = sampleSyncAgent.Synchronize()
        sampleStats.DisplayStats(syncStatistics, "subsequent")

        'Return the server data back to its original state.
        Utility.CleanUpServer()

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

    End Sub 'Main
End Class 'Program

'Create a class that is derived from 
'Microsoft.Synchronization.SyncAgent.
Public Class SampleSyncAgent
    Inherits SyncAgent

    Public Sub New()

        'Instantiate a client synchronization provider and specify it
        'as the local provider for this synchronization agent.
        Me.LocalProvider = New SampleClientSyncProvider()

        'Instantiate a server synchronization provider and specify it
        'as the remote provider for this synchronization agent.
        Me.RemoteProvider = New SampleServerSyncProvider()

        'Add the Vendor table: specify a synchronization direction of
        'Bidirectional, and that an existing table should be dropped.
        Dim vendorSyncTable As New SyncTable("Vendor")
        vendorSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
        vendorSyncTable.SyncDirection = SyncDirection.Bidirectional
        Me.Configuration.SyncTables.Add(vendorSyncTable)

    End Sub 'New
End Class 'SampleSyncAgent

'Create a class that is derived from 
'Microsoft.Synchronization.Server.DbServerSyncProvider.
Public Class SampleServerSyncProvider
    Inherits DbServerSyncProvider

    Public Sub New()

        'Create a connection to the sample server database.
        Dim util As New Utility()
        Dim serverConn As New SqlConnection(Utility.ConnStr_DbServerSync)
        Me.Connection = serverConn

        'Create a command to retrieve a new anchor value from
        'the server. In this case, we use a timestamp value
        'that is retrieved and stored in the client database.
        'During each synchronization, the new anchor value and
        'the last anchor value from the previous synchronization
        'are used: the set of changes between these upper and
        'lower bounds is synchronized.
        '
        'SyncSession.SyncNewReceivedAnchor is a string constant; 
        'you could also use @sync_new_received_anchor directly in 
        'your queries.
        Dim selectNewAnchorCommand As New SqlCommand()
        Dim newAnchorVariable As String = "@" + SyncSession.SyncNewReceivedAnchor
        With selectNewAnchorCommand
            .CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1"
            .Parameters.Add(newAnchorVariable, SqlDbType.Timestamp)
            .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
            .Connection = serverConn
        End With
        Me.SelectNewAnchorCommand = selectNewAnchorCommand

        'Create a command that enables you to pass in a 
        'client ID (a GUID) and get back the orginator ID (an integer) 
        'that is defined in a mapping table on the server.
        Dim selectClientIdCommand As New SqlCommand()
        With selectClientIdCommand
            .CommandType = CommandType.StoredProcedure
            .CommandText = "usp_GetOriginatorId"
            .Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output
            .Connection = serverConn
        End With
        Me.SelectClientIdCommand = selectClientIdCommand

        'Create a SyncAdapter for the Vendor table, and then define
        'the commands to synchronize changes:
        '* SelectIncrementalInsertsCommand, SelectIncrementalUpdatesCommand,
        '  and SelectIncrementalDeletesCommand are used to select changes
        '  from the server that the client provider then applies to the client.
        '* InsertCommand, UpdateCommand, and DeleteCommand are used to apply
        '  to the server the changes that the client provider has selected
        '  from the client.

        'Create the SyncAdapter
        Dim vendorSyncAdapter As New SyncAdapter("Vendor")

        'Select inserts from the server.
        'This command includes three session variables:
        '@sync_last_received_anchor, @sync_new_received_anchor,
        'and @sync_originator_id. The anchor variables are used with
        'SelectNewAnchorCommand to determine the set of changes to 
        'synchronize. In other example code, the commands use 
        '@sync_client_id instead of @sync_originator_id. In this case, 
        '@sync_originator_id is used because the SelectClientIdCommand 
        'is specified.
        Dim vendorIncrInserts As New SqlCommand()
        With vendorIncrInserts
            .CommandText = _
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
              & "FROM Sales.Vendor " _
              & "WHERE (InsertTimestamp > @sync_last_received_anchor " _
              & "AND InsertTimestamp <= @sync_new_received_anchor " _
              & "AND InsertId <> @sync_originator_id)"
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.SelectIncrementalInsertsCommand = vendorIncrInserts

        'Apply inserts to the server.
        'This command includes @sync_row_count, which returns
        'a count of how many rows were affected by the
        'last database operation. In SQL Server, the variable
        'is assigned the value of @@rowcount. The count is used
        'to determine whether an operation was successful or
        'was unsuccessful due to a conflict or an error.
        Dim vendorInserts As New SqlCommand()
        With vendorInserts
            .CommandText = _
                "INSERT INTO Sales.Vendor (VendorId, VendorName, CreditRating, PreferredVendor, InsertId, UpdateId) " _
              & "VALUES (@VendorId, @VendorName, @CreditRating, @PreferredVendor, @sync_originator_id, @sync_originator_id) " _
              & "SET @sync_row_count = @@rowcount"
            .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@VendorName", SqlDbType.NVarChar)
            .Parameters.Add("@CreditRating", SqlDbType.NVarChar)
            .Parameters.Add("@PreferredVendor", SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.InsertCommand = vendorInserts

        'Select updates from the server
        Dim vendorIncrUpdates As New SqlCommand()
        With vendorIncrUpdates
            .CommandText = _
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
              & "FROM Sales.Vendor " _
              & "WHERE (UpdateTimestamp > @sync_last_received_anchor " _
              & "AND UpdateTimestamp <= @sync_new_received_anchor " _
              & "AND UpdateId <> @sync_originator_id " _
              & "AND NOT (InsertTimestamp > @sync_last_received_anchor " _
              & "AND InsertId <> @sync_originator_id))"
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates

        'Apply updates to the server.
        'This command includes @sync_force_write, which can
        'be used to apply changes in case of a conflict.
        Dim vendorUpdates As New SqlCommand()
        With vendorUpdates
            .CommandText = _
                "UPDATE Sales.Vendor SET " _
              & "VendorName = @VendorName, CreditRating = @CreditRating, " _
              & "PreferredVendor = @PreferredVendor, " _
              & "UpdateId = @sync_originator_id " _
              & "WHERE (VendorId = @VendorId) " _
              & "AND (@sync_force_write = 1 " _
              & "OR (UpdateTimestamp <= @sync_last_received_anchor " _
              & "OR UpdateId = @sync_originator_id)) " _
              & "SET @sync_row_count = @@rowcount"
            .Parameters.Add("@VendorName", SqlDbType.NVarChar)
            .Parameters.Add("@CreditRating", SqlDbType.NVarChar)
            .Parameters.Add("@PreferredVendor", SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.UpdateCommand = vendorUpdates

        'Select deletes from the server.
        'This command includes @sync_initialized, which is
        'used to determine whether a client has been 
        'initialized already. If this variable returns 0,
        'this is the first synchronization for this client ID
        'or originator ID.
        Dim vendorIncrDeletes As New SqlCommand()
        With vendorIncrDeletes
            .CommandText = _
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
              & "FROM Sales.Vendor_Tombstone " _
              & "WHERE (@sync_initialized = 1 " _
              & "AND DeleteTimestamp > @sync_last_received_anchor " _
              & "AND DeleteTimestamp <= @sync_new_received_anchor " _
              & "AND DeleteId <> @sync_originator_id)"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes

        'Apply deletes to the server.            
        Dim vendorDeletes As New SqlCommand()
        With vendorDeletes
            .CommandText = _
                "DELETE FROM Sales.Vendor " _
              & "WHERE (VendorId = @VendorId) " _
              & "AND (@sync_force_write = 1 " _
              & "OR (UpdateTimestamp <= @sync_last_received_anchor " _
              & "OR UpdateId = @sync_originator_id)) " _
              & "SET @sync_row_count = @@rowcount " _
              & "IF (@sync_row_count > 0)  BEGIN " _
              & "UPDATE Sales.Vendor_Tombstone " _
              & "SET DeleteId = @sync_originator_id " _
              & "WHERE (VendorId = @VendorId) " _
              & "END"
            .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.DeleteCommand = vendorDeletes


        'Add the SyncAdapter to the server synchronization provider.
        Me.SyncAdapters.Add(vendorSyncAdapter)

    End Sub 'New 
End Class 'SampleServerSyncProvider

'Create a class that is derived from 
'Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider.
'You can just instantiate the provider directly and associate it
'with the SyncAgent, but here we use this class to handle client 
'provider events.
Public Class SampleClientSyncProvider
    Inherits SqlCeClientSyncProvider


    Public Sub New()
        'Specify a connection string for the sample client database.
        Dim util As New Utility()
        Me.ConnectionString = Utility.ConnStr_SqlCeClientSync

        'We use the CreatingSchema event to change the schema
        'by using the API. We use the SchemaCreated event to 
        'change the schema by using SQL.
        AddHandler Me.CreatingSchema, AddressOf SampleClientSyncProvider_CreatingSchema
        AddHandler Me.SchemaCreated, AddressOf SampleClientSyncProvider_SchemaCreated

    End Sub 'New


    Private Sub SampleClientSyncProvider_CreatingSchema(ByVal sender As Object, ByVal e As CreatingSchemaEventArgs)

        'Set the RowGuid property because it is not copied
        'to the client by default. This is also a good time
        'to specify literal defaults with .Columns[ColName].DefaultValue,
        'but we will specify defaults like NEWID() by calling
        'ALTER TABLE after the table is created.
        Console.Write("Creating schema for " + e.Table.TableName + " | ")
        e.Schema.Tables("Vendor").Columns("VendorId").RowGuid = True

    End Sub 'SampleClientSyncProvider_CreatingSchema


    Private Sub SampleClientSyncProvider_SchemaCreated(ByVal sender As Object, ByVal e As SchemaCreatedEventArgs)

        'Call ALTER TABLE on the client. This must be done
        'over the same connection and within the same
        'transaction that Sync Framework uses
        'to create the schema on the client.
        Dim util As New Utility()
        Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, e.Table.TableName)
        Console.WriteLine("Schema created for " + e.Table.TableName)

    End Sub 'SampleClientSyncProvider_SchemaCreated
End Class 'SampleClientSyncProvider

'Handle the statistics that are returned by the SyncAgent.
Public Class SampleStats

    Public Sub DisplayStats(ByVal syncStatistics As SyncStatistics, 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.TotalChangesUploaded)
        Console.WriteLine("Total Changes Downloaded: " & syncStatistics.TotalChangesDownloaded)
        Console.WriteLine("Complete Time: " & syncStatistics.SyncCompleteTime)
        Console.WriteLine(String.Empty)

    End Sub 'DisplayStats
End Class 'SampleStats

См. также

Основные положения

Программирование распространенных задач синхронизации клиента и сервера