SQL Server の変更追跡を使用する方法
このトピックでは、SQL Server の変更追跡の概要を示し、SQL Server データベースと SQL Server Compact データベースとの間で双方向同期を実行するコンソール アプリケーションについて説明します。サーバーで SQL Server 2008 を実行している場合は、SQL Server の変更追跡を使用することをお勧めします。サーバーで別のデータベースを実行している場合は、「カスタム変更追跡システムを使用する方法」を参照してください。
SQL Server の変更追跡の概要
このドキュメントの多くの例では、ベース テーブルに追加される一連の列とトリガー、および削除操作を追跡するための追加のテーブルにより、変更を追跡しています。詳細については、「サーバー データベースの変更の追跡」を参照してください。この種の追跡は SQL Server 2008 以外のデータベースに役立ちますが、次のような欠点があります。
サーバー データベースでスキーマを変更する必要があります。その結果、他のアプリケーションが影響を受ける可能性があります。また、変更が不可能な場合もあります。
行を変更するたびにトリガーが起動されます。これはパフォーマンスに影響を与えます。
正しい行バージョンおよび削除を管理するためのロジックが複雑になる場合があります。
サーバー データベースに実行時間の長いトランザクションが存在する場合、これらのトランザクションが適切に処理されていないと、同期中にデータの変更が無視される場合があります。その結果、データの不整合が生じます。
SQL Server の変更追跡では、これらの問題に対処し、変更を追跡するための簡単な手段が提供されます。テーブルで変更の追跡を有効にすると、そのテーブルに加えられた変更に関する情報が SQL Server データベース エンジンによって保持されます。アプリケーションでは、その後変更追跡関数を使用して、変更された行を特定したり、変更に関する情報を取得したりできます。SQL Server の変更追跡の主な利点は次のとおりです。
Sync Framework を使用するオフライン同期シナリオでは、トリガー、タイムスタンプ列、その他の追加の列、または追加のテーブルを作成する必要がありません。
変更は、DML 操作が行われたときではなくコミット時に追跡されます。
関数は、テーブルへの増分変更とバージョン情報を返します。これらの関数は、重複するトランザクションやコミットされていないトランザクションが存在する場合でも、信頼性が高く使いやすい結果を返します。
パフォーマンスのオーバーヘッドは最小限です。
変更追跡データは自動的にクリーンアップされます。
このトピックの残りの部分では、Sync Framework アプリケーションで SQL Server の変更追跡を使用する方法を説明します。変更追跡の詳細については、SQL Server 2008 オンライン ブックを参照してください。
Sync Framework オフライン データベース プロバイダーでの SQL Server の変更追跡の使用
ここでは、変更追跡を有効にする方法と、クライアントにダウンロードするデータの変更を変更追跡クエリを使用して特定する方法について説明します。このセクションでは、手動で作成したコマンドを使用してサーバーから変更を選択する方法について説明します。同期アダプター ビルダーを使用してコマンドを作成する方法については、「はじめに : クライアントとサーバーの同期」を参照してください。
SQL Server の変更追跡の有効化
変更追跡は、サーバー データベースで有効にした後、追跡が必要な各テーブルでも有効にします。次のコード例では、Sync Framework サンプル データベースの 1 つに含まれている Sales.Customer
テーブルのスキーマと、そのテーブルの変更追跡を有効にするための方法を示します。各テーブルは、主キーを必要とします。主キーはすべてのノードで一意である必要があるため、再利用できません。つまり、行が削除された場合、その行の主キーを別の行に使用することはできません。一般に、ID 列は分散環境に対して適切な選択肢ではありません。主キーの詳細については、「分散環境に適切な主キーの選択」を参照してください。
以下のコードを実行して指定する変更追跡オプションには、追跡メタデータの保持期間と、メタデータを自動的にクリーンアップするかどうかが含まれています。追跡オプションの詳細については、SQL Server 2008 オンライン ブックの「変更追跡」、「ALTER DATABASE」、および「ALTER TABLE」の各トピックを参照してください。
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
注意
変更情報のクエリにはスナップショット トランザクションを使用することを強くお勧めします。これにより、変更情報の一貫性を確保し、バックグラウンドのクリーンアップ タスクに関連する競合状態を回避することができます。スナップショット分離の詳細については、SQL Server 2008 オンライン ブックの「データベース エンジンにおける分離レベル」を参照してください。
クライアントにダウンロードするデータ変更の決定
変更追跡を有効にした後、Sync Framework アプリケーションで、変更追跡関数と "アンカー" を使用して、ダウンロードする挿入、更新、および削除を特定します。アンカーとは、同期する一連の変更を定義する際に使用される特定の時点です。次のクエリについて考えてみます。
SelectIncrementalInsertsCommand プロパティに指定するクエリ。次のクエリでは、クライアントに適用する増分挿入をサーバーの
Sales.Customer
テーブルから選択します。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
これがクライアントの最初の同期セッションである場合 (
@sync_initialized = 0
)、スキーマとすべての行はSales.Customer
ベース テーブルから直接選択されます。その後の同期では、ベース テーブルとその変更追跡テーブルの間で内部結合を実行することで、新たに挿入された行が選択されます。変更追跡テーブルのメタデータはCHANGETABLE()
関数によって公開されます。この関数は、パラメーターとして、ベース テーブル名と、前回の同期で格納された変更追跡バージョンを受け取ります。SYS_CHANGE_OPERATION
列は、変更追跡テーブルの行に格納されている変更の種類を定義します。注意
クエリでは、追跡テーブルから必要な変更がクリーンアップされているかどうかもチェックする必要があります。例については、この後の「サーバーから増分挿入を選択してクライアントに適用するコマンドを指定する」を参照してください。
>SelectNewAnchorCommand プロパティに指定するクエリ。このクエリでは、特定の時点の値を取得します。次のクエリは、
change_tracking_current_version()
関数を使用して、サーバーから新しいアンカー値を取得します。この組み込み SQL Server 関数は、変更追跡によって追跡されている最後にコミットされた追跡トランザクションに関連付けられているバージョンを表す整数を返します。SELECT @sync_new_received_anchor = change_tracking_current_version()
この整数値は、クライアント データベースに格納され、変更を同期するコマンドによって使用されます。各同期セッション中に、新しいアンカー値と、前回の同期セッションの最後のアンカー値が使用されます。その結果、この範囲の一連の変更が同期されます。
場合によっては、アプリケーションで各クライアントのデータのサブセットのみが必要になることがあります。WHERE 句には、データのフィルター選択を行う追加の条件を含めることができます。詳細については、「行および列をフィルター選択する方法」を参照してください。「非キー列に基づいたフィルター」セクションに、SQL Server の変更追跡を使用したフィルター選択に関する重要な情報が記載されています。
同期プロセス中に実行されるクエリ
Sales.Customer
テーブルが初めて同期したときには、次の処理が発生します。
新しいアンカー コマンドが実行されます。コマンドによって
372
などの整数値が返されます。この値は、クライアント データベースに格納されます。テーブルはまだ同期されていません。したがって、前回の同期でクライアント データベースに格納されたアンカー値はありません。この場合、Sync Framework は0
の値を使用します。Sync Framework によって実行されるクエリは次のとおりです。exec sp_executesql N'IF @sync_initialized = 0 SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] FROM Sales.Customer LEFT OUTER JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT ON CT.[CustomerId] = Sales.Customer.[CustomerId] ELSE BEGIN SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT ON CT.[CustomerId] = Sales.Customer.[CustomerId] WHERE (CT.SYS_CHANGE_OPERATION = ''I'' AND CT.SYS_CHANGE_CREATION_VERSION <= @sync_new_received_anchor) END', N'@sync_initialized int, @sync_last_received_anchor bigint, @sync_new_received_anchor bigint', @sync_initialized=0, @sync_last_received_anchor=0, @sync_new_received_anchor=372
2 回目の同期セッション中に、新しいアンカー コマンドが実行されます。前回のセッションの後に行が挿入されました。したがって、コマンドは値
375
を返します。テーブルは以前に同期されたことがあります。したがって、Sync Framework は、クライアント データベースに格納されたアンカー値372
を前回の同期から取得できます。実行されるクエリは次のとおりです。このクエリにより、2 つのアンカー値の間に挿入された行のみがテーブルからダウンロードされます。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
更新コマンドおよび削除コマンドの例については、この後の完全なコード例を参照してください。
データ変更を行ったクライアントの特定
データの変更を行ったクライアントを特定する理由は、主に 2 つあります。
アップロードのみの同期および双方向同期で競合検出と解決をサポートするため。
サーバーと 1 つまたは複数のクライアントで特定の行を変更できる場合、変更者を特定することが必要になる場合があります。この情報を使用すれば、たとえば、ある変更を別の変更より優先するコードを記述できます。この情報がなければ、行に対して最後に実行された変更が保存されます。
双方向同期中に変更がクライアントにエコー バックされないようにするため。
Sync Framework は、先に変更をサーバーにアップロードしてから、変更をクライアントにダウンロードします。変更を行ったクライアントの ID を追跡しなければ、変更はサーバーにアップロードされた後、同じ同期セッション中にクライアントに再びダウンロードされます。こうした変更のエコーが必要になる場合もありますが、必要にならない場合もあります。
変更追跡では、行が変更されたときにアプリケーション データを変更情報と共に格納するメカニズムが用意されています。このアプリケーション データを使用して、変更を行ったクライアントを識別できます。変更を行ったクライアントの ID は、変更のクエリを実行したときに返されます。
SYS_CHANGE_CONTEXT
列を ClientId プロパティと共に使用して、各挿入、更新、または削除を行ったクライアントを特定することができます。スナップショットの同期以外の方法でテーブルを初めて同期すると、変更を行ったクライアントを識別する GUID がそのクライアントに格納されます。この ID は、各 SyncAdapter オブジェクトのコマンドで使用できるように、DbServerSyncProvider に渡されます。ID 値は ClientId プロパティおよび @sync_client_id
セッション変数および @sync_client_id_binary
セッション変数で使用できます。次の Transact-SQL クエリについて考えてみます。
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));
このクエリは、サーバーで行われた挿入を追跡する上述のクエリと似ています。それぞれの WHERE
句で追加ステートメントを使用することにより、現在同期中のクライアントで行われたのではない挿入だけがダウンロード対象の挿入になるように限定しています。Sync Framework を使用すると、アプリケーションが GUID 値ではなく整数をサーバーで使用して、クライアントを識別することもできます。詳細については、「セッション変数を使用する方法」を参照してください。
サーバーで適用されるデータ変更を行ったクライアントを追跡するには、WITH CHANGE_TRACKING_CONTEXT
句を使用します。INSERT ステートメント、UPDATE ステートメント、または DELETE ステートメントを実行する前に、CHANGE_TRACKING_CONTEXT
を @sync_client_id
セッション変数または @sync_client_id_binary
セッション変数の値に設定します。この情報は変更追跡テーブルに格納されるので、アプリケーションは変更が行われたコンテキストを追跡できます。Sync Framework では、これは通常クライアント ID になります。ただし、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
サンプル アプリケーションの理解と実行
ここでは、同期の構成と実行に必要なアプリケーション コードを示します。コード例は、目を通すことでて理解することもできますが、実行して動作を確認すると、より知識を深めることができます。コードを実行する前に、次のコンポーネントがインストールされていることを確認してください。
Sync Framework
このアプリケーションでは、Microsoft.Synchronization.Data.dll、Microsoft.Synchronization.dll、Microsoft.Synchronization.Data.Server.dll、および Microsoft.Synchronization.Data.SqlServerCe.dll への参照が必要です。
SQL Server 2008
このコード例では、接続文字列に
localhost
を使用しています。リモート サーバーを使用するには、localhost
を適切なサーバー名に変更します。Sync Framework サンプル データベース。詳細については、データベース プロバイダーのセットアップ スクリプトに関するトピック のトピックを参照してください。
このアプリケーションで使用されている主なクラスについては、「クライアントとサーバーの同期のアーキテクチャとクラス」で説明されています。このアプリケーションは、次のクラスで構成されます。
SampleSyncAgent
. このクラスは SyncAgent から派生しています。SampleServerSyncProvider
. このクラスは DbServerSyncProvider から派生し、SyncAdapter、および変更追跡テーブルのクエリを実行する一連のコマンドを含んでいます。SampleClientSyncProvider
. このクラスは、SqlCeClientSyncProvider から派生し、SyncTable を含んでいます。SampleStats
: このクラスでは、SyncAgent によって返される統計情報を使用します。Program
: このクラスは、同期を設定し、Utility
クラスからメソッドを呼び出します。Utility
: このクラスは、接続文字列情報の保持やサーバー データベースとクライアント データベースへの変更など、同期に直接関連しないすべての機能を処理します。詳細については、「データベース プロバイダーの Utility クラスに関するトピック」を参照してください。
API の主要部分
完全なコード例を見る前に、次の例を確認することをお勧めします。これらの例は、このアプリケーションで使用されている API のいくつかの主要なセクションを示しています。ここに示すコード例はすべて SampleServerSyncProvider
クラスに含まれています。完全なコード例には、このセクションに示したコマンドの他に、挿入をサーバーに適用するコマンドと、削除を選択および適用するコマンドが含まれています。
最初の例は、DbServerSyncProvider プロパティ SelectNewAnchorCommand に直接適用されます。その他の例は、Sales.Customer
テーブルの SyncAdapter オブジェクトに適用されます。
サーバーから新しいアンカー値を取得する
次のコード例では、サーバーから新しいアンカー値を取得するコマンドを指定しています。SyncSession クラスには、同期コマンドで使用できるいくつかの文字列定数が含まれています。SyncNewReceivedAnchor はこうした定数の 1 つです。また、クエリでは、リテラルの @sync_new_received_anchor
を直接使用することもできます。
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
サーバーから増分挿入を選択してクライアントに適用するコマンドを指定する
次のコード例では、サーバーから増分挿入を選択してクライアントに適用するコマンドを指定します。増分変更のすべてのクエリは、必要な変更が変更追跡テーブルからクリーンアップされているかどうかをチェックします。このチェックは次の句で始まり、変更がクリーンアップされている場合にエラーを生成します。
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
サーバーから増分更新を選択してクライアントに適用するコマンドを指定する
次のコード例では、サーバーから増分更新を選択してクライアントに適用するコマンドを指定します。
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
クライアントからサーバーに増分更新を適用するコマンドを指定する
次のコード例において、UPDATE
ステートメントは、ベース テーブルを更新し、影響を受けた行の数を返します。行数が 0 の場合はエラーまたは競合が発生します。詳細については、「データの競合とエラーを処理する方法」を参照してください。
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
競合する行を選択する
次のコマンドでは、競合する行がベース テーブル内に存在する場合にサーバー データベースから競合する行を選択します。
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
次のコマンドでは、競合する行がベース テーブルから削除されている場合にサーバー データベースから競合する行を選択します。
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
データの競合に対する処理の詳細については、「データの競合とエラーを処理する方法」を参照してください。
完全なコード例
次の完全なコード例には、既に説明したコード例に加え、同期を実行するためのコードが含まれています。
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