カスタム変更追跡システムを使用する方法

多くのアプリケーションでは、サーバー データベースの変更を追跡することにより、その後の同期セッションでこれらの変更をクライアントに配信できるようにすることが求められます。ここでは、変更追跡システムの要件について説明し、Sync Framework で使用可能なカスタム システムの作成方法を示します。カスタム変更追跡は場合によっては適していますが、それによって複雑さが増し、サーバー データベースのパフォーマンスに影響が及ぶことがあります。SQL Server 2008 を使用している場合は、SQL Server の変更追跡機能を使用することをお勧めします。詳細については、「SQL Server の変更追跡を使用する方法」を参照してください。

同期シナリオのサーバー要件

Sync Framework は、サーバー データベースへの影響を最小限に抑えるように設計されています。したがって、サーバー データベースで変更追跡のために必要となる変更は、アプリケーションに必要な機能のレベルに比例します。以下の点に注意してください。

  • 最も単純な機能レベルは、データのダウンロード専用スナップショットです。これには変更が必要ありません。

  • 最も複雑な機能レベルは、完全な変更追跡と競合検出を伴う双方向同期です。

次の表に、Sync Framework の使用方法と、対応するサーバー データベースの要件を示します。

シナリオ 主キーまたは一意な列1 更新時刻の追跡 挿入時刻の追跡 削除時刻の追跡 更新に関するクライアント ID の追跡 挿入に関するクライアント ID の追跡 削除に関するクライアント ID の追跡

データのスナップショットをクライアントにダウンロードする。

×

×

×

×

×

×

×

増分挿入および増分更新をクライアントにダウンロードする。

○2

×

×

×

×

増分挿入、増分更新、および増分削除をクライアントにダウンロードする。

○2

×

×

×

挿入をサーバーにアップロードする。

×

×

×

×

×3

×

挿入および更新をサーバーにアップロードする。

×

×

×

×3

×3

×

挿入、更新、および削除をサーバーにアップロードする。

×

×

×

×3

×3

×3

競合検出を伴う双方向の挿入および更新。

○2

×

○4

○4

×

競合検出を伴う双方向の挿入、更新、および削除。

○2

○4

○4

○4

1 主キーはすべてのノードで一意である必要があるため、再利用できません。つまり、行が削除された場合、その行の主キーを別の行に使用することはできません。一般に、ID 列は分散環境に対して適切な選択肢ではありません。主キーの詳細については、「分散環境に適切な主キーの選択」を参照してください。

2 挿入と更新を区別する必要がある場合は必須です。詳細については、この後の「クライアントにダウンロードするデータ変更を決める」を参照してください。

3 複数のクライアントで行が変更される可能性があり、その変更を行ったクライアントを特定する場合は必須です。詳細については、このトピックの「データ変更を行ったクライアントを特定する」を参照してください。

4 変更を行ったクライアントにその変更をエコー バックしない場合に必須です。詳細については、このトピックの「データ変更を行ったクライアントを特定する」を参照してください。

注意

既に説明した変更に加えて、多くの場合はデータ アクセス用にストアド プロシージャも作成します。このドキュメントのほとんどの例では、コード内の処理内容をわかりやすくするために、インライン SQL を使用しています。運用アプリケーションでは、ストアド プロシージャを使用する必要があります。コードをカプセル化でき、通常はパフォーマンスが向上し、正しく作成すればインライン SQL より高いセキュリティを実現できるためです。

クライアントにダウンロードするデータ変更を決める

ダウンロードのみの同期および双方向同期では、クライアントにダウンロードするデータを Sync Framework で決定できるように、サーバーで変更を追跡する必要があります。Sync Framework で、変更追跡のサポート方法は特に定義されていませんが、一般的なアプローチとして、同期する各テーブルに対して次の方法を使用できます。

  • サーバー データベースに行が挿入された日時を追跡するための列を追加します。

  • サーバー データベースで行が最後に更新された日時を追跡する列と、場合によってはトリガーを追加します。

  • サーバー データベースから行が削除された日時を追跡する廃棄テーブルとトリガーを追加します。サーバーからデータを削除しなくても、クライアントに削除を送信する必要がある場合は、ベース テーブルで論理削除を追跡できます。1 つの列 (通常は bit 型) を使用して行が削除されることを示し、別の列を使用して削除が発生した日時を追跡します。

