Cómo especificar el orden y el tamaño de los lotes de los cambios

En este tema se muestra cómo especificar el orden y el tamaño por lotes de los cambios que se descargan en una base de datos cliente mediante Sync Services for ADO.NET. Los ejemplos de este tema se centran en los tipos y eventos siguientes de Sync Services:

Para obtener información acerca de cómo ejecutar el código de ejemplo, vea "Aplicaciones de ejemplo en los temas "Cómo..." en Programar tareas comunes de sincronización de cliente y servidor.

Información sobre el procesamiento por lotes y el orden de sincronización

Para cada tabla que se sincroniza, los cambios se seleccionan de la base de datos servidor siguiendo el orden: inserciones, actualizaciones y eliminaciones. El orden que se sigue para aplicar los cambios a la base de datos cliente es: eliminaciones, inserciones y actualizaciones. Cuando se sincronizan varias tablas, el orden en el que se procesa cada tabla depende del orden en el que se agregó su objeto SyncTable a la colección de tablas para el agente de sincronización. Por ejemplo, si se agregan las tablas Customer y OrderHeader en ese orden, primero se seleccionan las inserciones de la tabla Customer y luego las actualizaciones y las eliminaciones. A continuación, se seleccionan los cambios realizados en la tabla OrderHeader. Todos los cambios realizados en la tabla , Customer se aplican a la base de datos cliente en una transacción única (si no se usa el procesamiento por lotes), seguidos de los cambios realizados en , OrderHeader que se aplican a una segunda transacción. Si las tablas Customer y OrderHeader se asignan al mismo objeto SyncGroup, las inserciones, actualizaciones y eliminaciones de ambas tablas se seleccionan una vez. Todos los cambios se aplican a la base de datos cliente en una transacción única (de nuevo, si no se usa el procesamiento por lotes).

De manera predeterminada, Sync Services no divide los cambios en lotes. Los cambios se descargan de la base de datos cliente y se cargan también ahí como una unidad. Para muchas aplicaciones, tiene sentido dividir los cambios en lotes más pequeños. Por ejemplo, si se interrumpe una sesión de sincronización, ésta se puede reiniciar desde el último lote sin necesidad de reenviar todos los cambios. También se produce una mejora del rendimiento, ya que el cliente puede administrar un lote de cambios más pequeño cada vez. Debido a estas ventajas, Sync Services permite a las aplicaciones descargar lotes de cambios en el cliente (no se admite el procesamiento por lotes en las operaciones de carga).

Para habilitar el procesamiento por lotes se especifica un valor para la propiedad BatchSize y se crea un comando para la propiedad SelectNewAnchorCommand que puede devolver valores de delimitador para cada lote de cambios. Sin procesamiento por lotes, las aplicaciones usan los valores de delimitador más reciente y más antiguo para definir los límites superior e inferior de todo el conjunto de cambios que se van a descargar. Para obtener más información, vea Introducción: sincronización del cliente y el servidor. Con procesamiento por lotes, el máximo valor de delimitador recibido define el límite superior del conjunto de cambios, mientras que los valores de delimitador más reciente y más antiguo definen los límites superior e inferior de cada lote de cambios. El evento SessionProgress proporciona una manera eficaz de supervisar todo el progreso de sincronización, mientras que la propiedad BatchProgress proporciona acceso a la información de progreso en el nivel de lote.

Ejemplo

Los ejemplos de código siguientes muestran cómo puede sincronizar las tablas Customer y OrderHeader de la base de datos de ejemplo de Sync Services. Los cambios para estas tablas se descargan en lotes, con 50 cambios por lote. La sincronización inicial descarga 10 filas. Todas las filas se descargan en un único lote y se aplican en una transacción única. La sincronización posterior descarga 92 filas en dos lotes. Cada lote contiene cambios de la tabla Customer y de la tabla OrderHeader, y cada lote se aplica en una transacción única.

Partes principales de la API

Esta sección proporciona ejemplos de código que destacan las partes principales de la API que pueden utilizarse para procesar por lotes y ordenar los cambios. El ejemplo de código siguiente procede de una clase derivada de SyncAgent. El código crea un objeto SyncGroup para las tablas Customer y OrderHeader.

//Create a SyncGroup so that changes to Customer
//and OrderHeader are made in one transaction.
SyncGroup customerOrderSyncGroup = new SyncGroup("CustomerOrder");

