Cómo filtrar filas y columnas

En este tema se muestra cómo filtrar las filas y columnas de las tablas sincronizadas. Los ejemplos de este tema se centran en los siguientes tipos de Sync Services for ADO.NET:

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.

Descripción del filtrado

Muchas veces la base de datos cliente sólo necesita un subconjunto de los datos disponibles en el servidor. Sync Services permite especificar las filas y columnas que requiere un cliente, y también si los comandos de sincronización se crean manualmente o mediante el uso de SqlSyncAdapterBuilder. El filtrado de los datos permite hacer lo siguiente:

  • Reducir el volumen de datos enviados a través de la red.

  • Reducir la cantidad de espacio necesario para el almacenamiento en el cliente.

  • Proporcionar particiones de datos personalizadas, basadas en los requisitos individuales del cliente.

  • Evitar o disminuir los conflictos si los clientes actualizan los datos, puesto que podrían enviarse particiones de datos diferentes a clientes distintos (cada cliente actualiza valores de datos diferentes).

Los filtros pueden estar basados en una sola tabla o pueden hacer referencia a varias tablas, por medio de varias cláusulas JOIN o instrucciones SELECT. Basar los filtros en varias tablas permite al usuario descargar una partición de datos, por ejemplo, una vendedora que sólo requiera los datos de sus clientes y todos los pedidos de los clientes. Usar varias tablas para los filtros proporciona más flexibilidad. No obstante, los filtros deben ser tan sencillos como sea posible, y se debe probar el rendimiento si se aumenta el número de tablas. También se deben indizar las columnas en las que estén basados los filtros.

NoteImportante

La seguridad no debe depender del filtrado. La capacidad para filtrar datos del servidor que están basados en el identificador del cliente o del usuario no es una característica de seguridad. Es decir, este enfoque no ayuda a evitar que un cliente lea los datos pertenecientes a otro. Este tipo de filtrado sólo resulta útil para realizar particiones de datos y para disminuir la cantidad de datos que se pasan a la base de datos cliente.

Sync Services no proporciona una administración automática de particiones, lo cual tiene las consecuencias siguientes:

  • Si actualiza una fila y cambia el valor de una columna utilizada en el filtro, la fila no se eliminará automáticamente de los clientes en cuya partición esté incluida. Considere una aplicación que descarga a un vendedor los datos de los clientes en función del código postal. Si un cliente traslada su oficina a una zona con otro código postal, sus datos no se eliminarán de la base de datos del vendedor que los tenía originalmente. Si se requiere esa funcionalidad, puede desarrollar un sistema que permita a Sync Services descargar la actualización como una eliminación.

  • No existe ningún mecanismo que impida a una aplicación insertar datos en el cliente que estén fuera de la partición del cliente. Puede agregar restricciones al cliente, para impedir las inserciones y actualizaciones fuera de la partición.

Filtros basados en columnas sin clave

En el seguimiento de cambios de SQL Server y en algunos sistemas de seguimiento personalizados, solo se conserva la clave principal para las filas eliminadas. Si un filtro está basado únicamente en la clave principal, la consulta que se especifique para la propiedad SelectIncrementalDeletesCommand puede identificar el subconjunto correcto de filas y descargarlas al cliente. Si el filtro está basado en columnas sin clave principal, se produce un error en la consulta porque hace referencia a columnas que ya no existen para las filas eliminadas. Para resolver este problema, considere la posibilidad de usar uno de los enfoques siguientes:

  • Incluya todas las columnas de filtrado en la clave principal. Coloque las columnas adicionales al final de la clave para no influir en la selectividad de la clave.

  • Filtre únicamente inserciones y actualizaciones. Las eliminaciones superfluas se descargarán al cliente, pero se omitirán.

  • Realice eliminaciones lógicas en el servidor. En lugar de eliminar la fila, utilice un desencadenador ON DELETE para actualizar una columna que marque la fila como eliminada o archivada. El cambio se envía después al cliente como una actualización.

  • Sobrecargue la columna de contexto en el seguimiento de cambios de SQL Server (SYS_CHANGE_CONTEXT) con valores adicionales que se puedan utilizar para filtrar datos. Esta es probablemente la mejor opción en lo que respecta al rendimiento, pero puede que sea la más compleja porque tiene que analizar esta columna.