これらの列と廃棄テーブルは、ダウンロードする挿入、更新、および削除を決定するために、アンカーと共に使用されます。アンカーとは、同期する一連の変更を定義する際に使用される特定の時点です。次のクエリについて考えてみます。

  • SelectIncrementalInsertsCommand プロパティに指定するクエリ。このクエリでは、次のように、Sync Framework のサンプル データベースの Sales.Customer テーブルから増分挿入をダウンロードします。

    SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
    Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor
    AND InsertTimestamp <= @sync_new_received_anchor
    

    このプロパティ、および同期コマンドに関連する他のプロパティの詳細については、「スナップショット、ダウンロード、アップロード、および双方向の各同期を指定する方法」を参照してください。

  • SelectNewAnchorCommand プロパティに指定するクエリ。このクエリでは、特定の時点の値を取得します。InsertTimestamp 列には、タイムスタンプ値が格納されます。そのため、このクエリでは、SQL Server 2005 Service Pack 2 で導入された Transact-SQL MIN_ACTIVE_ROWVERSION 関数を使用して、次のように、サーバー データベースからタイムスタンプ値を取得します。

    SELECT @sync_new_received_anchor = MIN_ACTIVE_ROWVERSION - 1
    

    MIN_ACTIVE_ROWVERSION は、現在のデータベース内のアクティブな timestamp (rowversion とも呼ばれます) の最小値を返します。timestamp 値は、まだコミットされていないトランザクションで使用されている場合にアクティブです。データベースにアクティブな値がない場合は、MIN_ACTIVE_ROWVERSION によって @@DBTS + 1 と同じ値が返されます。MIN_ACTIVE_ROWVERSION は、timestamp 値を使用して一連の変更をグループ化するデータの同期などのシナリオで役に立ちます。アプリケーションがそのアンカー コマンドで MIN_ACTIVE_ROWVERSION ではなく @@DBTS を使用すると、同期が行われるときにアクティブな変更が失われる可能性があります。

Sales.Customer テーブルが初めて同期したときには、次の処理が発生します。

  1. 新しいアンカー コマンドが実行されます。このコマンドは、値 0x0000000000000D49 を返します。この値は、クライアント データベースに格納されます。テーブルはまだ同期されていません。したがって、前回の同期でクライアント データベースに格納されたアンカー値はありません。この場合、Sync Framework では、SQL Server timestamp データ型に使用できる最小値 0x0000000000000000 を使用します。Sync Framework によって実行されるクエリは次のとおりです。このクエリにより、テーブルからスキーマとすべての行がダウンロードされます。

    exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson,
    CustomerType FROM Sales.Customer WHERE (InsertTimestamp >
    @sync_last_received_anchor AND InsertTimestamp <=
    @sync_new_received_anchor)',N'@sync_last_received_anchor timestamp,
    @sync_new_received_anchor timestamp',
    @sync_last_received_anchor=0x0000000000000000,
    @sync_new_received_anchor=0x0000000000000D49
    
  2. 2 回目の同期中に、新しいアンカー コマンドが実行されます。前回の同期の後に行が挿入されました。したがって、コマンドは値 0x0000000000000D4C を返します。テーブルは以前に同期されたことがあります。そのため、Sync Framework では、アンカー値 0x0000000000000D49 を取得できます。この値は、前回の同期からクライアント データベースに格納されます。実行されるクエリは次のとおりです。このクエリにより、テーブルから、2 つのアンカー値の間で挿入された行のみがダウンロードされます。

    exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson,
    CustomerType FROM Sales.Customer WHERE (InsertTimestamp >
    @sync_last_received_anchor AND InsertTimestamp <=
    @sync_new_received_anchor)', N'@sync_last_received_anchor timestamp,
    @sync_new_received_anchor timestamp',
    @sync_last_received_anchor=0x0000000000000D49,
    @sync_new_received_anchor=0x0000000000000D4C
    

更新コマンドおよび削除コマンドの例については、「データの増分変更をクライアントにダウンロードする方法」および「クライアントとサーバー間で双方向でデータの増分変更を交換する方法」を参照してください。

既に述べたとおり、アンカー値の取得に使用されるコマンドは、サーバー データベースで追跡中の列のデータ型によって異なります。このドキュメントの例では、SQL Server timestamp (rowversion とも呼ばれています) を使用します。SQL Server の datetime 列を使用する場合、新しいアンカー コマンドのクエリは次のようになります。

SELECT @sync_new_received_anchor = GETUTCDATE()