//Add each table: specify a synchronization direction of
//DownloadOnly.
SyncTable customerSyncTable = new SyncTable("Customer");
customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
customerSyncTable.SyncDirection = SyncDirection.DownloadOnly;
customerSyncTable.SyncGroup = customerOrderSyncGroup;
this.Configuration.SyncTables.Add(customerSyncTable);

SyncTable orderHeaderSyncTable = new SyncTable("OrderHeader");
orderHeaderSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
orderHeaderSyncTable.SyncDirection = SyncDirection.DownloadOnly;
orderHeaderSyncTable.SyncGroup = customerOrderSyncGroup;
this.Configuration.SyncTables.Add(orderHeaderSyncTable);
'Create a SyncGroup so that changes to Customer
'and OrderHeader are made in one transaction.
Dim customerOrderSyncGroup As New SyncGroup("CustomerOrder")

'Add each table: specify a synchronization direction of
'DownloadOnly.
Dim customerSyncTable As New SyncTable("Customer")
customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
customerSyncTable.SyncDirection = SyncDirection.DownloadOnly
customerSyncTable.SyncGroup = customerOrderSyncGroup
Me.Configuration.SyncTables.Add(customerSyncTable)

Dim orderHeaderSyncTable As New SyncTable("OrderHeader")
orderHeaderSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
orderHeaderSyncTable.SyncDirection = SyncDirection.DownloadOnly
orderHeaderSyncTable.SyncGroup = customerOrderSyncGroup
Me.Configuration.SyncTables.Add(orderHeaderSyncTable)

El ejemplo de código siguiente procede de una clase derivada de DbServerSyncProvider. El código crea un comando de delimitador para una aplicación que no usa procesamiento por lotes. Devuelve un nuevo valor de delimitador una vez para que se sincronice todo el conjunto de cambios. Este ejemplo se incluye para mostrar mejor la diferencia existente entre un comando que usa procesamiento por lotes y otro que no.

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
selectNewAnchorCommand.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1"
selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp)
selectNewAnchorCommand.Parameters(newAnchorVariable).Direction = ParameterDirection.Output
selectNewAnchorCommand.Connection = serverConn
Me.SelectNewAnchorCommand = selectNewAnchorCommand

El ejemplo de código siguiente crea un comando de delimitador que se puede usar si se producen cambios en los lotes. En lugar de devolver un nuevo valor de delimitador una vez para todo el conjunto de cambios, devuelve un nuevo valor de delimitador para cada lote de cambios. Utiliza la propiedad BatchSize para especificar cuántos cambios debe haber en cada lote y las variables de sesión necesarias para pasar valores de delimitador de atrás adelante entre un procedimiento almacenado y el tiempo de ejecución de la sincronización. Si escribe comandos de adaptador de sincronización de forma manual, seguirá usando las variables de sesión @sync_new_received_anchor y @sync_last_received_anchor; la variable de sesión @sync_max_received_anchor la utiliza sólo el nuevo comando de delimitador.

SqlCommand selectNewAnchorCommand = new SqlCommand();
selectNewAnchorCommand.Connection = serverConn;
selectNewAnchorCommand.CommandText = "usp_GetNewBatchAnchor";
selectNewAnchorCommand.CommandType = CommandType.StoredProcedure;            
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp, 8);
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncMaxReceivedAnchor, SqlDbType.Timestamp, 8);
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp, 8);
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchSize, SqlDbType.Int, 4);
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchCount, SqlDbType.Int, 4);            

selectNewAnchorCommand.Parameters["@" + SyncSession.SyncMaxReceivedAnchor].Direction = ParameterDirection.Output;
selectNewAnchorCommand.Parameters["@" + SyncSession.SyncNewReceivedAnchor].Direction = ParameterDirection.Output;
selectNewAnchorCommand.Parameters["@" + SyncSession.SyncBatchCount].Direction = ParameterDirection.InputOutput;
this.SelectNewAnchorCommand = selectNewAnchorCommand;
this.BatchSize = 50;
Dim selectNewAnchorCommand As New SqlCommand()
selectNewAnchorCommand.Connection = serverConn
selectNewAnchorCommand.CommandText = "usp_GetNewBatchAnchor"
selectNewAnchorCommand.CommandType = CommandType.StoredProcedure
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp, 8)
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncMaxReceivedAnchor, SqlDbType.Timestamp, 8)
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp, 8)
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchSize, SqlDbType.Int, 4)
selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchCount, SqlDbType.Int, 4)

