プロジェクトに [ローカル データベース キャッシュ] 項目を追加してローカル SQL Server Compact 3.5 データベース キャッシュを構成し、Microsoft Synchronization Services for ADO.NET を有効にする一連の部分クラスを生成できます。 Visual Studio によって部分クラスが生成されるため、同期機能を追加するコードを作成しても、[データ同期の構成] ダイアログ ボックスで設定を表示して変更することができます。 部分クラスの詳細については、「方法: 1 つのクラスを複数の部分クラスに分割する (クラス デザイナー)」を参照してください。
既定で、[データ同期の構成] ダイアログ ボックスでは、ダウンロード用にのみ Synchronization Services を構成できます。 つまり、データ同期を構成した後、Synchronize() を呼び出しても、サーバーからクライアント データベースに変更がダウンロードされるだけです。 一般的に、同期コードを拡張するには、双方向同期を構成します。 これによって、クライアントからサーバーに変更をアップロードできるようになります。 双方向同期を有効にするには、次の方法を使って、生成されたコードを拡張することをお勧めします。
同期方向を双方向に設定します。
同期の競合を処理するコードを追加します。
同期コマンドから、サーバーのトラッキング列を削除します。
必須コンポーネント
このチュートリアルを実行する前に、「チュートリアル : 接続の頻度があまり高くないアプリケーションの作成」を実行する必要があります。 このチュートリアルを完了すると、[ローカル データベース キャッシュ] 項目を含むプロジェクトと、Windows フォーム アプリケーションが得られます。このアプリケーションを使用して Northwind Customers テーブルの変更をダウンロードし、SQL Server Compact データベースに適用します。 これで、このチュートリアルのソリューションを読み込み、双方向同期の機能を追加するための準備が整います。
注意
お使いのマシンで、Visual Studio ユーザー インターフェイスの一部の要素の名前や場所が、次の手順とは異なる場合があります。 これらの要素は、使用している Visual Studio のエディションや独自の設定によって決まります。 詳細については、「Visual Studio の設定」を参照してください。
OCSWalkthrough ソリューションを開くには
Visual Studio を開きます。
[ファイル] メニューで、既存のソリューションまたはプロジェクトを開き、OCSWalkthrough ソリューションを探します。 これは OCSWalkthrough.sln ファイルです。
同期方向の設定
[データ同期の構成] ダイアログ ボックスで、SyncDirection() プロパティを DownloadOnly() または Snapshot() に設定できます。 双方向同期を有効にするには、変更のアップロードを有効にする各テーブルについて、SyncDirection() プロパティを Bidirectional() に設定します。
同期方向を設定するには
NorthwindCache.sync を右クリックし、[コードの表示] をクリックします。 この操作を初めて実行したときに、ソリューション エクスプローラーの NorthwindCache.sync ノードの下に NorthwindCache ファイルが作成されます。 このファイルに、NorthwindCacheSyncAgent 部分クラスが含まれます。必要に応じて、他のクラスも追加できます。
NorthwindCache クラス ファイルに、次のコードに示すような形で NorthwindCacheSyncAgent.OnInitialized() メソッドを追加します。
partial void OnInitialized() { this.Customers.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional; }
Private Sub OnInitialized() Me.Customers.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional End Sub
コード エディターで Form1 を開きます。
Form1 ファイルにアップロードとダウンロードの統計情報が含まれるように、このファイルの SynchronizeButton_Click イベント ハンドラーのコードを変更します。
MessageBox.Show("Changes downloaded: " + syncStats.TotalChangesDownloaded.ToString() + Environment.NewLine + "Changes uploaded: " + syncStats.TotalChangesUploaded.ToString());
MessageBox.Show("Changes downloaded: " & _ syncStats.TotalChangesDownloaded.ToString & _ Environment.NewLine & _ "Changes uploaded: " & _ syncStats.TotalChangesUploaded.ToString)
アプリケーションのテスト
これで、同期中にアップロードとダウンロードの両方を実行するようにアプリケーションが構成されました。
アプリケーションをテストするには
F5 キーを押します。
フォームで、レコードを更新し、[保存] ボタン (ツール バーのフロッピー ディスクのアイコン) をクリックします。
[同期] をクリックします。
同期されたレコードに関する情報が含まれるメッセージ ボックスが表示されます。 サーバーで変更が発生していない場合でも、1 つの行がアップロードされ、1 つの行がダウンロードされたという情報が表示されます。 クライアントの変更がサーバーに適用された後、それがクライアントにエコー バックされるため、さらにダウンロードが発生します。 詳細については、「How to: Use a Custom Change Tracking System」を参照してください。
[OK] をクリックしてメッセージ ボックスを閉じますが、アプリケーションは実行したままにしてください。
次に、クライアントとサーバー上にある同じレコードを変更して、同期中にわざと競合 (同時実行違反) を発生させます。
アプリケーションをテストして強制的に競合を発生させるには
フォームで、レコードを更新し、[保存] ボタンをクリックします。
アプリケーションを実行している状態で、サーバー エクスプローラーまたはデータベース エクスプローラー (または他のデータベース管理ツール) を使用し、サーバー データベースに接続します。
競合の解決に関する既定の動作を確認するために、サーバー エクスプローラーまたはデータベース エクスプローラーで、フォームで更新したものと同じレコードを更新しますが、別の値に変更して、変更をコミットします。 変更した行から別の行に移動します。
フォームに戻り、[同期] をクリックします。
アプリケーション グリッドとサーバー データベースの更新を確認します。 サーバーに加えた更新によって、クライアント側の更新が上書きされていることに注意してください。 この競合解決処理の方法を変更する方法については、このトピックの次のセクション「同期競合を解決するコードの追加」を参照してください。
同期競合を解決するコードの追加
Synchronization Services では、同期と同期の間にクライアントとサーバーの両方で行が変更された場合、その行は競合状態になります。 Synchronization Services には、競合を検出して解決するために使用できる一連の機能が用意されています。 このセクションでは、クライアントとサーバーで同じ行が更新される場合の競合を処理する基本的なコードを追加します。 その他の競合には、一方のデータベースで行が削除され、もう一方のデータベースでは更新されているケースや、主キーが重複している行が両方のデータベースに挿入されるケースなどがあります。 競合の検出と解決の詳細については、「How to: Handle Data Conflicts and Errors」を参照してください。
注意
サンプル コードでは、競合処理の基本的な例を示します。 競合の処理方法は、実際のアプリケーションやビジネス ロジックの要件に応じて選択してください。
サーバーの ApplyChangeFailed イベントとクライアントの ApplyChangeFailed イベントを処理するコードを追加します。 このイベントは、競合またはエラーが原因で行を適用できない場合に発生します。 このイベントを処理するメソッドでは、競合の種類をチェックし、クライアントの変更をサーバー データベースに強制的に書き込むことでクライアント更新競合またはサーバー更新競合を解決するように指定します。 サーバー データベースに更新を適用する同期コマンドには、変更を強制的に適用するかどうかを判別するロジックが使用されています。 このコマンドは、このトピックの次のセクションである「同期コマンドからのサーバーのトラッキング列の削除」に示すコードに含まれています。
C# と Visual Basic のどちらを使用するかに応じて、競合処理を追加する手順は変わります。
競合処理を追加するには
C# を使用する場合、NorthwindCache.cs と Form1.cs にコードを追加します。 NorthwindCache.cs で、NorthwindCacheSyncAgent クラスの末尾に、次のコードを追加します。
public partial class NorthwindCacheServerSyncProvider { partial void OnInitialized() { this.ApplyChangeFailed += new System.EventHandler<Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs> (NorthwindCacheServerSyncProvider_ApplyChangeFailed); } private void NorthwindCacheServerSyncProvider_ApplyChangeFailed(object sender, Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs e) { if (e.Conflict.ConflictType == Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate) { // Resolve a client update / server update conflict by force writing // the client change to the server database. System.Windows.Forms.MessageBox.Show("A client update / server update conflict " + "was detected at the server."); e.Action = Microsoft.Synchronization.Data.ApplyAction.RetryWithForceWrite; } } } public partial class NorthwindCacheClientSyncProvider { public void AddHandlers() { this.ApplyChangeFailed += new System.EventHandler<Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs> (NorthwindCacheClientSyncProvider_ApplyChangeFailed); } private void NorthwindCacheClientSyncProvider_ApplyChangeFailed(object sender, Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs e) { if (e.Conflict.ConflictType == Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate) { // Resolve a client update / server update conflict by keeping the // client change. e.Action = Microsoft.Synchronization.Data.ApplyAction.Continue; } } }
Form1.cs で、SynchronizeButton_Click イベント ハンドラーのコードを変更し、前の手順で NorthwindCache.cs に追加した AddHandler メソッドを呼び出すようにします。
NorthwindCacheSyncAgent syncAgent = new NorthwindCacheSyncAgent(); NorthwindCacheClientSyncProvider clientSyncProvider = (NorthwindCacheClientSyncProvider)syncAgent.LocalProvider; clientSyncProvider.AddHandlers(); Microsoft.Synchronization.Data.SyncStatistics syncStats = syncAgent.Synchronize();
Visual Basic を使用している場合は、NorthwindCache.vb で、NorthwindCacheSyncAgent クラスの End Class ステートメントの後に次のコードを追加します。
Partial Public Class NorthwindCacheServerSyncProvider Private Sub NorthwindCacheServerSyncProvider_ApplyChangeFailed( _ ByVal sender As Object, ByVal e As _ Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs) _ Handles Me.ApplyChangeFailed If e.Conflict.ConflictType = _ Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate Then ' Resolve a client update / server update conflict by force writing ' the client change to the server database. MessageBox.Show("A client update / server update" & _ " conflict was detected at the server.") e.Action = Microsoft.Synchronization.Data.ApplyAction.RetryWithForceWrite End If End Sub End Class Partial Public Class NorthwindCacheClientSyncProvider Private Sub NorthwindCacheClientSyncProvider_ApplyChangeFailed( _ ByVal sender As Object, ByVal e As _ Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs) _ Handles Me.ApplyChangeFailed If e.Conflict.ConflictType = _ Microsoft.Synchronization.Data.ConflictType.ClientUpdateServerUpdate Then ' Resolve a client update / server update conflict by keeping the ' client change. e.Action = Microsoft.Synchronization.Data.ApplyAction.Continue End If End Sub End Class
競合解決を同期して表示するには
F5 キーを押します。
フォームで、レコードを更新し、[保存] ボタンをクリックします。
サーバー エクスプローラーまたはデータベース エクスプローラーで、フォームで更新したものと同じレコードを更新しますが、別の値に変更し、変更をコミットします。
フォームに戻り、[同期] をクリックします。
アプリケーション グリッドとサーバー データベースの更新を確認します。 クライアントに加えた更新によって、サーバー側の更新が上書きされていることに注意してください。
同期コマンドからのサーバーのトラッキング列の削除
ローカル データベース キャッシュが作成されると、サーバー データベースの変更のトラッキングに使用された列がクライアントにダウンロードされます。 このチュートリアルの場合、トラッキング列は CreationDate と LastEditDate です。 双方向同期をサポートし、クライアントとサーバーのデータ収束を保証するためには、サーバー データベースに変更を適用する SQL コマンドからこの列を削除します。 さらに、サーバーの変更を選択してクライアントに適用するコマンドからも、この列を削除できますが、削除するかどうかは任意です。 クライアント データベースでは一部のスキーマの変更に制限があるため、この列を削除することはできません。 同期コマンドの詳細については、「How to: Specify Snapshot, Download, Upload, and Bidirectional Synchronization」を参照してください。
注意
SQL Server 2008 の変更トラッキングを使用する場合、トラッキング列はテーブルに追加されません。 この場合、サーバーに変更を適用するコマンドを変更する必要はありません。
次に示すコードでは、Customers テーブルの SyncAdapter オブジェクトにプロパティとして設定されている 2 つのコマンド、InsertCommand() プロパティと UpdateCommand() プロパティを再定義しています。 このコマンドは [データ同期の構成] ダイアログ ボックスで生成されたもので、CreationDate 列と LastEditDate 列に対する参照を含んでいました。 次のコードでは、CustomersSyncAdapter クラスの OnInitialized メソッドで、これらのコマンドが再定義されます。 DeleteCommand() プロパティは CreationDate 列にも LastEditDate 列にも影響しないため、再定義されません。
各 SQL コマンドの変数を使用して、Synchronization Services、クライアント、およびサーバーの間でデータとメタデータがやり取りされます。 以降に示すコマンドでは、次のセッション変数が使用されます。
@sync\_row\_count : サーバーで行われた直前の操作の影響を受けた行の数を返します。 SQL Server データベースでは、@@rowcount によってこの変数の値が得られます。
@sync\_force\_write : 競合またはエラーが原因で失敗した変更を強制的に適用するために使用されます。
@sync\_last\_received\_anchor : セッションの際に同期される一連の変更を定義するために使用されます。
セッション変数の詳細については、「How to: Use Session Variables」を参照してください。
同期コマンドからトラッキング列を削除するには
NorthwindCacheServerSyncProvider クラスの End Class ステートメントの後にある NorthwindCache クラス (NorthwindCache.vb または NorthwindCache.cs) に、次のコードを追加します。
public partial class CustomersSyncAdapter { partial void OnInitialized() { // Redefine the insert command so that it does not insert values // into the CreationDate and LastEditDate columns. System.Data.SqlClient.SqlCommand insertCommand = new System.Data.SqlClient.SqlCommand(); insertCommand.CommandText = "INSERT INTO dbo.Customers ([CustomerID], [CompanyName], " + "[ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], " + "[Country], [Phone], [Fax] )" + "VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, " + "@Region, @PostalCode, @Country, @Phone, @Fax) SET @sync_row_count = @@rowcount"; insertCommand.CommandType = System.Data.CommandType.Text; insertCommand.Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar); insertCommand.Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Address", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@City", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Region", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Country", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar); insertCommand.Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int); insertCommand.Parameters["@sync_row_count"].Direction = System.Data.ParameterDirection.Output; this.InsertCommand = insertCommand; // Redefine the update command so that it does not update values // in the CreationDate and LastEditDate columns. System.Data.SqlClient.SqlCommand updateCommand = new System.Data.SqlClient.SqlCommand(); updateCommand.CommandText = "UPDATE dbo.Customers SET [CompanyName] = @CompanyName, [ContactName] " + "= @ContactName, [ContactTitle] = @ContactTitle, [Address] = @Address, [City] " + "= @City, [Region] = @Region, [PostalCode] = @PostalCode, [Country] = @Country, " + "[Phone] = @Phone, [Fax] = @Fax " + "WHERE ([CustomerID] = @CustomerID) AND (@sync_force_write = 1 " + "OR ([LastEditDate] <= @sync_last_received_anchor)) SET @sync_row_count = @@rowcount"; updateCommand.CommandType = System.Data.CommandType.Text; updateCommand.Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Address", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@City", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Region", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Country", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar); updateCommand.Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar); updateCommand.Parameters.Add("@sync_force_write", System.Data.SqlDbType.Bit); updateCommand.Parameters.Add("@sync_last_received_anchor", System.Data.SqlDbType.DateTime); updateCommand.Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int); updateCommand.Parameters["@sync_row_count"].Direction = System.Data.ParameterDirection.Output; this.UpdateCommand = updateCommand; } }
Partial Public Class CustomersSyncAdapter Private Sub OnInitialized() ' Redefine the insert command so that it does not insert values ' into the CreationDate and LastEditDate columns. Dim insertCommand As New System.Data.SqlClient.SqlCommand With insertCommand .CommandText = "INSERT INTO dbo.Customers ([CustomerID], [CompanyName], " & _ "[ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], " & _ "[Country], [Phone], [Fax] )" & _ "VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, " & _ "@Region, @PostalCode, @Country, @Phone, @Fax) SET @sync_row_count = @@rowcount" .CommandType = System.Data.CommandType.Text .Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar) .Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Address", System.Data.SqlDbType.NVarChar) .Parameters.Add("@City", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Region", System.Data.SqlDbType.NVarChar) .Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Country", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar) .Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int) .Parameters("@sync_row_count").Direction = ParameterDirection.Output End With Me.InsertCommand = insertCommand ' Redefine the update command so that it does not update values ' in the CreationDate and LastEditDate columns. Dim updateCommand As New System.Data.SqlClient.SqlCommand With updateCommand .CommandText = "UPDATE dbo.Customers SET [CompanyName] = @CompanyName, [ContactName] " & _ "= @ContactName, [ContactTitle] = @ContactTitle, [Address] = @Address, [City] " & _ "= @City, [Region] = @Region, [PostalCode] = @PostalCode, [Country] = @Country, " & _ "[Phone] = @Phone, [Fax] = @Fax " & _ "WHERE ([CustomerID] = @CustomerID) AND (@sync_force_write = 1 " & _ "OR ([LastEditDate] <= @sync_last_received_anchor)) SET @sync_row_count = @@rowcount" .CommandType = System.Data.CommandType.Text .Parameters.Add("@CompanyName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactName", System.Data.SqlDbType.NVarChar) .Parameters.Add("@ContactTitle", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Address", System.Data.SqlDbType.NVarChar) .Parameters.Add("@City", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Region", System.Data.SqlDbType.NVarChar) .Parameters.Add("@PostalCode", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Country", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Phone", System.Data.SqlDbType.NVarChar) .Parameters.Add("@Fax", System.Data.SqlDbType.NVarChar) .Parameters.Add("@CustomerID", System.Data.SqlDbType.NChar) .Parameters.Add("@sync_force_write", System.Data.SqlDbType.Bit) .Parameters.Add("@sync_last_received_anchor", System.Data.SqlDbType.DateTime) .Parameters.Add("@sync_row_count", System.Data.SqlDbType.Int) .Parameters("@sync_row_count").Direction = ParameterDirection.Output End With Me.UpdateCommand = updateCommand End Sub End Class
アプリケーションのテスト
トラッキング列の更新を同期して表示するには
F5 キーを押します。
フォームで、LastEditDate 列の値を変更してレコードを更新し、[保存] ボタンをクリックします。
フォームに戻り、[同期] をクリックします。
アプリケーション グリッドとサーバー データベースの更新を確認します。 サーバーの列の値によって、クライアント側の更新が上書きされていることに注意してください。 この更新のプロセスは次のとおりです。
Synchronization Services によって、クライアント側で変更された行が判別されます。
同期中にこの行がアップロードされ、サーバー データベースのテーブルに適用されます。 ただし、更新ステートメントにはトラッキング列が含まれません。 Synchronization Services により、テーブルに対して "ダミー更新" が効率的に実行されます。
この行は次にクライアントにエコー バックされますが、サーバーの変更を選択するコマンドにトラッキング列が含まれています。 そのため、クライアントに加えられた変更はサーバー上の値によって上書きされます。
次の手順
このチュートリアルでは、基本的な競合処理によって双方向同期を構成し、クライアント データベース内にサーバーのトラッキング列が含まれる問題を解決しました。 部分クラスを使用することで、ローカル データベース キャッシュのコードをさまざまな方法で拡張することができます。 たとえば、サーバー データベースの変更を選択する SQL コマンドを再定義することで、クライアントにデータがダウンロードされるときにデータをフィルター処理できます。 このドキュメントの「方法」トピックを参照して、アプリケーションのニーズに対応するために同期コードを追加および変更する方法を理解することをお勧めします。 詳細については、「How to Program Common Client and Server Synchronization Tasks」を参照してください。