Ejemplo

El ejemplo de código de este tema muestra cómo puede filtrar los datos de las tablas Customer, OrderHeader y OrderDetail de la base de datos de ejemplo Sync Services. La tabla Customer se filtra de forma que sólo se descarguen aquellas filas que tengan el valor Brenda Diaz en la columna SalesPerson. Después el filtro se aplica a las otras dos tablas. El ejemplo muestra cómo puede filtrar los datos mediante el SqlSyncAdapterBuilder y creando manualmente los comandos de sincronización. Para obtener información general sobre los comandos de sincronización, vea Cómo especificar una sincronización de instantáneas, de descarga, de carga y bidireccional.

Utilizar SqlSyncAdapterBuilder

En esta sección se explica la API que se utiliza para aplicar filtros si se crean comandos por medio de SqlSyncAdapterBuilder. Esta sección proporciona ejemplos de código que destacan las partes principales de la API y después proporciona un ejemplo de código completo.

Partes principales de la API

El ejemplo de código siguiente crea un parámetro de filtro que se incluye en la cláusula de filtro de las tres tablas.

SqlParameter filterParameter = new SqlParameter("@SalesPerson", SqlDbType.NVarChar);

En el ejemplo de código siguiente se especifica qué columnas se descargan para la tabla Customer.

string[] customerDataColumns = new string[3];
customerDataColumns[0] = "CustomerId";
customerDataColumns[1] = "CustomerName";
customerDataColumns[2] = "CustomerType";
customerBuilder.DataColumns.AddRange(customerDataColumns);
customerBuilder.TombstoneDataColumns.AddRange(customerDataColumns);

En el ejemplo de código siguiente se especifica qué filas se descargan para la tabla Customer. Puede codificar un valor para SalesPerson. No obstante, es más frecuente usar un parámetro con un valor que se pueda modificar, como se muestra en el ejemplo. Se utiliza el parámetro del primer ejemplo de código.

string customerFilterClause = "SalesPerson=@SalesPerson";
customerBuilder.FilterClause = customerFilterClause;
customerBuilder.FilterParameters.Add(filterParameter);
customerBuilder.TombstoneFilterClause = customerFilterClause;
customerBuilder.TombstoneFilterParameters.Add(filterParameter);

En el ejemplo de código siguiente se aplica el filtro de la tabla Customer a la tabla OrderHeader. En este caso, se utiliza una instrucción SELECT en la cláusula de filtro. Para los comandos manuales, se usa una cláusula JOIN, puesto que proporciona más control sobre la forma de especificar los comandos.

string orderHeaderFilterClause =
    "CustomerId IN (SELECT CustomerId FROM Sales.Customer " +
                        "WHERE SalesPerson=@SalesPerson)";
orderHeaderBuilder.FilterClause = orderHeaderFilterClause;   
orderHeaderBuilder.FilterParameters.Add(filterParameter);
orderHeaderBuilder.TombstoneFilterClause = orderHeaderFilterClause;
orderHeaderBuilder.TombstoneFilterParameters.Add(filterParameter);

El ejemplo de código siguiente especifica un valor para el parámetro @SalesPerson en una clase que se deriva de SyncAgent. En una aplicación, este valor podría obtenerse de un Id. de inicio de sesión u otra información especificada por el usuario.