アンカーに対して使用するデータ型を決定するには、アプリケーション要件を考慮し、サーバー データベース スキーマを変更するために必要な柔軟性を検討する必要があります。データベースが開発中の場合は、追加する列およびトリガーを正確に指定できますが、データベースが運用中の場合は、選択肢が限られる可能性があります。次のガイドラインを考慮してください。

  • 同期グループ内のすべてのテーブルで、同じデータ型と新しいアンカー コマンドを使用する必要があります。可能であれば、すべてのグループに対して同じデータ型とコマンドを使用してください。

  • datetime データ型はわかりやすく、行の変更日時を追跡するための列がテーブルに既に存在するケースがよくあります。ただし、このデータ型は、クライアントのタイム ゾーンがそれぞれ異なる場合に問題となる可能性があります。このデータ型を使用すると、増分変更が選択されたときに、トランザクションが失われる場合があります。

  • timestamp データ型は正確であり、クロック タイムに依存しません。ただし、SQL Server データベースの各テーブルには、このデータ型の列を 1 つしか含めることができません。したがって、挿入と更新を区別する必要がある場合は、binary(8) など別のデータ型の列を追加して、その列にタイムスタンプ値を格納できます。例については、「データベース プロバイダーのセットアップ スクリプトに関するトピック」を参照してください。timestamp データ型は、サーバー データベースがバックアップから復元された場合に問題となる可能性があります。詳細については、「Sync Framework でサポートされるデータベース オブジェクト」を参照してください。既に述べたように、新しいアンカーを選択するコマンドでは、MIN_ACTIVE_ROWVERSION を使用することをお勧めします。

データ変更を行ったクライアントを特定する

データの変更を行ったクライアントを特定する理由は、主に 2 つあります。

  • アップロードのみの同期および双方向同期で競合検出と解決をサポートするため。

    サーバーと 1 つまたは複数のクライアントで特定の行を変更できる場合、変更者を特定することが必要になる場合があります。この情報を使用すれば、たとえば、ある変更を別の変更より優先するコードを記述できます。この情報がなければ、行に対して最後に実行された変更が保存されます。

  • 双方向同期中に変更がクライアントにエコー バックされないようにするため。

    Sync Framework は、先に変更をサーバーにアップロードしてから、変更をクライアントにダウンロードします。変更を行ったクライアントの ID を追跡しなければ、変更はサーバーにアップロードされた後、同じ同期セッション中にクライアントに再びダウンロードされます。こうした変更のエコーは許容される場合もありますが、許容されない場合もあります。

変更追跡と同様に、Sync Framework で、変更追跡のサポート方法は特に定義されていませんが、一般的なアプローチとして、同期する各テーブルに対して次の方法を使用できます。

  • 各挿入を行ったクライアントを追跡する列をベース テーブルに追加します。

  • 各更新を行ったクライアントを追跡する列をベース テーブルに追加します。

  • 各削除を行ったクライアントを追跡する列を廃棄テーブルに追加します。

このような列とテーブルは、各挿入、更新、または削除を行ったクライアントを特定するために、ClientId プロパティと共に使用されます。スナップショットの同期以外の方法を使用してテーブルを初めて同期すると、Sync Framework は、そのクライアントを識別する、クライアントの GUID 値を格納します。この ID は、各 SyncAdapter の選択クエリと更新クエリで使用できるように、DbServerSyncProvider に渡されます。ID 値は ClientId プロパティで使用できます。次の Transact-SQL クエリについて考えてみます。

SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND
InsertTimestamp <= @sync_new_received_anchor AND InsertId <>
@sync_client_id

このクエリは、サーバーで行われた挿入を追跡する上述のクエリと似ています。WHERE 句のステートメントを使用することにより、現在同期中のクライアントで行われたのではない挿入だけがダウンロード対象の挿入になるように限定しています。

Sync Framework を使用すると、アプリケーションが GUID 値ではなく整数をサーバーで使用して、クライアントを識別することもできます。詳細については、「セッション変数を使用する方法」を参照してください。

サーバーの準備例

次の例では、最も複雑なアプリケーション シナリオ (競合検出を伴う双方向の挿入、更新、および削除の操作) を処理するための追跡インフラストラクチャを使用して、Sync Framework サンプル データベースの Sales.Customer テーブルを設定する方法を示します。それほど複雑ではないシナリオでは、このインフラストラクチャ全体は必要ありません。詳細については、このトピックの「同期シナリオのサーバー要件」を参照してください。この例のオブジェクトとその他のオブジェクトを作成する完全なスクリプトについては、「データベース プロバイダーのセットアップ スクリプトに関するトピック」を参照してください。これらのオブジェクトの使用方法の詳細については、「スナップショット、ダウンロード、アップロード、および双方向の各同期を指定する方法」を参照してください。

このセクションの例では、サーバーを準備する際に次の手順を実行します。

  1. Sales.Customer スキーマを確認します。変更追跡に使用できる主キーおよび列がテーブルにあるかどうかを判断します。

  2. 挿入および更新が行われた日時と場所を追跡する列を追加します。

  3. 廃棄テーブルを作成し、廃棄テーブルにデータを設定するトリガーを Sales.Customer テーブルに追加します。

Sales.Customer スキーマを確認する

