Usar el seguimiento de cambios de SQL Server

Este tema proporciona información general del seguimiento de cambios de SQL Server y describe una aplicación de consola que realiza la sincronización bidireccional entre una base de datos de SQL Server y una base de datos de SQL Server Compact. Si el servidor ejecuta SQL Server 2008, se recomienda utilizar el seguimiento de cambios de SQL Server. Si en el servidor se ejecuta una base de datos diferente, vea Utilizar un sistema de seguimiento de cambios personalizado.

Información general sobre el seguimiento de cambios de SQL Server

En muchos de los ejemplos de esta documentación el seguimiento de cambios se administra mediante un conjunto de columnas y desencadenadores que se agregan a las tablas base, y tablas adicionales para el seguimiento de las operaciones de eliminación. Para obtener más información, vea Seguimiento de cambios en la base de datos servidor. Este tipo de seguimiento es útil para las bases de datos que no son de SQL Server 2008. Sin embargo, tiene las desventajas siguientes:

  • Se requieren cambios de esquema en la base de datos servidor. Esto puede afectar a otras aplicaciones o no ser posible de ningún modo.

  • Los desencadenadores se activan para cada cambio realizado en una fila. Esto tiene implicaciones en el rendimiento.

  • La lógica para mantener las versiones y eliminaciones de fila correctas puede volverse compleja.

  • Si una base de datos servidor tiene transacciones cuya ejecución es larga, a menos que estas transacciones se administren correctamente, los cambios de los datos se pueden perder durante la sincronización. Esto puede dar lugar a incoherencias de los datos.

El seguimiento de cambios de SQL Server aborda estos problemas y proporciona un modo directo de realizar un seguimiento de los cambios. Cuando el seguimiento de cambios está habilitado en una tabla, Motor de base de datos de SQL Server conserva la información sobre los cambios que se realizaron en las tablas. A continuación, las aplicaciones usan las funciones de seguimiento de cambios para determinar qué filas han cambiado y para obtener información sobre los cambios. Las principales ventajas del seguimiento de cambios de SQL Server son las siguientes:

  • En escenarios de sincronización sin conexión que utilizan Sync Framework, no tiene que crear desencadenadores, columnas de marca de tiempo, otras columnas adicionales o tablas adicionales.

  • El seguimiento de cambios se realiza en el momento de la confirmación, no cuando tienen lugar las operaciones DML.

  • Las funciones devuelven los cambios incrementales que se han producido en las tablas e información sobre la versión. Estas funciones proporcionan resultados fiables y fáciles de consumir, incluso cuando existen transacciones sin confirmar que se superponen.

  • La sobrecarga de rendimiento es mínima.

  • Los datos del seguimiento de cambios se pueden limpiar automáticamente.

El resto de este tema muestra la forma de utilizar el seguimiento de cambios de SQL Server en una aplicación de Sync Framework. Para obtener más información acerca del seguimiento de cambios, vea los Libros en pantalla de SQL Server 2008.

Usar el seguimiento de cambios de SQL Server con proveedores de bases de datos sin conexión de Sync Framework

En esta sección del tema se describe cómo habilitar el seguimiento de cambios y cómo se usan las consultas de seguimiento de cambios para determinar qué datos deben descargarse en un cliente. La información de esta sección describe cómo se utilizan los comandos creados manualmente para seleccionar cambios del servidor. Para obtener información sobre cómo usar el generador del adaptador de sincronización para crear comandos automáticamente, vea Introducción: sincronización de cliente y servidor.

Habilitar el seguimiento de cambios de SQL Server

El seguimiento de cambios se habilita en la base de datos servidor y después para cada tabla que requiere seguimiento. Los ejemplos de código siguientes muestran el esquema para la tabla Sales.Customer en una de las bases de datos de ejemplo de Sync Framework y el código que se requiere para habilitar el seguimiento de cambios para esa tabla. Cada tabla debe tener una clave principal. Las claves principales deben ser únicas en todos los nodos, y no deben reutilizarse: si una fila se elimina, su clave principal no debe utilizarse para otra fila. Las columnas de identidad no son normalmente una opción adecuada para los entornos distribuidos. Para obtener más información acerca de las claves principales, vea Seleccionar una clave principal apropiada para un entorno distribuido.

Las opciones de seguimiento de cambios que se especifican ejecutando el código siguiente incluyen el tiempo que se conservan los metadatos de seguimiento y si esos metadatos se limpian automáticamente. Para obtener más información sobre opciones de seguimiento, vea los temas "Seguimiento de cambios", "ALTER DATABASE" y "ALTER TABLE" en los Libros en pantalla de SQL Server 2008.