this.Configuration.SyncParameters.Add(
    new SyncParameter("@SalesPerson", "Brenda Diaz"));

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. Al ejecutar el ejemplo, preste atención a la información que devuelve SyncStatistics: un subconjunto de filas se descarga tanto en la sincronización inicial como en las posteriores.

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

            //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 two SyncGroups so that changes to OrderHeader
            //and OrderDetail are made in one transaction. Depending on
            //application requirements, you might include Customer
            //in the same group.
            SyncGroup customerSyncGroup = new SyncGroup("Customer");
            SyncGroup orderSyncGroup = new SyncGroup("Order");

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

            SyncTable orderDetailSyncTable = new SyncTable("OrderDetail");
            orderDetailSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            orderDetailSyncTable.SyncDirection = SyncDirection.DownloadOnly;
            orderDetailSyncTable.SyncGroup = orderSyncGroup;
            this.Configuration.SyncTables.Add(orderDetailSyncTable);

            //Specify a value for the @SalesPerson parameter that is added
            //in the server synchronization provider. This value would
            //typically be provided by a user in the application, but we
            //have hardcoded it here for convenience.
            this.Configuration.SyncParameters.Add(
                new SyncParameter("@SalesPerson", "Brenda Diaz"));
        }
    }

    //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 use a timestamp value
            //that is retrieved and stored in the client database.
            //During each synchronization, the new anchor value and
            //the last anchor value from the previous synchronization
            //are used: the set of changes between these upper and
            //lower bounds is synchronized.
            //
            //SyncSession.SyncNewReceivedAnchor is a string constant; 
            //you could also use @sync_new_received_anchor directly in 
            //your queries.
            SqlCommand selectNewAnchorCommand = new SqlCommand();
            string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
            selectNewAnchorCommand.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;

            //Create a filter parameter that will be used in the filter clause for
            //all three tables.
            SqlParameter filterParameter = new SqlParameter("@SalesPerson", SqlDbType.NVarChar);

            //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
            //    changes are made.
            //  * Specify download-only synchronization.
            //  * Specify if you want only certain columns at the client.
            //  * Specify filter clauses for the base tables and tombstone
            //    tables.
            //  * Call ToSyncAdapter to create the SyncAdapter.
            //  * Specify a name for the SyncAdapter that matches the
            //    the name that is 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";
            
            //Specify the columns that you want at the client. If you
            //want all columns, this code is not required. In this
            //case, we filter out SalesPerson.
            string[] customerDataColumns = new string[3];
            customerDataColumns[0] = "CustomerId";
            customerDataColumns[1] = "CustomerName";
            customerDataColumns[2] = "CustomerType";
            customerBuilder.DataColumns.AddRange(customerDataColumns);
            customerBuilder.TombstoneDataColumns.AddRange(customerDataColumns);

            //Specify a filter clause, which is an SQL WHERE clause
            //without the WHERE keyword. Use the parameter that is 
            //created above. The value for the parameter is specified 
            //in the SyncAgent Configuration object.
            string customerFilterClause = "SalesPerson=@SalesPerson";
            customerBuilder.FilterClause = customerFilterClause;
            customerBuilder.FilterParameters.Add(filterParameter);
            customerBuilder.TombstoneFilterClause = customerFilterClause;
            customerBuilder.TombstoneFilterParameters.Add(filterParameter);
 
            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";
            //Filter properties: extend the filter to the OrderHeader table.
            string orderHeaderFilterClause =
                "CustomerId IN (SELECT CustomerId FROM Sales.Customer " +
                                    "WHERE SalesPerson=@SalesPerson)";
            orderHeaderBuilder.FilterClause = orderHeaderFilterClause;   
            orderHeaderBuilder.FilterParameters.Add(filterParameter);
            orderHeaderBuilder.TombstoneFilterClause = orderHeaderFilterClause;
            orderHeaderBuilder.TombstoneFilterParameters.Add(filterParameter);

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


            //OrderDetail table.
            SqlSyncAdapterBuilder orderDetailBuilder = new SqlSyncAdapterBuilder(serverConn);

            orderDetailBuilder.TableName = "Sales.OrderDetail";
            orderDetailBuilder.TombstoneTableName = orderDetailBuilder.TableName + "_Tombstone";
            orderDetailBuilder.SyncDirection = SyncDirection.DownloadOnly;
            orderDetailBuilder.CreationTrackingColumn = "InsertTimestamp";
            orderDetailBuilder.UpdateTrackingColumn = "UpdateTimestamp";
            orderDetailBuilder.DeletionTrackingColumn = "DeleteTimestamp";
            //Filter properties: extend the filter to the OrderDetail table.
            string orderDetailFilterClause =
                "OrderId IN (SELECT OrderId FROM Sales.OrderHeader " +
                                "WHERE CustomerId IN " +
                                    "(SELECT CustomerId FROM Sales.Customer " +
                                        "WHERE SalesPerson=@SalesPerson))";
            orderDetailBuilder.FilterClause = orderDetailFilterClause;      
            orderDetailBuilder.FilterParameters.Add(filterParameter);
            orderDetailBuilder.TombstoneFilterClause = orderDetailFilterClause;
            orderDetailBuilder.TombstoneFilterParameters.Add(filterParameter);

            SyncAdapter orderDetailSyncAdapter = orderDetailBuilder.ToSyncAdapter();
            orderDetailSyncAdapter.TableName = "OrderDetail";
            this.SyncAdapters.Add(orderDetailSyncAdapter);            
        }
    }

    //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 you could use this class to handle client 
    //provider events and other client-side processing.
    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 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 Downloaded: " + syncStatistics.TotalChangesDownloaded);            
            Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime);
            Console.WriteLine(String.Empty);
        }
    }
}

