Procédure : filtrer des données pour la synchronisation de bases de données (SQL Server)
Cette rubrique indique comment créer des filtres utilisables avec Sync Framework pour synchroniser les bases de données SQL Server, SQL Azure et SQL Server Compact. Les exemples de cette rubrique reposent sur les classes et les membres Sync Framework suivants :
Pour plus d'informations sur le mode d'exécution de l'exemple de code, consultez « Exemples d'application dans les rubriques de procédures » dans Synchronisation de SQL Server et SQL Server Compact.
Présentation des filtres
Les filtres permettent de contrôler les éléments énumérés par le fournisseur de source lors d'une session de synchronisation. Lorsque les éléments sont énumérés et envoyés au fournisseur de destination, le fournisseur de source inclut uniquement les éléments qui passent le filtre. Un filtre est généralement basé sur la valeur d'un ou de plusieurs champs, de façon à ce qu'une ligne soit uniquement passée par le filtre lorsque ses champs de filtre répondent aux critères du filtrage.
Sync Framework vous permet de créer deux types de filtres : des filtres statiques et des filtres basés sur des paramètres. Les filtres statiques sont définis dans le cadre d'une étendue de synchronisation et définissent les valeurs que les champs de filtre doivent contenir afin d'être passés par le filtre. Les filtres statiques sont encodés dans la procédure stockée utilisée par la base de données source afin d'énumérer les modifications pour l'étendue. Une fois qu'un filtre statique est défini, il ne peut plus être modifié. Pour plus d'informations et pour obtenir un exemple d'utilisation d'un filtre statique, consultez Procédure : configurer et exécuter la synchronisation de bases de données (SQL Server). Les filtres basés sur des paramètres sont définis par une clause de filtre et par un jeu de paramètres mappés aux colonnes de table dans l'étendue de synchronisation. Les filtres basés sur des paramètres sont définis en deux étapes. La première étape consiste à définir les paramètres et la clause du filtre, et à fournir la description de l'étendue associée au filtre. À ce stade, le filtre et l'étendue n'ont qu'un format de modèle. La seconde étape consiste à définir les valeurs de paramètre du filtre et à créer l'étendue de synchronisation à partir du modèle. L'étendue créée alors est celle qu'un fournisseur de destination utilise pour se synchroniser avec la base de données source. La base de données source utilisée pour le filtrage basé sur les paramètres peut être une base de données SQL Server ou SQL Azure, et la base de données de destination peut être une base de données SQL Server, SQL Azure ou SQL Server Compact.
Dans un scénario de filtrage classique, un administrateur de base de données ou développeur d'applications définit un filtre basé sur des paramètres et prépare la base de données serveur pour la synchronisation filtrée. Il peut aussi éventuellement créer un outil simple, tel qu'un outil d'abonnement Web, qui utilise des objets Sync Framework pour permettre aux utilisateurs de spécifier leurs valeurs de paramètres de filtre et d'abonner leurs bases de données client pour la synchronisation. La création d'un outil d'abonnement dispense l'administrateur de base de données de créer lui-même des filtres pour les utilisateurs individuels. En revanche, cet outil permet aux utilisateurs de spécifier les valeurs de paramètre appropriées pour chacun d'eux et de s'abonner à la synchronisation en fonction de leurs besoins.
Le processus suivant est un exemple de configuration de la synchronisation filtrée :
Vous utilisez une base de données contenant des informations sur les clients. Cette base de données comporte une colonne indiquant le type du client et une colonne indiquant la ville où se trouve le client.
Vous définissez un modèle de filtre basé sur deux paramètres de filtrage : le type et la ville du client. Vous spécifiez qu'une ligne ne doit passer le filtre que lorsque les valeurs de ses champs Type et Ville correspondent aux valeurs des paramètres.
L'un des commerciaux veut synchroniser sa base de données client avec votre serveur. Il demande la création d'une étendue filtrée avec des valeurs de paramètres pour les clients détaillants situés à Paris, puis il effectue la synchronisation.
Il obtient uniquement les données souhaitées, ce qui réduit le trafic réseau et la quantité de mémoire utilisée sur la base de données client.
Un autre commercial demande une étendue filtrée qui passe les clients grossistes situés à Lille, puis il effectue la synchronisation. Il obtient uniquement les données souhaitées.
Gardez à l'esprit que les filtres basés sur des paramètres sont uniquement appropriés lorsque les éléments n'effectuent de déplacements ni dans l'étendue du filtre ni en dehors de celle-ci. Des modifications de ce genre ne sont pas propagées correctement dans la communauté de synchronisation. Par exemple, imaginons un filtre défini en fonction d'une colonne contenant le nom de commerciaux. Le commercial A crée une étendue filtrée sur son nom et synchronise les données vers sa base de données client. Son responsable réassigne l'un de ses clients au commercial B. Si le commercial A se synchronise à nouveau avec la base de données, le client réaffecté figurera toujours dans sa base de données client et les données seront obsolètes et non supprimées.
Création d'un filtre basé sur des paramètres
Les filtres basés sur des paramètres sont créés en deux étapes. La première étape consiste à définir les modèles de filtre et d'étendue. La seconde étape consiste à créer une étendue filtrée comportant les valeurs spécifiques des paramètres filtrés. Ce processus en deux étapes offre les avantages suivants :
Configuration facile. Le modèle de filtre n'est défini qu'une seule fois. La création d'un modèle de filtre est la seule action nécessitant des autorisations pour créer des procédures stockées sur le serveur de base de données.
Abonnement facile. Les clients spécifient les valeurs de paramètre à créer et s'abonnent aux synchronisations filtrées en fonction de leurs besoins. Cette étape nécessite uniquement l'autorisation d'insérer des lignes dans les tables de synchronisation sur le serveur de base de données.
Gestion facile. Même lorsque plusieurs paramètres sont combinés et que de nombreuses étendues filtrées sont créées, la gestion est simple, car une seule procédure basée sur les paramètres est utilisée pour énumérer les modifications.
Définition d'un modèle de filtre
La première étape de création d'un filtre basé sur les paramètres consiste à définir un modèle de filtre pouvant être utilisé ultérieurement pour créer des étendues filtrées. Les modèles de filtres sont stockés dans la base de données source et nécessitent la création de tables de synchronisation et de procédures stockées. Vous devez donc disposer des autorisations appropriées dans la base de données source.
Les modèles de filtres sont définis en même temps que les étendues de synchronisation. Vous définissez un modèle de filtre pour une table dans l'étendue comme suit :
Ajoutez une colonne de filtrage à un objet SqlSyncTableProvisioning dans l'étendue de synchronisation à l'aide de la méthode AddFilterColumn. La colonne de filtrage est ajoutée à la table de suivi qui effectue le suivi des modifications pour la table de base.
Définissez un ou plusieurs paramètres de filtre en ajoutant des objets SqlParameter à la collection FilterParameters de l'objet SqlSyncTableProvisioning. Les paramètres spécifiés sont ajoutés à la liste d'arguments de la procédure stockée qui énumère les changements durant la synchronisation.
Ajoutez une clause de filtre pour définir la relation entre les valeurs de paramètre et les valeurs de colonne en définissant la propriété FilterClause de l'objet SqlSyncTableProvisioning. La clause de filtre est une clause WHERE sans le mot clé WHERE. L'alias [side] est un alias pour la table de suivi. Les paramètres correspondent aux paramètres spécifiés dans la collection FilterParameters. À ce stade, vous définissez uniquement la relation entre les paramètres de filtrage et les colonnes de filtrage. Les valeurs réelles des paramètres seront spécifiées ultérieurement, lorsque l'étendue filtrée sera créée.
Les modèles de filtre et d'étendue sont ensuite appliqués à la base de données source à l'aide de la méthode Apply de l'objet SqlSyncScopeProvisioning, puis les tables de synchronisation ainsi que les procédures stockées appropriées sont créées.
Dans la clause de filtre, les alias [base] et [side] sont définis par Sync Framework. [base] fait référence au nom de base de la table, tandis que [side] fait référence à la table de suivi des modifications. Par exemple, la table Customer est filtrée sur la base de la colonne CustomerType. Par défaut, [base] est un alias pour [Customer], tandis que [side] est un alias pour [Customer_tracking]. Comme la colonne CustomerType existe à la fois dans les tables de base et de suivi, les références à celle-ci doivent être qualifiées dans la clause de filtre ; sinon, la colonne est ambiguë et une erreur survient. Vous pouvez utiliser les noms de table réels à la place des alias [base] et [side], [Customer_tracking].[CustomerType] = @customertype par exemple.
L'exemple suivant définit un modèle de filtre et l'applique à la base de données source :
// Create a scope named "customertype_template", and add two tables to the scope.
// GetDescriptionForTable gets the schema of each table, so that tracking
// tables and triggers can be created for that table.
DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription("customertype_template");
// Set a friendly description of the template.
scopeDesc.UserDescription = "Template for Customer and CustomerContact tables. Customer data is filtered by CustomerType parameter.";
// Definition for tables.
DbSyncTableDescription customerDescription =
SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn);
scopeDesc.Tables.Add(customerDescription);
DbSyncTableDescription customerContactDescription =
SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.CustomerContact", serverConn);
scopeDesc.Tables.Add(customerContactDescription);
// Create a provisioning object for "customertype_template" that can be used to create a template
// from which filtered synchronization scopes can be created. We specify that
// all synchronization-related objects should be created in a
// database schema named "Sync". If you specify a schema, it must already exist
// in the database.
SqlSyncScopeProvisioning serverTemplate = new SqlSyncScopeProvisioning(serverConn, scopeDesc, SqlSyncScopeProvisioningType.Template);
serverTemplate.ObjectSchema = "Sync";
// Specify the column in the Customer table to use for filtering data,
// and the filtering clause to use against the tracking table.
// "[side]" is an alias for the tracking table.
// The CustomerType column that defines the filter is set up as a parameter in this template.
// An actual customer type will be specified when the synchronization scope is created.
serverTemplate.Tables["Sales.Customer"].AddFilterColumn("CustomerType");
serverTemplate.Tables["Sales.Customer"].FilterClause = "[side].[CustomerType] = @customertype";
SqlParameter param = new SqlParameter("@customertype", SqlDbType.NVarChar, 100);
serverTemplate.Tables["Sales.Customer"].FilterParameters.Add(param);
// Create the "customertype_template" template in the database.
// This action creates tables and stored procedures in the database, so appropriate database permissions are needed.
serverTemplate.Apply();
' Create a scope named "customertype_template", and add two tables to the scope.
' GetDescriptionForTable gets the schema of each table, so that tracking
' tables and triggers can be created for that table.
Dim scopeDesc As New DbSyncScopeDescription("customertype_template")
' Set a friendly description of the template.
scopeDesc.UserDescription = "Template for Customer and CustomerContact tables. Customer data is filtered by CustomerType parameter."
' Definition for tables.
Dim customerDescription As DbSyncTableDescription = SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn)
scopeDesc.Tables.Add(customerDescription)
Dim customerContactDescription As DbSyncTableDescription = SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.CustomerContact", serverConn)
scopeDesc.Tables.Add(customerContactDescription)
' Create a provisioning object for "customertype_template" that can be used to create a template
' from which filtered synchronization scopes can be created. We specify that
' all synchronization-related objects should be created in a
' database schema named "Sync". If you specify a schema, it must already exist
' in the database.
Dim serverTemplate As New SqlSyncScopeProvisioning(serverConn, scopeDesc, SqlSyncScopeProvisioningType.Template)
serverTemplate.ObjectSchema = "Sync"
' Specify the column in the Customer table to use for filtering data,
' and the filtering clause to use against the tracking table.
' "[side]" is an alias for the tracking table.
' The CustomerType column that defines the filter is set up as a parameter in this template.
' An actual customer type will be specified when the synchronization scope is created.
serverTemplate.Tables("Sales.Customer").AddFilterColumn("CustomerType")
serverTemplate.Tables("Sales.Customer").FilterClause = "[side].[CustomerType] = @customertype"
Dim param As New SqlParameter("@customertype", SqlDbType.NVarChar, 100)
serverTemplate.Tables("Sales.Customer").FilterParameters.Add(param)
' Create the "customertype_template" template in the database.
' This action creates tables and stored procedures in the database, so appropriate permissions are needed.
serverTemplate.Apply()
Création d'une étendue filtrée
Avant de pouvoir utiliser un filtre pour se synchroniser avec le serveur, le client doit d'abord définir des valeurs spécifiques pour les paramètres de filtrage. Pour ce faire, il remplit d'abord un objet SqlSyncScopeProvisioning à partir du modèle de filtre situé sur le serveur, puis il attribue un nom à l'étendue filtrée en appelant la méthode PopulateFromTemplate. Le client définit ensuite les valeurs des paramètres de filtrage en définissant les propriétés de valeur des membres de la collection FilterParameters dans l'objet SqlSyncTableProvisioning. Enfin, le client applique l'étendue filtrée au serveur en appelant la méthode Apply de l'objet SqlSyncScopeProvisioning. L'application d'une étendue filtrée au serveur de base de données permet d'ajouter des lignes aux tables de synchronisation et nécessite uniquement l'autorisation d'insérer des lignes dans ces tables.
Après avoir spécifié l'étendue filtrée sur la base de données serveur, vous approvisionnez une base de données client en appelant GetDescriptionForScope pour obtenir la description de l'étendue nommée, en chargeant cette description dans un objet SqlSyncScopeProvisioning et en appliquant la description de l'étendue à la base de données client en appelant la méthode Apply.
Le code permettant de définir les valeurs des paramètres de filtrage, d'appliquer le filtre venant d'être spécifié à la base de données serveur et d'approvisionner la base de données client peut être facilement encapsulé dans un outil distinct qui regroupe les valeurs des paramètres de filtrage d'un utilisateur et qui abonne la base de données client pour la synchronisation filtrée.
L'exemple suivant définit la valeur de paramètre d'un filtre, l'applique à la base de données serveur et approvisionne la base de données client avec l'étendue filtrée afin de la préparer pour la synchronisation.
// Create a synchronization scope for retail customers.
// This action adds rows to synchronization tables but does not create new tables or stored procedures, reducing
// the permissions needed on the server.
SqlSyncScopeProvisioning serverProvRetail = new SqlSyncScopeProvisioning(serverConn);
serverProvRetail.ObjectSchema = "Sync";
serverProvRetail.PopulateFromTemplate("RetailCustomers", "customertype_template");
serverProvRetail.Tables["Sales.Customer"].FilterParameters["@customertype"].Value = "Retail";
serverProvRetail.UserDescription = "Customer data includes only retail customers.";
serverProvRetail.Apply();
// Provision the existing database SyncSamplesDb_SqlPeer2 based on filtered scope
// information that is retrieved from the server.
DbSyncScopeDescription clientSqlDesc = SqlSyncDescriptionBuilder.GetDescriptionForScope("RetailCustomers", null, "Sync", serverConn);
SqlSyncScopeProvisioning clientSqlConfig = new SqlSyncScopeProvisioning(clientSqlConn, clientSqlDesc);
clientSqlConfig.ObjectSchema = "Sync";
clientSqlConfig.Apply();
' Create a synchronization scope for retail customers.
' This action adds rows to synchronization tables but does not create new tables or stored procedures, reducing
' the permissions needed on the server.
Dim serverProvRetail As New SqlSyncScopeProvisioning(serverConn)
serverProvRetail.ObjectSchema = "Sync"
serverProvRetail.PopulateFromTemplate("RetailCustomers", "customertype_template")
serverProvRetail.Tables("Sales.Customer").FilterParameters("@customertype").Value = "Retail"
serverProvRetail.UserDescription = "Customer data includes only retail customers."
serverProvRetail.Apply()
' Provision the existing database SyncSamplesDb_SqlPeer2 based on filtered scope
' information that is retrieved from the server.
Dim clientSqlDesc As DbSyncScopeDescription = SqlSyncDescriptionBuilder.GetDescriptionForScope("RetailCustomers", Nothing, "Sync", serverConn)
Dim clientSqlConfig As New SqlSyncScopeProvisioning(clientSqlConn, clientSqlDesc)
clientSqlConfig.ObjectSchema = "Sync"
clientSqlConfig.Apply()
Synchronisation d'un client à l'aide d'une étendue filtrée
Une fois que l'étendue filtrée est définie et que la base de données client est approvisionnée, le client peut être synchronisé en créant des objets SqlSyncProvider pour l'étendue filtrée dans les bases de données serveur et client, en associant les fournisseurs à un objet SyncOrchestrator, puis en appelant la méthode Synchronize.
L'exemple suivant effectue une synchronisation filtrée de deux bases de données :
syncOrchestrator = new SampleSyncOrchestrator(
new SqlSyncProvider("RetailCustomers", clientSqlConn, null, "Sync"),
new SqlSyncProvider("RetailCustomers", serverConn, null, "Sync")
);
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator = New SampleSyncOrchestrator(New SqlSyncProvider("RetailCustomers", clientSqlConn, Nothing, "Sync"), New SqlSyncProvider("RetailCustomers", serverConn, Nothing, "Sync"))
syncStats = syncOrchestrator.Synchronize()
Exemple
L'exemple ci-dessous inclut les exemples de code décrits précédemment, ainsi que du code supplémentaire pour effectuer la synchronisation. L'exemple requiert la classe Utility qui est disponible dans Classe d'utilitaire pour les rubriques de procédures sur le fournisseur de bases de données.
using System;
using System.Collections.ObjectModel;
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.SqlServer;
using Microsoft.Synchronization.Data.SqlServerCe;
namespace Microsoft.Samples.Synchronization
{
class Program
{
static void Main(string[] args)
{
// Create the connections over which provisioning and synchronization
// are performed. The Utility class handles all functionality that is not
// directly related to synchronization, such as holding connection
// string information and making changes to the server database.
SqlConnection serverConn = new SqlConnection(Utility.ConnStr_SqlSync_Server);
SqlConnection clientSqlConn = new SqlConnection(Utility.ConnStr_SqlSync_Client);
SqlCeConnection clientSqlCe1Conn = new SqlCeConnection(Utility.ConnStr_SqlCeSync1);
// Create a scope named "customertype_template", and add two tables to the scope.
// GetDescriptionForTable gets the schema of each table, so that tracking
// tables and triggers can be created for that table.
DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription("customertype_template");
// Set a friendly description of the template.
scopeDesc.UserDescription = "Template for Customer and CustomerContact tables. Customer data is filtered by CustomerType parameter.";
// Definition for tables.
DbSyncTableDescription customerDescription =
SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn);
scopeDesc.Tables.Add(customerDescription);
DbSyncTableDescription customerContactDescription =
SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.CustomerContact", serverConn);
scopeDesc.Tables.Add(customerContactDescription);
// Create a provisioning object for "customertype_template" that can be used to create a template
// from which filtered synchronization scopes can be created. We specify that
// all synchronization-related objects should be created in a
// database schema named "Sync". If you specify a schema, it must already exist
// in the database.
SqlSyncScopeProvisioning serverTemplate = new SqlSyncScopeProvisioning(serverConn, scopeDesc, SqlSyncScopeProvisioningType.Template);
serverTemplate.ObjectSchema = "Sync";
// Specify the column in the Customer table to use for filtering data,
// and the filtering clause to use against the tracking table.
// "[side]" is an alias for the tracking table.
// The CustomerType column that defines the filter is set up as a parameter in this template.
// An actual customer type will be specified when the synchronization scope is created.
serverTemplate.Tables["Sales.Customer"].AddFilterColumn("CustomerType");
serverTemplate.Tables["Sales.Customer"].FilterClause = "[side].[CustomerType] = @customertype";
SqlParameter param = new SqlParameter("@customertype", SqlDbType.NVarChar, 100);
serverTemplate.Tables["Sales.Customer"].FilterParameters.Add(param);
// Create the "customertype_template" template in the database.
// This action creates tables and stored procedures in the database, so appropriate database permissions are needed.
serverTemplate.Apply();
// Create a synchronization scope for retail customers.
// This action adds rows to synchronization tables but does not create new tables or stored procedures, reducing
// the permissions needed on the server.
SqlSyncScopeProvisioning serverProvRetail = new SqlSyncScopeProvisioning(serverConn);
serverProvRetail.ObjectSchema = "Sync";
serverProvRetail.PopulateFromTemplate("RetailCustomers", "customertype_template");
serverProvRetail.Tables["Sales.Customer"].FilterParameters["@customertype"].Value = "Retail";
serverProvRetail.UserDescription = "Customer data includes only retail customers.";
serverProvRetail.Apply();
// Provision the existing database SyncSamplesDb_SqlPeer2 based on filtered scope
// information that is retrieved from the server.
DbSyncScopeDescription clientSqlDesc = SqlSyncDescriptionBuilder.GetDescriptionForScope("RetailCustomers", null, "Sync", serverConn);
SqlSyncScopeProvisioning clientSqlConfig = new SqlSyncScopeProvisioning(clientSqlConn, clientSqlDesc);
clientSqlConfig.ObjectSchema = "Sync";
clientSqlConfig.Apply();
// Create a SQL Server Compact database and provision it based on a new scope that is created from template
// information coupled with a new customer type to use for the filter.
// Compact databases do not support separate schemas, so we prefix the name of all
// synchronization-related objects with "Sync" so that they are easy to
// identify.
Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, true);
// First create the scope in the server database based on the filter template.
// Again, note that this step requires only permission to insert rows in the synchronization tables.
SqlSyncScopeProvisioning serverProvWholesale = new SqlSyncScopeProvisioning(serverConn);
serverProvWholesale.ObjectSchema = "Sync";
serverProvWholesale.PopulateFromTemplate("WholesaleCustomers", "customertype_template");
serverProvWholesale.Tables["Sales.Customer"].FilterParameters["@customertype"].Value = "Wholesale";
serverProvWholesale.UserDescription = "Customer data includes only wholesale customers.";
serverProvWholesale.Apply();
// Now provision the SQL Server Compact database with the new filtered scope.
DbSyncScopeDescription clientSqlCe1Desc = SqlSyncDescriptionBuilder.GetDescriptionForScope("WholesaleCustomers", null, "Sync", serverConn);
SqlCeSyncScopeProvisioning clientSqlCe1Config = new SqlCeSyncScopeProvisioning(clientSqlCe1Conn, clientSqlCe1Desc);
clientSqlCe1Config.ObjectPrefix = "Sync";
clientSqlCe1Config.Apply();
// Initial synchronization sessions. 7 rows are synchronized:
// all rows (4) from CustomerContact, and the 3 rows from Customer
// that satisfy the filtering criteria.
SampleSyncOrchestrator syncOrchestrator;
SyncOperationStatistics syncStats;
// Data is downloaded from the server to the SQL Server client.
syncOrchestrator = new SampleSyncOrchestrator(
new SqlSyncProvider("RetailCustomers", clientSqlConn, null, "Sync"),
new SqlSyncProvider("RetailCustomers", serverConn, null, "Sync")
);
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator.DisplayStats(syncStats, "initial");
// Data is downloaded from the server to the
// first SQL Server Compact client.
syncOrchestrator = new SampleSyncOrchestrator(
new SqlCeSyncProvider("WholesaleCustomers", clientSqlCe1Conn, "Sync"),
new SqlSyncProvider("WholesaleCustomers", serverConn, null, "Sync")
);
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator.DisplayStats(syncStats, "initial");
// Make changes on the server:
// 1 insert affects RetailCustomers scope
// 1 update affects WholesaleCustomers scope
// 1 delete affects RetailCustomers scope
Utility.MakeDataChangesOnNode(Utility.ConnStr_SqlSync_Server, "Customer");
// Synchronize again. Three changes were made on the server.
syncOrchestrator = new SampleSyncOrchestrator(
new SqlCeSyncProvider("WholesaleCustomers", clientSqlCe1Conn, "Sync"),
new SqlSyncProvider("WholesaleCustomers", serverConn, null, "Sync")
);
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator.DisplayStats(syncStats, "subsequent");
syncOrchestrator = new SampleSyncOrchestrator(
new SqlSyncProvider("RetailCustomers", clientSqlConn, null, "Sync"),
new SqlSyncProvider("RetailCustomers", serverConn, null, "Sync")
);
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator.DisplayStats(syncStats, "subsequent");
serverConn.Close();
serverConn.Dispose();
clientSqlConn.Close();
clientSqlConn.Dispose();
clientSqlCe1Conn.Close();
clientSqlCe1Conn.Dispose();
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server);
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Client);
Console.Write("\nPress any key to exit.");
Console.Read();
}
}
public class SampleSyncOrchestrator : SyncOrchestrator
{
public SampleSyncOrchestrator(RelationalSyncProvider localProvider, RelationalSyncProvider remoteProvider)
{
this.LocalProvider = localProvider;
this.RemoteProvider = remoteProvider;
this.Direction = SyncDirectionOrder.UploadAndDownload;
}
public void DisplayStats(SyncOperationStatistics syncStatistics, string syncType)
{
Console.WriteLine(String.Empty);
if (syncType == "initial")
{
Console.WriteLine("****** Initial Synchronization ******");
}
else if (syncType == "subsequent")
{
Console.WriteLine("***** Subsequent Synchronization ****");
}
Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
Console.WriteLine("Total Changes Uploaded: " + syncStatistics.UploadChangesTotal);
Console.WriteLine("Total Changes Downloaded: " + syncStatistics.DownloadChangesTotal);
Console.WriteLine("Complete Time: " + syncStatistics.SyncEndTime);
Console.WriteLine(String.Empty);
}
}
}
Imports System
Imports System.Collections.ObjectModel
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.SqlServer
Imports Microsoft.Synchronization.Data.SqlServerCe
Namespace Microsoft.Samples.Synchronization
Class Program
Shared Sub Main(ByVal args As String())
' Create the connections over which provisioning and synchronization
' are performed. The Utility class handles all functionality that is not
' directly related to synchronization, such as holding connection
' string information and making changes to the server database.
Dim serverConn As New SqlConnection(Utility.ConnStr_SqlSync_Server)
Dim clientSqlConn As New SqlConnection(Utility.ConnStr_SqlSync_Client)
Dim clientSqlCe1Conn As New SqlCeConnection(Utility.ConnStr_SqlCeSync1)
' Create a scope named "customertype_template", and add two tables to the scope.
' GetDescriptionForTable gets the schema of each table, so that tracking
' tables and triggers can be created for that table.
Dim scopeDesc As New DbSyncScopeDescription("customertype_template")
' Set a friendly description of the template.
scopeDesc.UserDescription = "Template for Customer and CustomerContact tables. Customer data is filtered by CustomerType parameter."
' Definition for tables.
Dim customerDescription As DbSyncTableDescription = SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn)
scopeDesc.Tables.Add(customerDescription)
Dim customerContactDescription As DbSyncTableDescription = SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.CustomerContact", serverConn)
scopeDesc.Tables.Add(customerContactDescription)
' Create a provisioning object for "customertype_template" that can be used to create a template
' from which filtered synchronization scopes can be created. We specify that
' all synchronization-related objects should be created in a
' database schema named "Sync". If you specify a schema, it must already exist
' in the database.
Dim serverTemplate As New SqlSyncScopeProvisioning(serverConn, scopeDesc, SqlSyncScopeProvisioningType.Template)
serverTemplate.ObjectSchema = "Sync"
' Specify the column in the Customer table to use for filtering data,
' and the filtering clause to use against the tracking table.
' "[side]" is an alias for the tracking table.
' The CustomerType column that defines the filter is set up as a parameter in this template.
' An actual customer type will be specified when the synchronization scope is created.
serverTemplate.Tables("Sales.Customer").AddFilterColumn("CustomerType")
serverTemplate.Tables("Sales.Customer").FilterClause = "[side].[CustomerType] = @customertype"
Dim param As New SqlParameter("@customertype", SqlDbType.NVarChar, 100)
serverTemplate.Tables("Sales.Customer").FilterParameters.Add(param)
' Create the "customertype_template" template in the database.
' This action creates tables and stored procedures in the database, so appropriate permissions are needed.
serverTemplate.Apply()
' Write the template creation script to a file. You can modify
' this script if necessary and run it against the server
' to customize behavior.
File.WriteAllText("SampleTemplateScript.txt", serverTemplate.Script())
' Create a synchronization scope for retail customers.
' This action adds rows to synchronization tables but does not create new tables or stored procedures, reducing
' the permissions needed on the server.
Dim serverProvRetail As New SqlSyncScopeProvisioning(serverConn)
serverProvRetail.ObjectSchema = "Sync"
serverProvRetail.PopulateFromTemplate("RetailCustomers", "customertype_template")
serverProvRetail.Tables("Sales.Customer").FilterParameters("@customertype").Value = "Retail"
serverProvRetail.UserDescription = "Customer data includes only retail customers."
serverProvRetail.Apply()
' Provision the existing database SyncSamplesDb_SqlPeer2 based on filtered scope
' information that is retrieved from the server.
Dim clientSqlDesc As DbSyncScopeDescription = SqlSyncDescriptionBuilder.GetDescriptionForScope("RetailCustomers", Nothing, "Sync", serverConn)
Dim clientSqlConfig As New SqlSyncScopeProvisioning(clientSqlConn, clientSqlDesc)
clientSqlConfig.ObjectSchema = "Sync"
clientSqlConfig.Apply()
' Create a SQL Server Compact database and provision it based on a new scope that is created from template
' information coupled with a new customer type to use for the filter.
' Compact databases do not support separate schemas, so we prefix the name of all
' synchronization-related objects with "Sync" so that they are easy to
' identify.
Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, True)
' First create the scope in the server database based on the filter template.
' Again, note that this step requires only permission to insert rows to the synchronization tables.
Dim serverProvWholesale As New SqlSyncScopeProvisioning(serverConn)
serverProvWholesale.ObjectSchema = "Sync"
serverProvWholesale.PopulateFromTemplate("WholesaleCustomers", "customertype_template")
serverProvWholesale.Tables("Sales.Customer").FilterParameters("@customertype").Value = "Wholesale"
serverProvWholesale.UserDescription = "Customer data includes only wholesale customers."
serverProvWholesale.Apply()
' Now provision the SQL Server Compact database with the new filtered scope.
Dim clientSqlCe1Desc As DbSyncScopeDescription = SqlSyncDescriptionBuilder.GetDescriptionForScope("WholesaleCustomers", Nothing, "Sync", serverConn)
Dim clientSqlCe1Config As New SqlCeSyncScopeProvisioning(clientSqlCe1Conn, clientSqlCe1Desc)
clientSqlCe1Config.ObjectPrefix = "Sync"
clientSqlCe1Config.Apply()
' Initial synchronization sessions. 7 rows are synchronized:
' all rows (4) from CustomerContact, and the 3 rows from Customer
' that satisfy the filtering criteria.
Dim syncOrchestrator As SampleSyncOrchestrator
Dim syncStats As SyncOperationStatistics
' Data is downloaded from the server to the SQL Server client.
syncOrchestrator = New SampleSyncOrchestrator(New SqlSyncProvider("RetailCustomers", clientSqlConn, Nothing, "Sync"), New SqlSyncProvider("RetailCustomers", serverConn, Nothing, "Sync"))
syncStats = syncOrchestrator.Synchronize()
syncOrchestrator.DisplayStats(syncStats, "initial")
' Data is downloaded from the server to the
' first SQL Server Compact client.
syncOrchestrator = New SampleSyncOrchestrator(New SqlCeSyncProvider("WholesaleCustomers", clientSqlCe1Conn, "Sync"), New SqlSyncProvider("WholesaleCustomers", serverConn, Nothing, "Sync"))
syncStats = syncOrchestrator.Synchronize()
syncOrchestrator.DisplayStats(syncStats, "initial")
' Make changes on the server:
' 1 insert affects RetailCustomers scope
' 1 update affects WholesaleCustomers scope
' 1 delete affects RetailCustomers scope
Utility.MakeDataChangesOnNode(Utility.ConnStr_SqlSync_Server, "Customer")
' Synchronize again. Three changes were made on the server.
syncOrchestrator = New SampleSyncOrchestrator(New SqlCeSyncProvider("WholesaleCustomers", clientSqlCe1Conn, "Sync"), New SqlSyncProvider("WholesaleCustomers", serverConn, Nothing, "Sync"))
syncStats = syncOrchestrator.Synchronize()
syncOrchestrator.DisplayStats(syncStats, "subsequent")
syncOrchestrator = New SampleSyncOrchestrator(New SqlSyncProvider("RetailCustomers", clientSqlConn, Nothing, "Sync"), New SqlSyncProvider("RetailCustomers", serverConn, Nothing, "Sync"))
syncStats = syncOrchestrator.Synchronize()
syncOrchestrator.DisplayStats(syncStats, "subsequent")
serverConn.Close()
serverConn.Dispose()
clientSqlConn.Close()
clientSqlConn.Dispose()
clientSqlCe1Conn.Close()
clientSqlCe1Conn.Dispose()
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server)
Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Client)
Console.Write(vbLf & "Press any key to exit.")
Console.Read()
End Sub
End Class
Public Class SampleSyncOrchestrator
Inherits SyncOrchestrator
Public Sub New(ByVal localProvider As RelationalSyncProvider, ByVal remoteProvider As RelationalSyncProvider)
Me.LocalProvider = localProvider
Me.RemoteProvider = remoteProvider
Me.Direction = SyncDirectionOrder.UploadAndDownload
End Sub
Public Sub DisplayStats(ByVal syncStatistics As SyncOperationStatistics, ByVal syncType As String)
Console.WriteLine([String].Empty)
If syncType = "initial" Then
Console.WriteLine("****** Initial Synchronization ******")
ElseIf syncType = "subsequent" Then
Console.WriteLine("***** Subsequent Synchronization ****")
End If
Console.WriteLine("Start Time: " & syncStatistics.SyncStartTime)
Console.WriteLine("Total Changes Uploaded: " & syncStatistics.UploadChangesTotal)
Console.WriteLine("Total Changes Downloaded: " & syncStatistics.DownloadChangesTotal)
Console.WriteLine("Complete Time: " & syncStatistics.SyncEndTime)
Console.WriteLine([String].Empty)
End Sub
End Class
End Namespace