selectNewAnchorCommand.Parameters("@" + SyncSession.SyncMaxReceivedAnchor).Direction = ParameterDirection.Output
selectNewAnchorCommand.Parameters("@" + SyncSession.SyncNewReceivedAnchor).Direction = ParameterDirection.Output
selectNewAnchorCommand.Parameters("@" + SyncSession.SyncBatchCount).Direction = ParameterDirection.InputOutput
Me.SelectNewAnchorCommand = selectNewAnchorCommand
Me.BatchSize = 50

El siguiente ejemplo de código crea un procedimiento almacenado que devuelve nuevos valores de delimitador, un valor de delimitador máximo y el recuento de lotes actual para las inserciones y las actualizaciones. El procedimiento permite al proveedor de sincronización del servidor seleccionar los lotes de cambios de la base de datos servidor. La lógica de este procedimiento almacenado es un ejemplo, pero se puede usar cualquier lógica siempre que proporcione los valores obtenidos aquí. Un problema del código de ejemplo es que puede haber lotes vacíos si se cambia una fila más de 50 veces entre sincronizaciones. Puede agregar lógica para solucionar este caso.

CREATE PROCEDURE usp_GetNewBatchAnchor (
    @sync_last_received_anchor timestamp, 
    @sync_batch_size bigint,
    @sync_max_received_anchor timestamp out,
    @sync_new_received_anchor timestamp out,            
    @sync_batch_count int output)            
AS            
       -- Set a default batch size if a valid one is not passed in.
       IF  @sync_batch_size IS NULL OR @sync_batch_size <= 0
         SET @sync_batch_size = 1000    

       -- Before selecting the first batch of changes,
       -- set the maximum anchor value for this synchronization session.
       -- After the first time that this procedure is called, 
       -- Synchronization Services passes a value for @sync_max_received_anchor
       -- to the procedure. Batches of changes are synchronized until this 
       -- value is reached.
       IF @sync_max_received_anchor IS NULL
         SELECT  @sync_max_received_anchor = MIN_ACTIVE_ROWVERSION() - 1
       
       -- If this is the first synchronization session for a database,
       -- get the lowest timestamp value from the tables. By default,
       -- Synchronization Services uses a value of 0 for @sync_last_received_anchor
       -- on the first synchronization. If you do not set @sync_last_received_anchor,
       -- this can cause empty batches to be downloaded until the lowest
       -- timestamp value is reached.
       IF @sync_last_received_anchor IS NULL OR @sync_last_received_anchor = 0
       BEGIN
                
        SELECT @sync_last_received_anchor = MIN(TimestampCol) FROM (
          SELECT MIN(UpdateTimestamp) AS TimestampCol FROM Sales.Customer
          UNION
          SELECT MIN(InsertTimestamp) AS TimestampCol FROM Sales.Customer
          UNION
          SELECT MIN(UpdateTimestamp) AS TimestampCol FROM Sales.OrderHeader
          UNION
          SELECT MIN(InsertTimestamp) AS TimestampCol FROM Sales.OrderHeader
        ) MinTimestamp  
       
        SET @sync_new_received_anchor = @sync_last_received_anchor + @sync_batch_size

        -- Determine how many batches are required during the initial synchronization.
        IF @sync_batch_count <= 0
          SET @sync_batch_count = ((@sync_max_received_anchor / @sync_batch_size) - (@sync_last_received_anchor /  @sync_batch_size))

        END

       ELSE
       BEGIN

        SET @sync_new_received_anchor = @sync_last_received_anchor + @sync_batch_size

        -- Determine how many batches are required during subsequent synchronizations.
        IF @sync_batch_count <= 0
          SET @sync_batch_count = ((@sync_max_received_anchor / @sync_batch_size) - (@sync_new_received_anchor /  @sync_batch_size)) + 1  
       
       END

       -- Check whether this is the last batch.      
       IF @sync_new_received_anchor >= @sync_max_received_anchor
       BEGIN

         SET @sync_new_received_anchor = @sync_max_received_anchor        
         IF @sync_batch_count <= 0
           SET @sync_batch_count = 1

       END
GO