Usar comandos manuales

En esta sección se explica la API utilizada para aplicar filtros si los comandos se crean manualmente. Esta sección proporciona ejemplos de código que destacan las partes principales de la API y después proporciona un ejemplo de código completo.

Partes principales de la API

En el ejemplo de código siguiente se especifica qué columnas y filas insertadas se descargan para la tabla Customer. Puede codificar un valor para SalesPerson. No obstante, es más frecuente usar un parámetro con un valor que se pueda modificar, como se muestra en el ejemplo. El ejemplo pasa el parámetro de filtro, junto con los demás parámetros que se requieran para descargar las inserciones incrementales.

SqlCommand customerIncrInserts = new SqlCommand();
customerIncrInserts.CommandText =
    "SELECT CustomerId, CustomerName, CustomerType " +
    "FROM Sales.Customer " +
    "WHERE SalesPerson = @SalesPerson " +
    "AND (InsertTimestamp > @sync_last_received_anchor " +
    "AND InsertTimestamp <= @sync_new_received_anchor " +
    "AND InsertId <> @sync_client_id)";
customerIncrInserts.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
customerIncrInserts.Connection = serverConn;
customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts;

En el ejemplo de código siguiente se aplica el filtro de la tabla Customer a la tabla OrderHeader. En este caso, se usa una cláusula JOIN para definir la relación entre las dos tablas.

SqlCommand orderHeaderIncrInserts = new SqlCommand();
orderHeaderIncrInserts.CommandText =
    "SELECT oh.OrderId, oh.CustomerId, oh.OrderDate, oh.OrderStatus " +
    "FROM Sales.OrderHeader oh " +
    "JOIN Sales.Customer c ON oh.CustomerId = c.CustomerId " +
    "WHERE c.SalesPerson = @SalesPerson " +
    "AND (oh.InsertTimestamp > @sync_last_received_anchor " +
    "AND oh.InsertTimestamp <= @sync_new_received_anchor " +
    "AND oh.InsertId <> @sync_client_id)";
orderHeaderIncrInserts.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
orderHeaderIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
orderHeaderIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
orderHeaderIncrInserts.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
orderHeaderIncrInserts.Connection = serverConn;
orderHeaderSyncAdapter.SelectIncrementalInsertsCommand = orderHeaderIncrInserts;