次のコード例では、Sales.Customer テーブルのスキーマを示します。このテーブルでは、CustomerId 列に主キーがあり、変更追跡に使用できる列がありません。

CREATE TABLE SyncSamplesDb.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)

挿入操作と更新操作を追跡する列を追加する

次のコード例では、UpdateTimestampInsertTimestampUpdateId、および InsertId の 4 つの列を追加します。UpdateTimestamp 列は、SQL Server の timestamp 列です。この列は、行の更新時に自動的に更新されます。既に述べたように、1 つのテーブルに含めることができる timestamp 列は 1 つだけです。したがって、InsertTimestamp 列は、binary(8) 型の列であり、既定値は @@DBTS + 1 です。この例では、挿入が実行されると UpdateTimestamp 列と InsertTimestamp 列の値が同じになるように、@@DBTS から返される値に加算しています。この処理を行わないと、各行が挿入後に更新されたように見える結果になります。

Sync Framework によって各クライアントに作成される ID は GUID であるため、2 つの ID 列は uniqueidentifier 型の列になります。この列の既定値は、00000000-0000-0000-0000-000000000000 です。この値は、サーバーで更新または挿入が実行されたことを示します。後に示す例では、廃棄テーブルに DeleteId 列が含まれています。

ALTER TABLE SyncSamplesDb.Sales.Customer 
    ADD UpdateTimestamp timestamp
ALTER TABLE SyncSamplesDb.Sales.Customer 
    ADD InsertTimestamp binary(8) DEFAULT @@DBTS + 1
ALTER TABLE SyncSamplesDb.Sales.Customer 
    ADD UpdateId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
ALTER TABLE SyncSamplesDb.Sales.Customer 
    ADD InsertId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'

列が追加されたので、次のコード例ではインデックスを追加します。コード例では、これらのインデックスおよび他のインデックスが、同期中に照会する列に作成されます。これは、サーバー データベースに変更追跡を実装する際にインデックスを考慮する必要があることを強調するためです。サーバーのパフォーマンスと同期のパフォーマンスのバランスを保つようにしてください。

CREATE NONCLUSTERED INDEX IX_Customer_UpdateTimestamp
ON Sales.Customer(UpdateTimestamp)

CREATE NONCLUSTERED INDEX IX_Customer_InsertTimestamp
ON Sales.Customer(InsertTimestamp)

CREATE NONCLUSTERED INDEX IX_Customer_UpdateId
ON Sales.Customer(UpdateId)

CREATE NONCLUSTERED INDEX IX_Customer_InsertId
ON Sales.Customer(InsertId)

削除操作を追跡する廃棄テーブルを追加する

次のコード例では、クラスター化インデックスとトリガーを含む廃棄テーブルを作成し、テーブルにデータを設定します。Sales.Customer テーブルで削除操作が発生すると、トリガーによって Sales.Customer_Tombstone テーブルに行が挿入されます。トリガーは、挿入操作を実行する前に、削除された行の主キーを含む行が既に Sales.Customer_Tombstone テーブルに存在するかどうかを確認します。この動作は、行が Sales.Customer から削除され、再挿入された後にもう一度削除された場合に発生します。このような行は Sales.Customer_Tombstone で検出されると、トリガーによって削除され、再挿入されます。Sales.Customer_TombstoneDeleteTimestamp 列も更新される場合があります。

CREATE TABLE SyncSamplesDb.Sales.Customer_Tombstone(
    CustomerId uniqueidentifier NOT NULL PRIMARY KEY NONCLUSTERED, 
    CustomerName nvarchar(100) NOT NULL,
    SalesPerson nvarchar(100) NOT NULL,
    CustomerType nvarchar(100) NOT NULL,
    DeleteId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
    DeleteTimestamp timestamp)
CREATE TRIGGER Customer_DeleteTrigger 
ON SyncSamplesDb.Sales.Customer FOR DELETE 
AS 
BEGIN 
    SET NOCOUNT ON
    DELETE FROM SyncSamplesDb.Sales.Customer_Tombstone 
        WHERE CustomerId IN (SELECT CustomerId FROM deleted)
    INSERT INTO SyncSamplesDb.Sales.Customer_Tombstone (CustomerId, CustomerName, SalesPerson, CustomerType) 
    SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM deleted
    SET NOCOUNT OFF
END
CREATE CLUSTERED INDEX IX_Customer_Tombstone_DeleteTimestamp
ON Sales.Customer_Tombstone(DeleteTimestamp)

CREATE NONCLUSTERED INDEX IX_Customer_Tombstone_DeleteId
ON Sales.Customer_Tombstone(DeleteId)

参照

概念

サーバー データベースの変更の追跡