CREATE TABLE SyncSamplesDb_ChangeTracking.Sales.Customer(
    CustomerId uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(), 
    CustomerName nvarchar(100) NOT NULL,
    SalesPerson nvarchar(100) NOT NULL,
    CustomerType nvarchar(100) NOT NULL)
ALTER DATABASE SyncSamplesDb_ChangeTracking SET ALLOW_SNAPSHOT_ISOLATION ON

ALTER DATABASE SyncSamplesDb_ChangeTracking
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON)
ALTER TABLE SyncSamplesDb_ChangeTracking.Sales.Customer
ENABLE CHANGE_TRACKING

Nota

Se recomienda encarecidamente el uso de transacciones de instantáneas cuando se consulta información de cambios. Esto ayuda a garantizar la coherencia de la información de cambios y evita las condiciones de carrera relacionadas con la tarea de limpieza en segundo plano. Para obtener más información sobre el aislamiento de instantáneas, vea "Niveles de aislamiento en el motor de base de datos" en los Libros en pantalla de SQL Server 2008.

Determinar qué cambios de datos se van a descargar en un cliente

Una vez habilitado el seguimiento de cambios, las aplicaciones Sync Framework usan funciones del seguimiento de cambios y delimitadores para determinar qué inserciones, actualizaciones y eliminaciones se van a descargar. Un delimitador es sencillamente un instante de tiempo que permite definir un conjunto de cambios que se van a sincronizar. Considere las consultas siguientes:

  • La consulta especificada para la propiedad SelectIncrementalInsertsCommand. La consulta siguiente selecciona las inserciones incrementales de la tabla Sales.Customer del servidor que se van a aplicar al cliente:

    IF @sync_initialized = 0
      SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] 
      FROM Sales.Customer LEFT OUTER JOIN 
      CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
      ON CT.[CustomerId] = Sales.Customer.[CustomerId]
    ELSE
    BEGIN
      SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType]
      FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
      ON CT.[CustomerId] = Sales.Customer.[CustomerId]
      WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION 
      <= @sync_new_received_anchor)
    END
    

    Si ésta es la primera sesión de sincronización para un cliente (@sync_initialized = 0), el esquema y todas las filas se seleccionan directamente en la tabla base Sales.Customer. Durante sincronizaciones posteriores, las filas insertadas recientemente se seleccionan realizando una combinación interna entre la tabla base y su tabla de seguimiento de cambios. Los metadatos de la tabla de seguimiento de cambios los expone la función CHANGETABLE(). Esta función toma como parámetros el nombre de la tabla base y la versión del seguimiento de cambios almacenada de la sincronización anterior. La columna SYS_CHANGE_OPERATION define el tipo de cambio que está almacenado en una fila de la tabla de seguimiento de cambios.

    Nota

    Las consultas también deben comprobar si los cambios necesarios se han limpiado de las tablas de seguimiento. Para obtener un ejemplo, vea "Especificar un comando para seleccionar inserciones incrementales del servidor y aplicarlas al cliente" más adelante en este tema.

  • La consulta especificada para la propiedad SelectNewAnchorCommand. Esta consulta recupera un valor de momento dado. La consulta siguiente recupera un nuevo valor de delimitador del servidor utilizando la función change_tracking_current_version(). Esta función de SQL Server integrada devuelve un número entero de versión que está asociado a la última transacción confirmada que fue objeto de seguimiento de cambios.

    SELECT @sync_new_received_anchor = change_tracking_current_version()
    

    El valor entero se almacena en la base de datos cliente y lo utilizan los comandos que sincronizan los cambios. Durante cada sesión de sincronización, se utilizan el nuevo y el último valor de delimitador de la sesión de sincronización anterior: esto representa el conjunto de cambios entre estos límites superior e inferior que se sincroniza.

En algunos casos, una aplicación requiere solo un subconjunto de los datos en cada cliente. Puede incluir condiciones adicionales en la cláusula WHERE para filtrar datos. Para obtener más información, vea Filtrar filas y columnas. La sección "Filtros basados en columnas sin clave" incluye información relevante sobre el filtro con el seguimiento de cambios de SQL Server.

Consultas que se ejecutan durante el proceso de sincronización