El ejemplo de código siguiente especifica un valor para el parámetro @SalesPerson en una clase que se deriva de SyncAgent. En una aplicación, este valor podría obtenerse de un Id. de inicio de sesión u otra información especificada por el usuario.

this.Configuration.SyncParameters.Add(
    new SyncParameter("@SalesPerson", "Brenda Diaz"));

this.Configuration.SyncParameters.Add(
    new SyncParameter("@SalesPerson", "Brenda Diaz"));

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. Al ejecutar el ejemplo, preste atención a la información que devuelve SyncStatistics: un subconjunto de filas se descarga tanto en la sincronización inicial como en las posteriores.

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.
            SampleStats sampleStats = new SampleStats();

            //Request a password for the client database, and delete
            //and recreate 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("Customer");

            //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 two SyncGroups, so that changes to OrderHeader
            //and OrderDetail are made in one transaction. Depending on
            //application requirements, you might include Customer
            //in the same group.
            SyncGroup customerSyncGroup = new SyncGroup("Customer");
            SyncGroup orderSyncGroup = new SyncGroup("Order");

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

            //Specify a value for the @SalesPerson parameter that is added
            //in the server synchronization provider. This value would
            //typically be provided by a user in the application, but we
            //have hardcoded it here for convenience.
            this.Configuration.SyncParameters.Add(
                new SyncParameter("@SalesPerson", "Brenda Diaz"));
        }
    }

    //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 use a timestamp value
            //that is retrieved and stored in the client database.
            //During each synchronization, the new anchor value and
            //the last anchor value from the previous synchronization
            //are used: the set of changes between these upper and
            //lower bounds is synchronized.
            //
            //SyncSession.SyncNewReceivedAnchor is a string constant; 
            //you could also use @sync_new_received_anchor directly in 
            //your queries.
            SqlCommand selectNewAnchorCommand = new SqlCommand();
            string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
            selectNewAnchorCommand.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;

            //Create a SyncAdapter for each 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.
            //* Specify if you want only certain columns at the client by 
            //  using the SELECT statement in the command.
            //* Filter rows by using the WHERE clause in the command. 
            //  In this case, we filter out SalesPerson.

            //
            //Customer table
            //

            //Create the SyncAdapter
            SyncAdapter customerSyncAdapter = new SyncAdapter("Customer");

            //Select inserts from the server
            SqlCommand customerIncrInserts = new SqlCommand();
            customerIncrInserts.CommandText =
                "SELECT CustomerId, CustomerName, CustomerType " +
                "FROM Sales.Customer " +
                "WHERE SalesPerson = @SalesPerson " +
                "AND (InsertTimestamp > @sync_last_received_anchor " +
                "AND InsertTimestamp <= @sync_new_received_anchor " +
                "AND InsertId <> @sync_client_id)";
            customerIncrInserts.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            customerIncrInserts.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts;

            //Select updates from the server
            SqlCommand customerIncrUpdates = new SqlCommand();
            customerIncrUpdates.CommandText =
                "SELECT CustomerId, CustomerName, CustomerType " +
                "FROM Sales.Customer " +
                "WHERE SalesPerson = @SalesPerson " +
                "AND (UpdateTimestamp > @sync_last_received_anchor " +
                "AND UpdateTimestamp <= @sync_new_received_anchor " +
                "AND UpdateId <> @sync_client_id " +
                "AND NOT (InsertTimestamp > @sync_last_received_anchor " +
                "AND InsertId <> @sync_client_id))";
            customerIncrUpdates.Parameters.Add("@SalesPerson", SqlDbType.NVarChar); 
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            customerIncrUpdates.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates;

            //Select deletes from the server
            SqlCommand customerIncrDeletes = new SqlCommand();
            customerIncrDeletes.CommandText =
                "SELECT CustomerId, CustomerName, CustomerType " +
                "FROM Sales.Customer_Tombstone " +
                "WHERE SalesPerson = @SalesPerson " +
                "AND (@sync_initialized = 1 " +
                "AND DeleteTimestamp > @sync_last_received_anchor " +
                "AND DeleteTimestamp <= @sync_new_received_anchor " +
                "AND DeleteId <> @sync_client_id)";
            customerIncrDeletes.Parameters.Add("@SalesPerson", SqlDbType.NVarChar); 
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            customerIncrDeletes.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalDeletesCommand = customerIncrDeletes;

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

            
            //
            //OrderHeader table
            //

            //Create the SyncAdapter
            SyncAdapter orderHeaderSyncAdapter = new SyncAdapter("OrderHeader");

            //Select inserts from the server
            SqlCommand orderHeaderIncrInserts = new SqlCommand();
            orderHeaderIncrInserts.CommandText =
                "SELECT oh.OrderId, oh.CustomerId, oh.OrderDate, oh.OrderStatus " +
                "FROM Sales.OrderHeader oh " +
                "JOIN Sales.Customer c ON oh.CustomerId = c.CustomerId " +
                "WHERE c.SalesPerson = @SalesPerson " +
                "AND (oh.InsertTimestamp > @sync_last_received_anchor " +
                "AND oh.InsertTimestamp <= @sync_new_received_anchor " +
                "AND oh.InsertId <> @sync_client_id)";
            orderHeaderIncrInserts.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            orderHeaderIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            orderHeaderIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            orderHeaderIncrInserts.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            orderHeaderIncrInserts.Connection = serverConn;
            orderHeaderSyncAdapter.SelectIncrementalInsertsCommand = orderHeaderIncrInserts;

            //Select updates from the server
            SqlCommand orderHeaderIncrUpdates = new SqlCommand();
            orderHeaderIncrUpdates.CommandText =
                "SELECT oh.OrderId, oh.CustomerId, oh.OrderDate, oh.OrderStatus " +
                "FROM Sales.OrderHeader oh " +
                "JOIN Sales.Customer c ON oh.CustomerId = c.CustomerId " +
                "WHERE c.SalesPerson = @SalesPerson " +
                "AND (oh.UpdateTimestamp > @sync_last_received_anchor " +
                "AND oh.UpdateTimestamp <= @sync_new_received_anchor " +
                "AND oh.UpdateId <> @sync_client_id " +
                "AND NOT (oh.InsertTimestamp > @sync_last_received_anchor " +
                "AND oh.InsertId <> @sync_client_id))";
            orderHeaderIncrUpdates.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            orderHeaderIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            orderHeaderIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            orderHeaderIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            orderHeaderIncrUpdates.Connection = serverConn;
            orderHeaderSyncAdapter.SelectIncrementalUpdatesCommand = orderHeaderIncrUpdates;

            //Select deletes from the server
            SqlCommand orderHeaderIncrDeletes = new SqlCommand();
            orderHeaderIncrDeletes.CommandText =
                "SELECT oht.OrderId, oht.CustomerId, oht.OrderDate, oht.OrderStatus " +
                "FROM Sales.OrderHeader_Tombstone oht " +
                "JOIN Sales.Customer c ON oht.CustomerId = c.CustomerId " +
                "WHERE c.SalesPerson = @SalesPerson " +
                "AND (@sync_initialized = 1 " +
                "AND oht.DeleteTimestamp > @sync_last_received_anchor " +
                "AND oht.DeleteTimestamp <= @sync_new_received_anchor " +
                "AND oht.DeleteId <> @sync_client_id)";
            orderHeaderIncrDeletes.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            orderHeaderIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
            orderHeaderIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            orderHeaderIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            orderHeaderIncrDeletes.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            orderHeaderIncrDeletes.Connection = serverConn;
            orderHeaderSyncAdapter.SelectIncrementalDeletesCommand = orderHeaderIncrDeletes;

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

            
            //
            //OrderDetail table
            //

            //Create the SyncAdapter
            SyncAdapter orderDetailSyncAdapter = new SyncAdapter("OrderDetail");

            //Select inserts from the server
            SqlCommand orderDetailIncrInserts = new SqlCommand();
            orderDetailIncrInserts.CommandText =
                "SELECT od.OrderDetailId, od.OrderId, od.Product, od.Quantity " +
                "FROM Sales.OrderDetail od " +
                "JOIN Sales.OrderHeader oh ON od.OrderId = oh.OrderId " +
                "JOIN Sales.Customer c ON oh.CustomerId = c.CustomerId " +
                "WHERE SalesPerson = @SalesPerson " +
                "AND (od.InsertTimestamp > @sync_last_received_anchor " +
                "AND od.InsertTimestamp <= @sync_new_received_anchor " +
                "AND od.InsertId <> @sync_client_id)";
            orderDetailIncrInserts.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            orderDetailIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            orderDetailIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            orderDetailIncrInserts.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            orderDetailIncrInserts.Connection = serverConn;
            orderDetailSyncAdapter.SelectIncrementalInsertsCommand = orderDetailIncrInserts;

            //Select updates from the server
            SqlCommand orderDetailIncrUpdates = new SqlCommand();
            orderDetailIncrUpdates.CommandText =
                "SELECT od.OrderDetailId, od.OrderId, od.Product, od.Quantity " +
                "FROM Sales.OrderDetail od " +
                "JOIN Sales.OrderHeader oh ON od.OrderId = oh.OrderId " +
                "JOIN Sales.Customer c ON oh.CustomerId = c.CustomerId " +
                "WHERE SalesPerson = @SalesPerson " +
                "AND (od.UpdateTimestamp > @sync_last_received_anchor " +
                "AND od.UpdateTimestamp <= @sync_new_received_anchor " +
                "AND od.UpdateId <> @sync_client_id " +
                "AND NOT (od.InsertTimestamp > @sync_last_received_anchor " +
                "AND od.InsertId <> @sync_client_id))";
            orderDetailIncrUpdates.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            orderDetailIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            orderDetailIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            orderDetailIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            orderDetailIncrUpdates.Connection = serverConn;
            orderDetailSyncAdapter.SelectIncrementalUpdatesCommand = orderDetailIncrUpdates;

            //Select deletes from the server
            SqlCommand orderDetailIncrDeletes = new SqlCommand();
            orderDetailIncrDeletes.CommandText =
                "SELECT odt.OrderDetailId, odt.OrderId, odt.Product, odt.Quantity " +
                "FROM Sales.OrderDetail_Tombstone odt " +
                "JOIN Sales.OrderHeader oh ON odt.OrderId = oh.OrderId " +
                "JOIN Sales.Customer c ON oh.CustomerId = c.CustomerId " +
                "WHERE SalesPerson = @SalesPerson " +
                "AND (@sync_initialized = 1 " +
                "AND odt.DeleteTimestamp > @sync_last_received_anchor " +
                "AND odt.DeleteTimestamp <= @sync_new_received_anchor " +
                "AND odt.DeleteId <> @sync_client_id)";
            orderDetailIncrDeletes.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            orderDetailIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
            orderDetailIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            orderDetailIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            orderDetailIncrDeletes.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            orderDetailIncrDeletes.Connection = serverConn;
            orderDetailSyncAdapter.SelectIncrementalDeletesCommand = orderDetailIncrDeletes;

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

    //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 you could use this class to handle client 
    //provider events and other client-side processing.
    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 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 Downloaded: " + syncStatistics.TotalChangesDownloaded);
            Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime);
            Console.WriteLine(String.Empty);
        }
    }
}

Vea también

Mostrar: