Procédure : utiliser le suivi des modifications de SQL Server
Cette rubrique présente le suivi des modifications de SQL Server et décrit une application console qui effectue une synchronisation bidirectionnelle entre une base de données SQL Server et une base de données SQL Server Compact. Si le serveur exécute SQL Server 2008, il est conseillé d'utiliser le suivi des modifications de SQL Server. Si le serveur exécute une base de données différente, consultez Procédure : utiliser un système de suivi des modifications personnalisé.
Vue d'ensemble du suivi des modifications SQL Server
Dans la plupart des exemples de cette documentation, le suivi des modifications est géré par un ensemble de colonnes et de déclencheurs qui sont ajoutés aux tables de base, et d'autres tables pour effectuer le suivi des opérations de suppression. Pour plus d'informations, consultez Suivi des modifications dans la base de données serveur. Ce type de suivi est utile pour les bases de données autres que les bases de données SQL Server 2008. Il présente toutefois les inconvénients suivants :
Des modifications de schéma sont requises dans la base de données serveur. Ces modifications peuvent affecter d'autres applications ou ne pas être possibles.
Les déclencheurs sont exécutés pour chaque modification apportée à une ligne. Les performances sont affectées.
La logique de conservation des versions de lignes et suppressions appropriées peut devenir complexe.
Si une base de données serveur contient des transactions dont l'exécution est longue, les modifications de données peuvent être oubliées lors de la synchronisation, sauf si ces transactions sont correctement gérées. Cela peut entraîner des incohérences dans les données.
Le suivi des modifications de SQL Server résout ces problèmes et offre un moyen simple d'effectuer le suivi des modifications. Lorsque le suivi des modifications est activé sur une table, le Moteur de base de données SQL Server gère les informations sur les modifications apportées aux tables. Les applications peuvent alors utiliser des fonctions de suivi des modifications pour déterminer quelles lignes ont changé et obtenir des informations à propos des modifications. Les principaux avantages du suivi des modifications de SQL Server sont les suivants :
Pour les scénarios de synchronisation hors connexion qui utilisent Sync Framework, vous n'avez pas à créer de déclencheurs, de colonnes timestamp, d'autres colonnes ou d'autres tables.
Les modifications sont suivies en fonction de l'heure de validation et non au moment où les opérations DML se produisent.
Les fonctions retournent des modifications incrémentielles des tables et des informations de version. Ces fonctions fournissent des résultats fiables et faciles à utiliser, même en cas de transactions qui se chevauchent ou qui ne sont pas validées.
La diminution des performances est minime.
Les données de suivi des modifications peuvent être nettoyées automatiquement.
Le reste de cette rubrique illustre l'utilisation du suivi des modifications de SQL Server dans une application Sync Framework. Pour plus d'informations sur le suivi des modifications, consultez la documentation en ligne de SQL Server 2008.
Utilisation du suivi des modifications SQL Server avec des fournisseurs de bases de données hors connexion Sync Framework
Cette section de la rubrique décrit comment activer le suivi des modifications et comment les requêtes de suivi des modifications sont utilisées pour déterminer les modifications de données à télécharger pour un client. Les informations de cette section décrivent l'utilisation des commandes créées manuellement pour sélectionner les modifications sur le serveur. Pour plus d'informations sur l'utilisation du générateur de l'adaptateur de synchronisation pour créer des commandes automatiquement, consultez Mise en route : synchronisation client et serveur.
Activation du suivi des modifications SQL Server
Le suivi des modifications est activé sur la base de données serveur, puis pour chaque table qui nécessite un suivi. Les exemples de code suivants affichent le schéma pour la table Sales.Customer dans l'un des exemples de bases de données Sync Framework ainsi que le code requis pour activer le suivi des modifications pour cette table. Chaque table doit avoir une clé primaire. Les clés primaires doivent être uniques sur tous les nœuds et ne doivent pas être réutilisées : si une ligne est supprimée, la clé primaire de cette ligne ne doit pas être utilisée pour une autre ligne. Les colonnes d'identité ne sont généralement pas appropriées à des environnements distribués. Pour plus d'informations sur les clés primaires, consultez Sélection d'une clé primaire appropriée pour un environnement distribué.
Les options de suivi des modifications qui sont spécifiées en exécutant le code suivant comprennent la durée de conservation des métadonnées de suivi et le nettoyage automatique de ces métadonnées. Pour plus d'informations sur les options de suivi, consultez les rubriques « Suivi des modifications », « ALTER DATABASE » et « ALTER TABLE » dans la documentation en ligne 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
Notes
Nous vous recommandons fortement d'utiliser des transactions d'instantané lorsque vous interrogez les informations de modification. Elles permettent en effet de garantir la cohérence des informations de modification et d'éviter les situations de concurrence liées à la tâche de nettoyage en arrière-plan. Pour plus d'informations sur le niveau d'isolement d'instantané, consultez « Niveaux d'isolation du moteur de base de données » dans la documentation en ligne de SQL Server 2008.
Identification des modifications de données à télécharger sur un client
Une fois le suivi des modifications activé, les applications Sync Framework utilisent les fonctions de suivi des modifications et les ancres pour identifier les insertions, les mises à jour et les suppressions devant être téléchargées. Une ancre est simplement une limite dans le temps qui permet de définir un ensemble de modifications à synchroniser. Prenons les requêtes suivantes :
Requête que vous spécifiez pour la propriété SelectIncrementalInsertsCommand. La requête suivante sélectionne les insertions incrémentielles à appliquer au client à partir de la table Sales.Customer du serveur :
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 c'est la première session de synchronisation pour un client (@sync_initialized = 0), le schéma et toutes les lignes sont sélectionnés directement dans la table de base Sales.Customer. Lors de synchronisations postérieures, les lignes récemment insérées sont sélectionnées en effectuant une jointure interne entre la table de base et sa table de suivi des modifications. Les métadonnées de la table de suivi des modifications sont exposées par la fonction CHANGETABLE(). Cette fonction prend comme paramètres le nom de la table de base et la version de suivi des modifications stockée au cours de la synchronisation précédente. La colonne SYS_CHANGE_OPERATION définit le type de modification stockée dans une ligne de la table de suivi des modifications.
Notes
Les requêtes doivent également vérifier si des modifications requises ont été nettoyées dans les tables de suivi. Pour obtenir un exemple, consultez « Spécification d'une commande pour sélectionner des insertions incrémentielles sur le serveur à appliquer au client », plus loin dans cette rubrique.
Requête que vous spécifiez pour la propriété SelectNewAnchorCommand. Cette requête extrait une valeur jusqu'à une date et heure. La requête suivante récupère une nouvelle valeur d'ancre à partir du serveur à l'aide de la fonction change_tracking_current_version(). Cette fonction SQL Server intégrée retourne un entier de version qui est associé à la dernière transaction validée qui a fait l'objet d'un suivi des modifications.
SELECT @sync_new_received_anchor = change_tracking_current_version()
La valeur entière est stockée dans la base de données client et est utilisée par les commandes qui synchronisent les modifications. Durant chaque session de synchronisation, la nouvelle valeur d'ancre et l'ancienne valeur d'ancre de la session de synchronisation précédente sont utilisées : cela représente l'ensemble des modifications survenues entre ces limites inférieure et supérieure.
Dans certains cas, une application nécessite uniquement un sous-ensemble des données sur chaque client. Vous pouvez inclure d'autres conditions dans la clause WHERE pour filtrer les données. Pour plus d'informations, consultez Procédure : filtrer des lignes et des colonnes. La section « Filtres basés sur des colonnes non-clés » contient des informations importantes sur le filtrage avec le suivi des modifications SQL Server.
Requêtes qui sont exécutées lors du processus de synchronisation
Lors de la première synchronisation de la table Sales.Customer, le processus suivant se produit :
La nouvelle commande d'ancre est exécutée. La commande renvoie une valeur entière, telle que 372. Cette valeur est stockée dans la base de données client. La table n'a jamais été synchronisée. Par conséquent, aucune valeur d'ancre n'a été stockée dans la base de données client à partir d'une synchronisation antérieure. Dans ce cas, Sync Framework utilise une valeur 0. La requête qui est exécutée par Sync Framework est la suivante :
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
Au cours de la deuxième session de synchronisation, la nouvelle commande d'ancre est exécutée. Des lignes ont été insérées depuis la dernière session synchronisation. Par conséquent, la commande retourne la valeur 375. La table a déjà été synchronisée. Par conséquent, Sync Framework peut récupérer la valeur d'ancre 372 stockée dans la base de données client à partir de la synchronisation antérieure. La requête qui est exécutée est la suivante. La requête télécharge uniquement les lignes de la table qui ont été insérées entre les deux valeurs d'ancre.
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
Pour les exemples de commandes de mise à jour et de suppression, consultez l'exemple de code complet plus loin dans cette rubrique.
Identification du client ayant effectué une modification de données
Il est important d'identifier le client qui a modifié des données, et ce pour deux raisons :
Pour prendre en charge la détection et la résolution de conflits lors de la synchronisation par téléchargement ascendant uniquement et de la synchronisation bidirectionnelle.
Si le serveur et le client, ou plusieurs clients, ont la possibilité de modifier une ligne spécifique, il peut être utile d'identifier la personne qui a effectué la modification. Ces informations vous permettent d'écrire du code, par exemple, qui stipule la priorité d'une modification par rapport à une autre. Sans ces informations, la dernière modification apportée à la ligne est conservée.
Pour éviter de répercuter les modifications sur le client lors de la synchronisation bidirectionnelle.
Sync Framework télécharge en premier lieu les modifications sur le serveur, et ensuite sur le client. Si vous n'identifiez pas le client qui a effectué une modification, cette dernière sera téléchargée sur le serveur, puis téléchargée à nouveau sur le client lors de la même session de synchronisation. Cette répercussion des modifications est requise dans certains cas, mais pas dans tous les types de scénarios.
Le suivi des modifications fournit un mécanisme permettant de stocker des données d'application avec les informations sur les modifications lorsque les lignes changent. Ces données d'application permettent d'identifier le client qui apporte une modification. L'identité du client qui a apporté une modification peut ensuite être retournée lorsque vous demandez des modifications.
La colonne SYS_CHANGE_CONTEXT peut être utilisée avec la propriété ClientId pour déterminer les clients qui ont effectué les différentes insertions, mises à jour ou suppressions. La première fois qu'une table est synchronisée à l'aide d'une méthode autre que la synchronisation par instantané, Sync Framework stocke une valeur GUID sur le client qui identifie ce client. Cet ID est transmis à l'objet DbServerSyncProvider de sorte qu'il puisse être utilisé par les commandes de chaque objet SyncAdapter. La valeur d'ID est disponible par le biais de la propriété ClientId et des variables de session @sync\_client\_id et @sync\_client\_id\_binary. Prenons la requête Transact-SQL suivante :
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));
Cette requête est similaire à la requête précédente qui assure le suivi des insertions effectuées sur le serveur. L'instruction supplémentaire dans chaque clause WHERE garantit que les seules insertions qui sont téléchargées sont celles qui n'ont pas été effectuées par le client en cours de synchronisation. Sync Framework permet également aux applications d'identifier les clients en utilisant un entier au niveau du serveur et non une valeur GUID. Pour plus d'informations, consultez Procédure : utiliser des variables de session.
Pour effectuer le suivi du client qui a apporté une modification de donnée appliquée au serveur, utilisez la clause WITH CHANGE_TRACKING_CONTEXT. Avant d'exécuter une instruction INSERT, UPDATE ou DELETE, affectez à CHANGE_TRACKING_CONTEXT la valeur de la variable de session @sync\_client\_id ou @sync\_client\_id\_binary. Ces informations sont stockées dans la table de suivi des modifications afin que les applications puissent suivre le contexte dans lequel une modification a été apportée. Pour Sync Framework, il s'agit en général de l'ID client. Toutefois, vous pouvez stocker toute valeur qui s'ajuste à une colonne 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
Fonctionnement et exécution de l'exemple d'application
Cette section de la rubrique contient le code d'application qui est requis pour configurer et effectuer la synchronisation. Vous pouvez obtenir des informations simplement en examinant l'exemple de code. Toutefois, il est plus instructif d'exécuter cet exemple de code et d'observer son fonctionnement. Avant d'exécuter le code, assurez-vous que les éléments suivants sont installés :
Sync Framework
L'application nécessite des références à Microsoft.Synchronization.Data.dll, Microsoft.Synchronization.dll, Microsoft.Synchronization.Data.Server.dll et Microsoft.Synchronization.Data.SqlServerCe.dll.
SQL Server 2008
L'exemple de code utilise localhost dans les chaînes de connexion. Pour utiliser un serveur distant, remplacez localhost par le nom de serveur approprié.
Exemples de bases de données Sync Framework. Pour plus d'informations, consultez Scripts d'installation pour les rubriques de procédures sur le fournisseur de bases de données.
Si vous avez lu la rubrique Architecture et classes pour la synchronisation client et serveur, vous connaissez déjà les principales classes qui sont utilisées dans l'application. L'application est composée des classes suivantes :
SampleSyncAgent. Cette classe est dérivée de l'objet SyncAgent.
SampleServerSyncProvider. Cette classe est dérivée de l'objet DbServerSyncProvider et contient l'objet SyncAdapter ainsi qu'un ensemble de commandes qui interrogent les tables de suivi des modifications.
SampleClientSyncProvider. Cette classe est dérivée de l'objet SqlCeClientSyncProvider et contient un objet SyncTable.
SampleStats. Cette classe utilise les statistiques qui sont retournées par l'objet SyncAgent.
Program. Cette classe configure la synchronisation et appelle des méthodes à partir de la classe Utility.
Utility. Cette classe gère toutes les fonctionnalités qui ne sont pas directement liées à la synchronisation, telles que la détention d'informations de chaîne de connexion et l'apport de modifications aux bases de données client et serveur. Pour plus d'informations, consultez Classe d'utilitaire pour les rubriques de procédures sur le fournisseur de bases de données.
Éléments clés de l'API
Avant d'examiner l'exemple de code complet, nous vous recommandons de passer en revue les exemples suivants. Ces exemples illustrent plusieurs sections clés de l'API qui sont utilisées dans cette application. L'intégralité de l'exemple de code qui est affiché est contenue dans la classe SampleServerSyncProvider. Outre les commandes qui sont illustrées dans cette section, l'exemple de code complet contient une commande pour appliquer les insertions au serveur et des commandes pour sélectionner et appliquer les suppressions.
Le premier exemple s'applique directement à la propriété DbServerSyncProviderSelectNewAnchorCommand. Les autres exemples s'appliquent à l'objet SyncAdapter pour la table Sales.Customer.
Récupération d'une nouvelle valeur d'ancre à partir du serveur
L'exemple de code suivant spécifie la commande permettant de récupérer une nouvelle valeur d'ancre à partir du serveur. La classe SyncSession contient plusieurs constantes de chaîne utilisables dans les commandes de synchronisation. SyncNewReceivedAnchor est l'une de ces constantes. Vous pouvez également utiliser le littéral @sync\_new\_received\_anchor directement dans vos requêtes.
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
Spécification d'une commande pour sélectionner des insertions incrémentielles sur le serveur à appliquer au client
L'exemple de code suivant spécifie une commande permettant de sélectionner les insertions incrémentielles du serveur à appliquer au client. Toutes les requêtes de modifications incrémentielles vérifient si les modifications requises ont été nettoyées dans la table de suivi des modifications. Cette vérification démarre par la clause suivante et génère une erreur si les modifications ont été nettoyées :
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
Spécification d'une commande pour sélectionner des mises à jour incrémentielles sur le serveur à appliquer au client
L'exemple de code suivant spécifie une commande permettant de sélectionner les mises à jour incrémentielles du serveur à appliquer au client.
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
Spécification d'une commande pour appliquer des mises à jour incrémentielles du client au serveur
Dans l'exemple de code suivant, l'instruction UPDATE met à jour la table de base et retourne le nombre de lignes affectées. Si le nombre de lignes est 0, une erreur ou un conflit s'est produit. Pour plus d'informations, consultez Procédure : gérer les conflits de données et les erreurs.
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
Sélection de lignes en conflit
La commande suivante sélectionne les lignes en conflit dans la base de données serveur si les lignes existent toujours dans la table de 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
La commande suivante sélectionne les lignes en conflit dans la base de données serveur si les lignes ont été supprimées de la table de 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
Pour plus d'informations sur la gestion des conflits de données, consultez Procédure : gérer les conflits de données et les erreurs.
Exemple de code complet
L'exemple de code complet ci-dessous inclut les exemples de code décrits précédemment, ainsi que du code supplémentaire pour effectuer la synchronisation.
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