La primera vez que se sincroniza la tabla Sales.Customer, se realiza el proceso siguiente:

  1. Se ejecuta el comando del nuevo delimitador. El comando devuelve un valor entero, como 372. Este valor se almacena en la base de datos cliente. La tabla nunca se ha sincronizado. Por lo tanto, la base de datos cliente no tiene almacenado un valor de delimitador procedente de una sincronización anterior. En este caso, Sync Framework usa un valor de 0. La consulta que ejecuta Sync Framework es la siguiente:

    exec sp_executesql N'IF @sync_initialized = 0 SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer LEFT OUTER JOIN 
    CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT 
    ON CT.[CustomerId] = Sales.Customer.[CustomerId] ELSE  BEGIN SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer JOIN CHANGETABLE(CHANGES 
    Sales.Customer, @sync_last_received_anchor) CT ON CT.[CustomerId] = 
    Sales.Customer.[CustomerId] WHERE (CT.SYS_CHANGE_OPERATION = ''I'' 
    AND CT.SYS_CHANGE_CREATION_VERSION <= @sync_new_received_anchor) 
    END', N'@sync_initialized int, @sync_last_received_anchor bigint, 
    @sync_new_received_anchor bigint', @sync_initialized=0, 
    @sync_last_received_anchor=0, @sync_new_received_anchor=372
    
  2. Durante la segunda sesión de sincronización, se ejecuta el comando del nuevo delimitador. Se han insertado filas desde la última sesión. Por consiguiente, el comando devuelve el valor 375. La tabla se ha sincronizado antes. Por tanto, Sync Framework puede recuperar el valor de delimitador de 372 que estaba almacenado en la base de datos cliente desde la sincronización anterior. La consulta que se ejecuta es la siguiente: La consulta solo descarga las filas de la tabla que se insertaron entre los dos valores de delimitador.

    exec sp_executesql N'IF @sync_initialized = 0 SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer LEFT OUTER JOIN 
    CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT 
    ON CT.[CustomerId] = Sales.Customer.[CustomerId] ELSE  BEGIN SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer JOIN CHANGETABLE(CHANGES 
    Sales.Customer, @sync_last_received_anchor) CT ON CT.[CustomerId] = 
    Sales.Customer.[CustomerId] WHERE (CT.SYS_CHANGE_OPERATION = ''I'' 
    AND CT.SYS_CHANGE_CREATION_VERSION <= @sync_new_received_anchor) 
    END', N'@sync_initialized int, @sync_last_received_anchor bigint, 
    @sync_new_received_anchor bigint', @sync_initialized=1, 
    @sync_last_received_anchor=372, @sync_new_received_anchor=375
    

Para obtener ejemplos de comandos de actualización y eliminación, vea el ejemplo de código completo más adelante en este tema.

Identificar el cliente que realiza el cambio de datos

Existen dos motivos principales para identificar al cliente que realiza cambios de datos:

  • Admitir la detección y resolución de conflictos en una sincronización bidireccional y de sólo carga.

    Cuando el servidor y uno o más clientes pueden cambiar una determinada fila, es posible que desee identificar quién efectúa el cambio. Esta información permite, por ejemplo, escribir un código que dé prioridad a un cambio sobre otro. Sin esta información, se conservaría el último cambio efectuado en la fila.

  • Impedir mostrar los cambios al cliente durante la sincronización bidireccional.

    Sync Framework transmite en primer lugar los cambios al servidor y, a continuación, los descarga en el cliente. Si no lleva un seguimiento de la identidad del cliente que efectúa un cambio, el cambio se cargará en el servidor y seguidamente en el cliente durante la misma sesión de sincronización. En algunas situaciones, esta repetición de los cambios es necesaria, pero en otras no.

El seguimiento de cambios proporciona un mecanismo para almacenar los datos de la aplicación junto con la información de cambios cuando se cambian filas. Estos datos de la aplicación se pueden utilizar para identificar el cliente que está realizando un cambio. La identidad del cliente que realizó un cambio se puede devolver después al consultar los cambios.

La columna SYS_CHANGE_CONTEXT se puede usar con la propiedad ClientId para determinar qué cliente realizó cada inserción, actualización o eliminación. La primera vez que se sincroniza una tabla con un método distinto a la sincronización de instantáneas, Sync Framework almacena un valor GUID en el cliente con el que identifica a dicho cliente. Este identificador se pasa a DbServerSyncProvider para que pueda ser usado por los comandos para cada objeto SyncAdapter. El valor del identificador está disponible a través de la propiedad ClientId, y las variables de sesión @sync_client_id_binary y @sync_client_id. Considere la consulta Transact-SQL siguiente:

IF @sync_initialized = 0
  SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] 
  FROM Sales.Customer LEFT OUTER JOIN 
  CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
  ON CT.[CustomerId] = Sales.Customer.[CustomerId]
  WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)