Ejemplo de código completo

El ejemplo de código completo siguiente incluye los ejemplos de código descritos anteriormente y un código adicional para realizar la sincronización. Para el ejemplo, se requiere que la clase Utility esté disponible en Clase de utilidad para los temas "Cómo..." de Sync Services.

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 Utility class handles all functionality that is not
            //directly related to synchronization, such as holding connection 
            //string information and making changes to the server and client databases.
            Utility util = new Utility();

            //The SampleStats class handles information from the 
            //SyncStatistics object that the Synchronize method returns and
            //from SyncAgent events.
            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.
            util.SetClientPassword();
            util.RecreateClientDatabase();
            
            //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.
            util.MakeDataChangesOnServer("CustomerAndOrderHeader");

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

            //Return server data back to its original state.
            util.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();

            //Create a SyncGroup so that changes to Customer
            //and OrderHeader are made in one transaction.
            SyncGroup customerOrderSyncGroup = new SyncGroup("CustomerOrder");

            //Add each table: specify a synchronization direction of
            //DownloadOnly.
            SyncTable customerSyncTable = new SyncTable("Customer");
            customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            customerSyncTable.SyncDirection = SyncDirection.DownloadOnly;
            customerSyncTable.SyncGroup = customerOrderSyncGroup;
            this.Configuration.SyncTables.Add(customerSyncTable);

            SyncTable orderHeaderSyncTable = new SyncTable("OrderHeader");
            orderHeaderSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            orderHeaderSyncTable.SyncDirection = SyncDirection.DownloadOnly;
            orderHeaderSyncTable.SyncGroup = customerOrderSyncGroup;
            this.Configuration.SyncTables.Add(orderHeaderSyncTable);
        }
    }

    //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(util.ServerConnString);
            this.Connection = serverConn;

            //Create a command to retrieve a new anchor value from
            //the server. In this case, we call a stored procedure
            //that returns an anchor that can be used with batches
            //of changes.
            SqlCommand selectNewAnchorCommand = new SqlCommand();
            selectNewAnchorCommand.Connection = serverConn;
            selectNewAnchorCommand.CommandText = "usp_GetNewBatchAnchor";
            selectNewAnchorCommand.CommandType = CommandType.StoredProcedure;            
            selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp, 8);
            selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncMaxReceivedAnchor, SqlDbType.Timestamp, 8);
            selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp, 8);
            selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchSize, SqlDbType.Int, 4);
            selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchCount, SqlDbType.Int, 4);            

            selectNewAnchorCommand.Parameters["@" + SyncSession.SyncMaxReceivedAnchor].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Parameters["@" + SyncSession.SyncNewReceivedAnchor].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Parameters["@" + SyncSession.SyncBatchCount].Direction = ParameterDirection.InputOutput;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;
            this.BatchSize = 50;
            
            //Create SyncAdapters for each table by using the SqlSyncAdapterBuilder:
            //  * Specify the base table and tombstone table names.
            //  * Specify the columns that are used to track when
            //    and where changes are made.
            //  * Specify download only synchronization.
            //  * Call ToSyncAdapter to create the SyncAdapter.
            //  * Specify a name for the SyncAdapter that matches the
            //    the name specified for the corresponding SyncTable.
            //    Do not include the schema names (Sales in this case).

            //Customer table
            SqlSyncAdapterBuilder customerBuilder = new SqlSyncAdapterBuilder(serverConn);

            customerBuilder.TableName = "Sales.Customer";
            customerBuilder.TombstoneTableName = customerBuilder.TableName + "_Tombstone";
            customerBuilder.SyncDirection = SyncDirection.DownloadOnly;
            customerBuilder.CreationTrackingColumn = "InsertTimestamp";
            customerBuilder.UpdateTrackingColumn = "UpdateTimestamp";
            customerBuilder.DeletionTrackingColumn = "DeleteTimestamp";
            
            SyncAdapter customerSyncAdapter = customerBuilder.ToSyncAdapter();
            customerSyncAdapter.TableName = "Customer";
            this.SyncAdapters.Add(customerSyncAdapter);


            //OrderHeader table.
            SqlSyncAdapterBuilder orderHeaderBuilder = new SqlSyncAdapterBuilder(serverConn);

            orderHeaderBuilder.TableName = "Sales.OrderHeader";
            orderHeaderBuilder.TombstoneTableName = orderHeaderBuilder.TableName + "_Tombstone";
            orderHeaderBuilder.SyncDirection = SyncDirection.DownloadOnly;
            orderHeaderBuilder.CreationTrackingColumn = "InsertTimestamp";
            orderHeaderBuilder.UpdateTrackingColumn = "UpdateTimestamp";
            orderHeaderBuilder.DeletionTrackingColumn = "DeleteTimestamp";

            SyncAdapter orderHeaderSyncAdapter = orderHeaderBuilder.ToSyncAdapter();
            orderHeaderSyncAdapter.TableName = "OrderHeader";
            this.SyncAdapters.Add(orderHeaderSyncAdapter);

            //Handle the ChangesSelected event, and display
            //information to the console.
            this.ChangesSelected += new EventHandler<ChangesSelectedEventArgs>(SampleServerSyncProvider_ChangesSelected);
        }

        public void SampleServerSyncProvider_ChangesSelected(object sender, ChangesSelectedEventArgs e)
        {
            Console.WriteLine("Total number of batches: " + e.Context.BatchCount);
            Console.WriteLine("Changes applied for group " + e.GroupMetadata.GroupName);
            Console.WriteLine("Inserts applied for group: " + e.Context.GroupProgress.TotalInserts.ToString());
            Console.WriteLine("Updates applied for group: " + e.Context.GroupProgress.TotalUpdates.ToString());
            Console.WriteLine("Deletes applied for group: " + e.Context.GroupProgress.TotalDeletes.ToString());
        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider.
    public class SampleClientSyncProvider : SqlCeClientSyncProvider
    {

        public SampleClientSyncProvider()
        {
            //Specify a connection string for the sample client database.
            Utility util = new Utility();
            this.ConnectionString = util.ClientConnString;         
        }
    }

    //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 Stats ******");
            }
            else if (syncType == "subsequent")
            {
                Console.WriteLine("***** Subsequent Synchronization Stats ****");
            }

            Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
            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 Utility class handles all functionality that is not
        'directly related to synchronization, such as holding connection 
        'string information and making changes to the server and client databases.
        Dim util As New Utility()

        'The SampleStats class handles information from the 
        'SyncStatistics object that the Synchronize method returns and
        'from SyncAgent events.
        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.
        util.SetClientPassword()
        util.RecreateClientDatabase()

        '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.
        util.MakeDataChangesOnServer("CustomerAndOrderHeader")

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

        'Return server data back to its original state.
        util.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()

        'Create a SyncGroup so that changes to Customer
        'and OrderHeader are made in one transaction.
        Dim customerOrderSyncGroup As New SyncGroup("CustomerOrder")

        'Add each table: specify a synchronization direction of
        'DownloadOnly.
        Dim customerSyncTable As New SyncTable("Customer")
        customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
        customerSyncTable.SyncDirection = SyncDirection.DownloadOnly
        customerSyncTable.SyncGroup = customerOrderSyncGroup
        Me.Configuration.SyncTables.Add(customerSyncTable)

        Dim orderHeaderSyncTable As New SyncTable("OrderHeader")
        orderHeaderSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
        orderHeaderSyncTable.SyncDirection = SyncDirection.DownloadOnly
        orderHeaderSyncTable.SyncGroup = customerOrderSyncGroup
        Me.Configuration.SyncTables.Add(orderHeaderSyncTable)

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(util.ServerConnString)
        Me.Connection = serverConn

        'Create a command to retrieve a new anchor value from
        'the server. In this case, we call a stored procedure
        'that returns an anchor that can be used with batches
        'of changes.
        Dim selectNewAnchorCommand As New SqlCommand()
        selectNewAnchorCommand.Connection = serverConn
        selectNewAnchorCommand.CommandText = "usp_GetNewBatchAnchor"
        selectNewAnchorCommand.CommandType = CommandType.StoredProcedure
        selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp, 8)
        selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncMaxReceivedAnchor, SqlDbType.Timestamp, 8)
        selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp, 8)
        selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchSize, SqlDbType.Int, 4)
        selectNewAnchorCommand.Parameters.Add("@" + SyncSession.SyncBatchCount, SqlDbType.Int, 4)

        selectNewAnchorCommand.Parameters("@" + SyncSession.SyncMaxReceivedAnchor).Direction = ParameterDirection.Output
        selectNewAnchorCommand.Parameters("@" + SyncSession.SyncNewReceivedAnchor).Direction = ParameterDirection.Output
        selectNewAnchorCommand.Parameters("@" + SyncSession.SyncBatchCount).Direction = ParameterDirection.InputOutput
        Me.SelectNewAnchorCommand = selectNewAnchorCommand
        Me.BatchSize = 50
        'Create SyncAdapters for each table by using the SqlSyncAdapterBuilder:
        '  * Specify the base table and tombstone table names.
        '  * Specify the columns that are used to track when
        '    and where changes are made.
        '  * Specify download only synchronization.
        '  * Call ToSyncAdapter to create the SyncAdapter.
        '  * Specify a name for the SyncAdapter that matches the
        '    the name specified for the corresponding SyncTable.
        '    Do not include the schema names (Sales in this case).
        'Customer table
        Dim customerBuilder As New SqlSyncAdapterBuilder(serverConn)

        customerBuilder.TableName = "Sales.Customer"
        customerBuilder.TombstoneTableName = customerBuilder.TableName + "_Tombstone"
        customerBuilder.SyncDirection = SyncDirection.DownloadOnly
        customerBuilder.CreationTrackingColumn = "InsertTimestamp"
        customerBuilder.UpdateTrackingColumn = "UpdateTimestamp"
        customerBuilder.DeletionTrackingColumn = "DeleteTimestamp"

        Dim customerSyncAdapter As SyncAdapter = customerBuilder.ToSyncAdapter()
        customerSyncAdapter.TableName = "Customer"
        Me.SyncAdapters.Add(customerSyncAdapter)


        'OrderHeader table.
        Dim orderHeaderBuilder As New SqlSyncAdapterBuilder(serverConn)

        orderHeaderBuilder.TableName = "Sales.OrderHeader"
        orderHeaderBuilder.TombstoneTableName = orderHeaderBuilder.TableName + "_Tombstone"
        orderHeaderBuilder.SyncDirection = SyncDirection.DownloadOnly
        orderHeaderBuilder.CreationTrackingColumn = "InsertTimestamp"
        orderHeaderBuilder.UpdateTrackingColumn = "UpdateTimestamp"
        orderHeaderBuilder.DeletionTrackingColumn = "DeleteTimestamp"

        Dim orderHeaderSyncAdapter As SyncAdapter = orderHeaderBuilder.ToSyncAdapter()
        orderHeaderSyncAdapter.TableName = "OrderHeader"
        Me.SyncAdapters.Add(orderHeaderSyncAdapter)

        'Handle the ChangesSelected event, and display
        'information to the console.
        AddHandler Me.ChangesSelected, AddressOf SampleServerSyncProvider_ChangesSelected

    End Sub 'New


    Public Sub SampleServerSyncProvider_ChangesSelected(ByVal sender As Object, ByVal e As ChangesSelectedEventArgs)
        Console.WriteLine("Total number of batches: " & e.Context.BatchCount)
        Console.WriteLine("Changes applied for group " & e.GroupMetadata.GroupName)
        Console.WriteLine("Inserts applied for group: " & e.Context.GroupProgress.TotalInserts.ToString())
        Console.WriteLine("Updates applied for group: " & e.Context.GroupProgress.TotalUpdates.ToString())
        Console.WriteLine("Deletes applied for group: " & e.Context.GroupProgress.TotalDeletes.ToString())

    End Sub 'SampleServerSyncProvider_ChangesSelected
End Class 'SampleServerSyncProvider

'Create a class that is derived from 
'Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider.
Public Class SampleClientSyncProvider
    Inherits SqlCeClientSyncProvider


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

    End Sub 'New
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 Stats ******")
        ElseIf syncType = "subsequent" Then
            Console.WriteLine("***** Subsequent Synchronization Stats ****")
        End If

        Console.WriteLine("Start Time: " & syncStatistics.SyncStartTime)
        Console.WriteLine("Total Changes Downloaded: " & syncStatistics.TotalChangesDownloaded)
        Console.WriteLine("Complete Time: " & syncStatistics.SyncCompleteTime)
        Console.WriteLine(String.Empty)

    End Sub 'DisplayStats
End Class 'SampleStats

Vea también

Conceptos

Programar tareas comunes de sincronización de cliente y servidor