ELSE
BEGIN
  SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType]
  FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
  ON CT.[CustomerId] = Sales.Customer.[CustomerId]
  WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION 
<= @sync_new_received_anchor 
  AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary));

Esta consulta es parecida a la que realiza seguimientos de las inserciones efectuadas en el servidor. La instrucción adicional en cada cláusula WHERE asegura que las únicas inserciones que se descargan son las que no fueron efectuadas por el cliente que se está sincronizando en este momento. Sync Framework también permite a las aplicaciones identificar clientes en el servidor usando un número entero en lugar de un valor GUID. Para obtener más información, vea Usar variables de sesión.

Para realizar el seguimiento del cliente que efectúa un cambio de datos que se aplica en el servidor, utilice la cláusula WITH CHANGE_TRACKING_CONTEXT. Antes de ejecutar una instrucción INSERT, DELETE o UPDATE, establezca CHANGE_TRACKING_CONTEXT en el valor de la variable de sesión @sync_client_id_binary o @sync_client_id. Esta información se almacena en la tabla de seguimiento de cambios para que las aplicaciones puedan realizar el seguimiento del contexto en el que se realizó un cambio. En Sync Framework, suele tratarse del identificador de cliente. Sin embargo, se puede almacenar cualquier valor que se ajuste a una columna varbinary(128).

WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary)
INSERT INTO Sales.Customer (CustomerId, CustomerName, SalesPerson,
 CustomerType)
VALUES (@CustomerId, @CustomerName, @SalesPerson, @CustomerType)
SET @sync_row_count = @@rowcount

Descripción y ejecución de la aplicación de ejemplo

Esta sección del tema incluye el código de aplicación necesario para configurar y realizar la sincronización. Puede aprender sencillamente leyendo el código de ejemplo. No obstante, es más instructivo ejecutarlo y verlo en acción. Antes de ejecutar el código, asegúrese de instalar los siguientes componentes:

  • Sync Framework

    La aplicación requiere referencias a Microsoft.Synchronization.Data.dll, Microsoft.Synchronization.dll, Microsoft.Synchronization.Data.Server.dll y Microsoft.Synchronization.Data.SqlServerCe.dll.

  • SQL Server 2008

    En el código de ejemplo se usa localhost en las cadenas de conexión. Para usar un servidor remoto, cambie localhost por el nombre de servidor adecuado.

  • Las bases de datos de ejemplo de Sync Framework. Para obtener más información, vea Scripts de configuración para los temas de procedimientos del proveedor de base de datos.

Si ha leído el tema Arquitectura y clases para la sincronización del cliente y el servidor, ya debe conocer las clases principales que se usan en la aplicación. La aplicación se compone de las clases siguientes.

  • SampleSyncAgent. Esta clase se deriva de SyncAgent.

  • SampleServerSyncProvider. Esta clase se deriva de DbServerSyncProvider y contiene SyncAdapter y un conjunto de comandos que consultan las tablas de seguimiento de cambios.

  • SampleClientSyncProvider. Esta clase se deriva de SqlCeClientSyncProvider y contiene SyncTable.

  • SampleStats. Esta clase usa las estadísticas devueltas por SyncAgent.

  • Program. Esta clase configura la sincronización y llama a métodos de la clase Utility.

  • Utility. Esta clase controla toda la funcionalidad que no está relacionada directamente con la sincronización, como conservar la información de las cadenas de conexión y efectuar cambios en las bases de datos cliente y servidor. Para obtener más información, vea Clase de utilidad para los temas de procedimientos del proveedor de bases de datos.

Partes principales de la API

Antes de examinar el ejemplo de código completo, se recomienda revisar los ejemplos siguientes. En estos ejemplos se muestran varias secciones fundamentales de la API que se utilizan en esta aplicación. Todo el código de ejemplo que se muestra está contenido en la clase SampleServerSyncProvider. Además de los comandos que se muestran en esta sección, el ejemplo de código completo también contiene un comando para aplicar inserciones al servidor y comandos para seleccionar y aplicar eliminaciones.

El primer ejemplo se aplica directamente a DbServerSyncProvider de la propiedad SelectNewAnchorCommand. Los otros ejemplos se aplican al objeto SyncAdapter para la tabla Sales.Customer.

Recuperar un nuevo valor de delimitador del servidor

El ejemplo de código siguiente especifica el comando que recupera un nuevo valor de delimitador del servidor. La clase SyncSession contiene varias constantes de cadena que pueden usarse en los comandos de sincronización. SyncNewReceivedAnchor es una de ellas. Si lo desea, puede usar el literal @sync_new_received_anchor directamente en las consultas.

SqlCommand selectNewAnchorCommand = new SqlCommand();
string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
selectNewAnchorCommand.CommandText =
    "SELECT " + newAnchorVariable + " = change_tracking_current_version()";
selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.BigInt);
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 + " = change_tracking_current_version()"
    .Parameters.Add(newAnchorVariable, SqlDbType.BigInt)
    .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
    .Connection = serverConn
End With
Me.SelectNewAnchorCommand = selectNewAnchorCommand

Especificar un comando para seleccionar inserciones incrementales del servidor y aplicarlas al cliente

El ejemplo de código siguiente especifica un comando que selecciona inserciones incrementales en el servidor y las aplica al cliente. Todas las consultas para los cambios incrementales comprueban si los cambios necesarios se han limpiado de la tabla de seguimiento de cambios. Esta comprobación empieza con la cláusula siguiente y produce un error si los cambios se han limpiado:

IF CHANGE_TRACKING_MIN_VALID_VERSION (object_id (@sync_table_name)) > @sync_last_received_anchor

SqlCommand customerIncrInserts = new SqlCommand();
customerIncrInserts.CommandText =
    "IF @sync_initialized = 0 " +
        "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
        "FROM Sales.Customer LEFT OUTER JOIN " +
        "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
        "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
        "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " +
    "ELSE  " +
    "BEGIN " +
        "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
        "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
        "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
        "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " +
        "<= @sync_new_received_anchor " +
        "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
        "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
        "> @sync_last_received_anchor " +
        "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
        "To recover from this error, the client must reinitialize its local database and try again' " +
        ",16,3,@sync_table_name)  " +
    "END";
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
customerIncrInserts.Connection = serverConn;
customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts;
Dim customerIncrInserts As New SqlCommand()
With customerIncrInserts
    .CommandText = _
        "IF @sync_initialized = 0 " _
            & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
            & "FROM Sales.Customer LEFT OUTER JOIN " _
            & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
            & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
            & "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " _
        & "ELSE  " _
        & "BEGIN " _
            & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
            & "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
            & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
            & "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " _
            & "<= @sync_new_received_anchor " _
            & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
            & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
            & "> @sync_last_received_anchor " _
            & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
            & "To recover from this error, the client must reinitialize its local database and try again' " _
            & ",16,3,@sync_table_name)  " _
        & "END"
    .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
    .Connection = serverConn
End With
customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts

Especificar un comando para seleccionar actualizaciones incrementales del servidor y aplicarlas al cliente

El ejemplo de código siguiente especifica un comando que selecciona actualizaciones incrementales en el servidor y las aplica al cliente.

SqlCommand customerIncrUpdates = new SqlCommand();
customerIncrUpdates.CommandText =
    "IF @sync_initialized > 0  " +
    "BEGIN " +
        "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
        "FROM Sales.Customer JOIN " +
        "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
        "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
        "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " +
        "<= @sync_new_received_anchor " +
        "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
        "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
        "> @sync_last_received_anchor " +
        "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
        "To recover from this error, the client must reinitialize its local database and try again'" +
        ",16,3,@sync_table_name)  " +
    "END";
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
customerIncrUpdates.Connection = serverConn;
customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates;
Dim customerIncrUpdates As New SqlCommand()
With customerIncrUpdates
    .CommandText = _
          "IF @sync_initialized > 0  " _
        & "BEGIN " _
            & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
            & "FROM Sales.Customer JOIN " _
            & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
            & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
            & "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " _
            & "<= @sync_new_received_anchor " _
            & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
            & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
            & "> @sync_last_received_anchor " _
            & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
            & "To recover from this error, the client must reinitialize its local database and try again'" _
            & ",16,3,@sync_table_name)  " _
        & "END"
    .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
    .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
    .Connection = serverConn
End With
customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates

Especificar un comando para aplicar actualizaciones incrementales del cliente al servidor

En el ejemplo de código siguiente, la instrucción UPDATE actualiza la tabla base y devuelve un recuento de las filas afectadas. Si el recuento de filas es 0, significa que se ha producido un error o un conflicto. Para obtener más información, vea Controlar conflictos de datos y errores.

SqlCommand customerUpdates = new SqlCommand();
customerUpdates.CommandText =
    ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
    "UPDATE Sales.Customer " +
    "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " +
    "FROM Sales.Customer  " +
    "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
    "WHERE (@sync_force_write = 1 " +
    "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " +
    "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " +
    "SET @sync_row_count = @@rowcount; " +
    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
        "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
        "To recover from this error, the client must reinitialize its local database and try again'" +
        ",16,3,@sync_table_name)";
customerUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary); 
customerUpdates.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
customerUpdates.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
customerUpdates.Parameters.Add("@CustomerType", SqlDbType.NVarChar);            
customerUpdates.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
customerUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
customerUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
customerUpdates.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
customerUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
customerUpdates.Connection = serverConn;
customerSyncAdapter.UpdateCommand = customerUpdates;
Dim customerUpdates As New SqlCommand()
With customerUpdates
    .CommandText = _
          ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
        & "UPDATE Sales.Customer " _
        & "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " _
        & "FROM Sales.Customer  " _
        & "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
        & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
        & "WHERE (@sync_force_write = 1 " _
        & "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " _
        & "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " _
        & "SET @sync_row_count = @@rowcount; " _
        & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
            & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
            & "To recover from this error, the client must reinitialize its local database and try again'" _
            & ",16,3,@sync_table_name)"
    .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
    .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
    .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
    .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
    .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
    .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
    .Connection = serverConn
End With
customerSyncAdapter.UpdateCommand = customerUpdates

Seleccionar filas conflictivas

El comando siguiente selecciona las filas conflictivas de la base de datos servidor si dichas filas siguen existiendo en la tabla base.

SqlCommand customerUpdateConflicts = new SqlCommand();
customerUpdateConflicts.CommandText =
    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " +
    "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
    "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
    "ON CT.[CustomerId] = Sales.Customer.[CustomerId]";
customerUpdateConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
customerUpdateConflicts.Connection = serverConn;
customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts;
Dim customerUpdateConflicts As New SqlCommand()
With customerUpdateConflicts
    .CommandText = _
          "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " _
        & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
        & "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
        & "ON CT.[CustomerId] = Sales.Customer.[CustomerId]"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Connection = serverConn
End With
customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts

El comando siguiente selecciona las filas conflictivas de la base de datos servidor si dichas filas se han eliminado de la tabla base.

SqlCommand customerDeleteConflicts = new SqlCommand();
customerDeleteConflicts.CommandText =
    "SELECT CT.[CustomerId], " +
    "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
    "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
    "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')";
customerDeleteConflicts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerDeleteConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
customerDeleteConflicts.Connection = serverConn;
customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts;
Dim customerDeleteConflicts As New SqlCommand()
With customerDeleteConflicts
    .CommandText = _
          "SELECT CT.[CustomerId], " _
        & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
        & "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
        & "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')"
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Connection = serverConn
End With
customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts

Para obtener más información sobre cómo controlar conflictos de datos, vea Controlar conflictos de datos y errores.

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.

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);

            //Specify which server and database to connect to.
            Utility.SetServerAndDb_DbServerSync("localhost", "SyncSamplesDb_ChangeTracking");
            
            //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("Customer");
            Utility.MakeDataChangesOnClient("Customer");          

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

            //Make conflicting changes on the server and client.
            Utility.MakeConflictingChangesOnClientAndServer();

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

            //Return 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 Customer table: specify a synchronization direction of
            //Bidirectional, and that an existing table should be dropped.
            SyncTable customerSyncTable = new SyncTable("Customer");
            customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            customerSyncTable.SyncDirection = SyncDirection.Bidirectional;
            this.Configuration.SyncTables.Add(customerSyncTable);
        }
    }

    //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 BigInt value
            //from the change tracking table.
            //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 + " = change_tracking_current_version()";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.BigInt);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;
            
            //Create a SyncAdapter for the Customer 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.
            //* SelectConflictUpdatedRowsCommand SelectConflictDeletedRowsCommand
            //  are used to detect if there are conflicts on the server during
            //  synchronization.
            //The commands reference the change tracking table that is configured
            //for the Customer table.

            //Create the SyncAdapter.
            SyncAdapter customerSyncAdapter = new SyncAdapter("Customer");            
            
            //Select inserts from the server.
            SqlCommand customerIncrInserts = new SqlCommand();
            customerIncrInserts.CommandText =
                "IF @sync_initialized = 0 " +
                    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
                    "FROM Sales.Customer LEFT OUTER JOIN " +
                    "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                    "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " +
                "ELSE  " +
                "BEGIN " +
                    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
                    "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                    "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " +
                    "<= @sync_new_received_anchor " +
                    "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
                    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
                    "> @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again' " +
                    ",16,3,@sync_table_name)  " +
                "END";
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerIncrInserts.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts;

            //Apply inserts to the server.
            SqlCommand customerInserts = new SqlCommand();
            customerInserts.CommandText =
                ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
                "INSERT INTO Sales.Customer ([CustomerId], [CustomerName], [SalesPerson], [CustomerType]) " +
                "VALUES (@CustomerId, @CustomerName, @SalesPerson, @CustomerType) " +
                "SET @sync_row_count = @@rowcount; " +
                "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)";
            customerInserts.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerInserts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerInserts.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            customerInserts.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
            customerInserts.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerInserts.Connection = serverConn;
            customerSyncAdapter.InsertCommand = customerInserts;
                                    
            //Select updates from the server.
            SqlCommand customerIncrUpdates = new SqlCommand();
            customerIncrUpdates.CommandText =
                "IF @sync_initialized > 0  " +
                "BEGIN " +
                    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
                    "FROM Sales.Customer JOIN " +
                    "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                    "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " +
                    "<= @sync_new_received_anchor " +
                    "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
                    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
                    "> @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)  " +
                "END";
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerIncrUpdates.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates;
            
            //Apply updates to the server.
            SqlCommand customerUpdates = new SqlCommand();
            customerUpdates.CommandText =
                ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
                "UPDATE Sales.Customer " +
                "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " +
                "FROM Sales.Customer  " +
                "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
                "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                "WHERE (@sync_force_write = 1 " +
                "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " +
                "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " +
                "SET @sync_row_count = @@rowcount; " +
                "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)";
            customerUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary); 
            customerUpdates.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
            customerUpdates.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            customerUpdates.Parameters.Add("@CustomerType", SqlDbType.NVarChar);            
            customerUpdates.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            customerUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            customerUpdates.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
            customerUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerUpdates.Connection = serverConn;
            customerSyncAdapter.UpdateCommand = customerUpdates;

            //Select deletes from the server.
            SqlCommand customerIncrDeletes = new SqlCommand();
            customerIncrDeletes.CommandText =
                "IF @sync_initialized > 0  " +
                "BEGIN " +
                    "SELECT CT.[CustomerId] FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "WHERE (CT.SYS_CHANGE_OPERATION = 'D' AND CT.SYS_CHANGE_VERSION " +
                    "<= @sync_new_received_anchor " +
                    "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
                    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
                    "> @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)  " +
                "END";
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerIncrDeletes.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalDeletesCommand = customerIncrDeletes;

            //Apply deletes to the server.            
            SqlCommand customerDeletes = new SqlCommand();
            customerDeletes.CommandText =
                ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
                "DELETE Sales.Customer FROM Sales.Customer " +
                "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
                "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                "WHERE (@sync_force_write = 1 " +
                "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " +
                "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " +
                "SET @sync_row_count = @@rowcount; " +
                "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)";
            customerDeletes.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerDeletes.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerDeletes.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            customerDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);           
            customerDeletes.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            customerDeletes.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
            customerDeletes.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerDeletes.Connection = serverConn;
            customerSyncAdapter.DeleteCommand = customerDeletes;
           
            //This command is used if @sync_row_count returns
            //0 when changes are applied to the server.
            SqlCommand customerUpdateConflicts = new SqlCommand();
            customerUpdateConflicts.CommandText =
                "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " +
                "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
                "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
                "ON CT.[CustomerId] = Sales.Customer.[CustomerId]";
            customerUpdateConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerUpdateConflicts.Connection = serverConn;
            customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts;

            //This command is used if the server provider cannot find
            //a row in the base table.
            SqlCommand customerDeleteConflicts = new SqlCommand();
            customerDeleteConflicts.CommandText =
                "SELECT CT.[CustomerId], " +
                "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
                "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')";
            customerDeleteConflicts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerDeleteConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerDeleteConflicts.Connection = serverConn;
            customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts;


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

        }
    }

    //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["Customer"].Columns["CustomerId"].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)

        'Specify which server and database to connect to.
        Utility.SetServerAndDb_DbServerSync("localhost", "SyncSamplesDb_ChangeTracking")

        '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("Customer")
        Utility.MakeDataChangesOnClient("Customer")

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

        'Make conflicting changes on the server and client.
        Utility.MakeConflictingChangesOnClientAndServer()

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

        'Return 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 Customer table: specify a synchronization direction of
        'Bidirectional, and that an existing table should be dropped.
        Dim customerSyncTable As New SyncTable("Customer")
        customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
        customerSyncTable.SyncDirection = SyncDirection.Bidirectional
        Me.Configuration.SyncTables.Add(customerSyncTable)

    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 BigInt value
        'from the change tracking table.
        '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 + " = change_tracking_current_version()"
            .Parameters.Add(newAnchorVariable, SqlDbType.BigInt)
            .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
            .Connection = serverConn
        End With
        Me.SelectNewAnchorCommand = selectNewAnchorCommand

        'Create a SyncAdapter for the Customer 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.
        '* SelectConflictUpdatedRowsCommand SelectConflictDeletedRowsCommand
        '  are used to detect if there are conflicts on the server during
        '  synchronization.
        'The commands reference the change tracking table that is configured
        'for the Customer table.
        'Create the SyncAdapter.
        Dim customerSyncAdapter As New SyncAdapter("Customer")

        'Select inserts from the server.
        Dim customerIncrInserts As New SqlCommand()
        With customerIncrInserts
            .CommandText = _
                "IF @sync_initialized = 0 " _
                    & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
                    & "FROM Sales.Customer LEFT OUTER JOIN " _
                    & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                    & "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " _
                & "ELSE  " _
                & "BEGIN " _
                    & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
                    & "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                    & "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " _
                    & "<= @sync_new_received_anchor " _
                    & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
                    & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
                    & "> @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again' " _
                    & ",16,3,@sync_table_name)  " _
                & "END"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts
        'Apply inserts to the server.
        Dim customerInserts As New SqlCommand()
        With customerInserts
            .CommandText = _
                  ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
                & "INSERT INTO Sales.Customer ([CustomerId], [CustomerName], [SalesPerson], [CustomerType]) " _
                & "VALUES (@CustomerId, @CustomerName, @SalesPerson, @CustomerType) " _
                & "SET @sync_row_count = @@rowcount; " _
                & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)"
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
            .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
            .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Connection = serverConn
        End With
        customerSyncAdapter.InsertCommand = customerInserts

        'Select updates from the server.
        Dim customerIncrUpdates As New SqlCommand()
        With customerIncrUpdates
            .CommandText = _
                  "IF @sync_initialized > 0  " _
                & "BEGIN " _
                    & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
                    & "FROM Sales.Customer JOIN " _
                    & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                    & "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " _
                    & "<= @sync_new_received_anchor " _
                    & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
                    & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
                    & "> @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)  " _
                & "END"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates
        'Apply updates to the server.
        Dim customerUpdates As New SqlCommand()
        With customerUpdates
            .CommandText = _
                  ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
                & "UPDATE Sales.Customer " _
                & "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " _
                & "FROM Sales.Customer  " _
                & "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
                & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                & "WHERE (@sync_force_write = 1 " _
                & "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " _
                & "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " _
                & "SET @sync_row_count = @@rowcount; " _
                & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)"
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
            .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
            .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.UpdateCommand = customerUpdates
        'Select deletes from the server.
        Dim customerIncrDeletes As New SqlCommand()
        With customerIncrDeletes
            .CommandText = _
                  "IF @sync_initialized > 0  " _
                & "BEGIN " _
                    & "SELECT CT.[CustomerId] FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "WHERE (CT.SYS_CHANGE_OPERATION = 'D' AND CT.SYS_CHANGE_VERSION " _
                    & "<= @sync_new_received_anchor " _
                    & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
                    & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
                    & "> @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)  " _
                & "END"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectIncrementalDeletesCommand = customerIncrDeletes

        'Apply deletes to the server.            
        Dim customerDeletes As New SqlCommand()
        With customerDeletes
            .CommandText = _
                  ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
                & "DELETE Sales.Customer FROM Sales.Customer " _
                & "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
                & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                & "WHERE (@sync_force_write = 1 " _
                & "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " _
                & "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " _
                & "SET @sync_row_count = @@rowcount; " _
                & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)"
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.DeleteCommand = customerDeletes

        'This command is used if @sync_row_count returns
        '0 when changes are applied to the server.
        Dim customerUpdateConflicts As New SqlCommand()
        With customerUpdateConflicts
            .CommandText = _
                  "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " _
                & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
                & "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
                & "ON CT.[CustomerId] = Sales.Customer.[CustomerId]"
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts

        'This command is used if the server provider cannot find
        'a row in the base table.
        Dim customerDeleteConflicts As New SqlCommand()
        With customerDeleteConflicts
            .CommandText = _
                  "SELECT CT.[CustomerId], " _
                & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
                & "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                & "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')"
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts

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

    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("Customer").Columns("CustomerId").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

Vea también

Conceptos

Seguimiento de cambios en la base de datos servidor