共用方式為


HOW TO:使用工作階段變數

此主題示範如何在 Sync Framework 中使用工作階段變數。此主題中的範例將重點放在下列的 Sync Framework 型別:

如需有關如何執行範例程式碼的詳細資訊,請參閱 撰寫一般用戶端和伺服器同步處理工作中的<HOW-TO 主題中的範例應用程式>。

了解工作階段變數

Sync Framework 提供一組工作階段變數,讓您能在同步處理期間,將值傳遞至 SyncAdapterDbServerSyncProvider 命令。指定這些變數與其他參數的指定方式一樣,都是在 ADO.NET 命令中指定到查詢或預存程序中。在同步處理工作階段期間,當 DbServerSyncProvider 叫用每一個 ADO.NET 命令物件時,提供者會逐一檢查同步處理參數集合 (SyncParameters),以判斷它是否可以根據名稱將每一個參數比對到 ADO.NET 命令參數。如果可比對到內建工作階段變數或是您已定義的自訂參數,Sync Framework 會先擴展該變數,然後提供者才會呼叫命令。

例如,下列查詢會使用 sync_last_received_anchorsync_new_received_anchorsync_client_id 工作階段變數,由 Customer 資料表中選取變更。

"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"

在同步處理期間,這些變數的值由 Sync Framework 提供。您可以像在先前的查詢中一樣直接使用這些變數的名稱,或是使用 SyncSession 物件中可用的常數。

"SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
Sales.Customer WHERE InsertTimestamp > @" +
SyncSession.SyncLastReceivedAnchor + 
" AND InsertTimestamp <= @" +
SyncSession.SyncNewReceivedAnchor +
" AND InsertId <> @"
SyncSession.SyncClientId

下表列出所有可用的工作階段變數及其使用方式。

工作階段變數 使用方式

sync_client_idsync_client_id_hashsync_client_id_binarysync_originator_id

用來識別正在同步處理的用戶端。系統通常會使用 ID 來進行衝突偵測,同時可以防止變更在雙向 (Bidirectional) 同步處理中回應至用戶端。如需詳細資訊,請參閱 HOW TO:交換用戶端和伺服器之間的雙向增量資料變更

根據預設,Sync Framework 會以由 sync_client_id 傳回的 GUID 辨識每個用戶端。您也可以建立 ID 的雜湊,並在查詢中使用 sync_client_id_hash。當您使用 SQL Server 變更追蹤時,sync_client_id_binary 會很有用。可以從 sync_client_id 將此 GUID 對應到整數,然後使用 sync_originator_id。如需更多資訊,請參閱本主題稍後的「範例」一節。

sync_last_received_anchorsync_new_received_anchor

用以定義在工作階段期間要同步 (Synchronize) 的變更集合。在目前的同步處理期間,為 SelectNewAnchorCommand 屬性指定的命令,會提供新的錨定值。在上一次收到錨定值之後,以及在新收到的錨定值之前所做的變更會進行同步處理。然後會儲存新收到的錨定,並在下一次同步處理時將它當做上一次收到的錨定值。如需詳細資訊,請參閱 追蹤伺服器資料庫中的變更中的<判斷要將哪些資料變更下載到用戶端>一節。

sync_force_write

RetryWithForceWriteApplyAction 搭配使用,強制結束因為衝突或錯誤而失敗的變更之應用程式。如需詳細資訊,請參閱 HOW TO:處理資料衝突和錯誤

sync_row_count

傳回伺服器端最後的作業所影響的資料列數目。在 SQL Server 資料庫中,@@ROWCOUNT 提供此變數的值。資料列計數為 0,表示某項作業失敗,通常是由於衝突或錯誤。如需詳細資訊,請參閱 HOW TO:處理資料衝突和錯誤

sync_initialized

傳回目前的同步處理是初始同步處理 (值為 0),或是後續的同步處理 (值為 1)。

sync_table_namesync_group_name

如果您必須在查詢中指定資料表名稱或群組名稱,就要使用它。

sync_batch_countsync_batch_sizesync_max_received_anchor

如果進行批次變更,就要使用它。如需詳細資訊,請參閱 HOW TO:指定變更的順序和批次大小

sync_session_id

傳回識別目前的同步處理工作階段的 GUID 值。

範例

下列程式碼範例顯示如何在同步處理 Sync Framework 範例資料庫的 Vendor 資料表時,使用工作階段變數。

API 的主要部分

本節提供的程式碼範例將指出牽涉到工作階段變數的 API 主要部分。下列程式碼範例指出 SelectNewAnchorCommand 屬性的查詢,它會設定 sync_new_received_anchor 變數的值。從伺服器資料庫選取變更的同步處理命令會使用此值。

SqlCommand selectNewAnchorCommand = new SqlCommand();
string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
selectNewAnchorCommand.CommandText = 
    "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
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 + " = min_active_rowversion() - 1"
    .Parameters.Add(newAnchorVariable, SqlDbType.Timestamp)
    .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
    .Connection = serverConn
End With
Me.SelectNewAnchorCommand = selectNewAnchorCommand

下列程式碼範例會指定一個命令,以便從伺服器選取要套用至用戶端的累加更新。此命令包括錨定變數與及 sync_originator_id 變數。sync_originator_id 變數的值是由針對 SelectClientIdCommand 屬性指定的查詢所提供。在本節最後會描述此查詢與屬性。

SqlCommand vendorIncrUpdates = new SqlCommand();
vendorIncrUpdates.CommandText =
    "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
    "FROM Sales.Vendor " +
    "WHERE (UpdateTimestamp > @sync_last_received_anchor " +
    "AND UpdateTimestamp <= @sync_new_received_anchor " +
    "AND UpdateId <> @sync_originator_id " +
    "AND NOT (InsertTimestamp > @sync_last_received_anchor " +
    "AND InsertId <> @sync_originator_id))";
vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
vendorIncrUpdates.Connection = serverConn;
vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates;
Dim vendorIncrUpdates As New SqlCommand()
With vendorIncrUpdates
    .CommandText = _
        "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
      & "FROM Sales.Vendor " _
      & "WHERE (UpdateTimestamp > @sync_last_received_anchor " _
      & "AND UpdateTimestamp <= @sync_new_received_anchor " _
      & "AND UpdateId <> @sync_originator_id " _
      & "AND NOT (InsertTimestamp > @sync_last_received_anchor " _
      & "AND InsertId <> @sync_originator_id))"
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
    .Connection = serverConn
End With
vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates

下列程式碼範例指出從用戶端套用更新至伺服器的命令。除了錨定和 ID 變數之外,此命令也包括了 sync_force_writesync_row_count variables

SqlCommand vendorUpdates = new SqlCommand();
vendorUpdates.CommandText =
    "UPDATE Sales.Vendor SET " + 
    "VendorName = @VendorName, CreditRating = @CreditRating, " + 
    "PreferredVendor = @PreferredVendor, " + 
    "UpdateId = @sync_originator_id " +           
    "WHERE (VendorId = @VendorId) " + 
    "AND (@sync_force_write = 1 " + 
    "OR (UpdateTimestamp <= @sync_last_received_anchor " +
    "OR UpdateId = @sync_originator_id)) " + 
    "SET @sync_row_count = @@rowcount";
vendorUpdates.Parameters.Add("@VendorName", SqlDbType.NVarChar);
vendorUpdates.Parameters.Add("@CreditRating", SqlDbType.NVarChar);
vendorUpdates.Parameters.Add("@PreferredVendor", SqlDbType.NVarChar);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
vendorUpdates.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
vendorUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
vendorUpdates.Connection = serverConn;
vendorSyncAdapter.UpdateCommand = vendorUpdates;
Dim vendorUpdates As New SqlCommand()
With vendorUpdates
    .CommandText = _
        "UPDATE Sales.Vendor SET " _
      & "VendorName = @VendorName, CreditRating = @CreditRating, " _
      & "PreferredVendor = @PreferredVendor, " _
      & "UpdateId = @sync_originator_id " _
      & "WHERE (VendorId = @VendorId) " _
      & "AND (@sync_force_write = 1 " _
      & "OR (UpdateTimestamp <= @sync_last_received_anchor " _
      & "OR UpdateId = @sync_originator_id)) " _
      & "SET @sync_row_count = @@rowcount"
    .Parameters.Add("@VendorName", SqlDbType.NVarChar)
    .Parameters.Add("@CreditRating", SqlDbType.NVarChar)
    .Parameters.Add("@PreferredVendor", SqlDbType.NVarChar)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
    .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
    .Connection = serverConn
End With
vendorSyncAdapter.UpdateCommand = vendorUpdates

下列程式碼範例指出的命令,可從伺服器選取累加刪除並套用至用戶端。除了錨定和 ID 參數之外,此命令也包括了 sync_initialized 變數。在這個案例中,只有在有後續同步處理時,才會從標記資料表選取資料列。在初始同步處理期間,則與標記資料表中的資料列無關。

SqlCommand vendorIncrDeletes = new SqlCommand();
vendorIncrDeletes.CommandText =
    "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
    "FROM Sales.Vendor_Tombstone " +
    "WHERE (@sync_initialized = 1 " +
    "AND DeleteTimestamp > @sync_last_received_anchor " +
    "AND DeleteTimestamp <= @sync_new_received_anchor " +
    "AND DeleteId <> @sync_originator_id)";
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
vendorIncrDeletes.Connection = serverConn;
vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes;
Dim vendorIncrDeletes As New SqlCommand()
With vendorIncrDeletes
    .CommandText = _
        "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
      & "FROM Sales.Vendor_Tombstone " _
      & "WHERE (@sync_initialized = 1 " _
      & "AND DeleteTimestamp > @sync_last_received_anchor " _
      & "AND DeleteTimestamp <= @sync_new_received_anchor " _
      & "AND DeleteId <> @sync_originator_id)"
    .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
    .Connection = serverConn
End With
vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes

下列程式碼範例指出對應用戶端 ID 至原始 ID 的命令。這不是必要的步驟,但是使用整數而非 Sync Framework 使用的 GUID 來代表用戶端,有時會很有用。在下一個程式碼範例中會敘述預存程序。

SqlCommand selectClientIdCommand = new SqlCommand();
selectClientIdCommand.CommandType = CommandType.StoredProcedure;
selectClientIdCommand.CommandText = "usp_GetOriginatorId";
selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output;
selectClientIdCommand.Connection = serverConn;
this.SelectClientIdCommand = selectClientIdCommand;
Dim selectClientIdCommand As New SqlCommand()
With selectClientIdCommand
    .CommandType = CommandType.StoredProcedure
    .CommandText = "usp_GetOriginatorId"
    .Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output
    .Connection = serverConn
End With
Me.SelectClientIdCommand = selectClientIdCommand

下列程式碼範例會建立要讀取的對應資料表與預存程序,並填入資料表。預存程序將用戶端 ID (一個 GUID) 視為輸入,並傳回原始 ID (一個整數)。系統會插入伺服器的對應資料列,並加入其他的資料列,做為新的用戶端同步。在用戶端經過一次同步後,對應的資料表會包含此用戶端的輸入。因為 SyncAdapter 命令使用原始 ID,因此在 Vendor 資料表中的追蹤資料行就會是 int 型別而不是 uniqueidentifier 型別。

CREATE TABLE IdMapping(
    ClientId uniqueidentifier NOT NULL PRIMARY KEY, 
    OriginatorId int NOT NULL)
GO

--Insert a mapping for the server.
INSERT INTO IdMapping VALUES ('00000000-0000-0000-0000-000000000000', 0)
GO

CREATE PROCEDURE usp_GetOriginatorId
    @sync_client_id uniqueidentifier,
    @sync_originator_id int out

AS
     SELECT @sync_originator_id = OriginatorId FROM IdMapping WHERE ClientId = @sync_client_id 

     IF ( @sync_originator_id IS NULL )
     BEGIN
          SELECT @sync_originator_id = MAX(OriginatorId) + 1 FROM IdMapping
          INSERT INTO IdMapping VALUES (@sync_client_id, @sync_originator_id)
     END
GO

完整的程式碼範例

下列的完整程式碼範例包含先前所述的程式碼範例,以及其他的程式碼,可用來執行同步處理。此範例需要 Utility 類別,詳情請參閱 資料庫提供者公用程式類別的 HOW-TO 主題

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);
            
            //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("Vendor");
            Utility.MakeDataChangesOnClient("Vendor");

            //Subsequent synchronization.
            syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "subsequent");

            //Return the 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 Vendor table: specify a synchronization direction of
            //Bidirectional, and that an existing table should be dropped.
            SyncTable vendorSyncTable = new SyncTable("Vendor");
            vendorSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            vendorSyncTable.SyncDirection = SyncDirection.Bidirectional;
            this.Configuration.SyncTables.Add(vendorSyncTable);
        }
    }

    //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 timestamp value
            //that is retrieved and stored in the client database.
            //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 + " = min_active_rowversion() - 1";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;
            
            //Create a command that enables you to pass in a 
            //client ID (a GUID) and get back the orginator ID (an integer) 
            //that is defined in a mapping table on the server.
            SqlCommand selectClientIdCommand = new SqlCommand();
            selectClientIdCommand.CommandType = CommandType.StoredProcedure;
            selectClientIdCommand.CommandText = "usp_GetOriginatorId";
            selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier);
            selectClientIdCommand.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output;
            selectClientIdCommand.Connection = serverConn;
            this.SelectClientIdCommand = selectClientIdCommand;

            //Create a SyncAdapter for the Vendor 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.

            //Create the SyncAdapter
            SyncAdapter vendorSyncAdapter = new SyncAdapter("Vendor");            
            
            //Select inserts from the server.
            //This command includes three session variables:
            //@sync_last_received_anchor, @sync_new_received_anchor,
            //and @sync_originator_id. The anchor variables are used with
            //SelectNewAnchorCommand to determine the set of changes to 
            //synchronize. In other example code, the commands use 
            //@sync_client_id instead of @sync_originator_id. In this case, 
            //@sync_originator_id is used because the SelectClientIdCommand 
            //is specified.
            SqlCommand vendorIncrInserts = new SqlCommand();
            vendorIncrInserts.CommandText =  
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
                "FROM Sales.Vendor " +
                "WHERE (InsertTimestamp > @sync_last_received_anchor " +
                "AND InsertTimestamp <= @sync_new_received_anchor " +
                "AND InsertId <> @sync_originator_id)";
            vendorIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrInserts.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorIncrInserts.Connection = serverConn;
            vendorSyncAdapter.SelectIncrementalInsertsCommand = vendorIncrInserts;

            //Apply inserts to the server.
            //This command includes @sync_row_count, which returns
            //a count of how many rows were affected by the
            //last database operation. In SQL Server, the variable
            //is assigned the value of @@rowcount. The count is used
            //to determine whether an operation was successful or
            //was unsuccessful due to a conflict or an error.
            SqlCommand vendorInserts = new SqlCommand();
            vendorInserts.CommandText =
                "INSERT INTO Sales.Vendor (VendorId, VendorName, CreditRating, PreferredVendor, InsertId, UpdateId) " +
                "VALUES (@VendorId, @VendorName, @CreditRating, @PreferredVendor, @sync_originator_id, @sync_originator_id) " +
                "SET @sync_row_count = @@rowcount";
            vendorInserts.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);
            vendorInserts.Parameters.Add("@VendorName", SqlDbType.NVarChar);
            vendorInserts.Parameters.Add("@CreditRating", SqlDbType.NVarChar);
            vendorInserts.Parameters.Add("@PreferredVendor", SqlDbType.NVarChar);
            vendorInserts.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorInserts.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            vendorInserts.Connection = serverConn;
            vendorSyncAdapter.InsertCommand = vendorInserts;
                                    
            
            //Select updates from the server
            SqlCommand vendorIncrUpdates = new SqlCommand();
            vendorIncrUpdates.CommandText =
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
                "FROM Sales.Vendor " +
                "WHERE (UpdateTimestamp > @sync_last_received_anchor " +
                "AND UpdateTimestamp <= @sync_new_received_anchor " +
                "AND UpdateId <> @sync_originator_id " +
                "AND NOT (InsertTimestamp > @sync_last_received_anchor " +
                "AND InsertId <> @sync_originator_id))";
            vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorIncrUpdates.Connection = serverConn;
            vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates;
            
            //Apply updates to the server.
            //This command includes @sync_force_write, which can
            //be used to apply changes in case of a conflict.
            SqlCommand vendorUpdates = new SqlCommand();
            vendorUpdates.CommandText =
                "UPDATE Sales.Vendor SET " + 
                "VendorName = @VendorName, CreditRating = @CreditRating, " + 
                "PreferredVendor = @PreferredVendor, " + 
                "UpdateId = @sync_originator_id " +           
                "WHERE (VendorId = @VendorId) " + 
                "AND (@sync_force_write = 1 " + 
                "OR (UpdateTimestamp <= @sync_last_received_anchor " +
                "OR UpdateId = @sync_originator_id)) " + 
                "SET @sync_row_count = @@rowcount";
            vendorUpdates.Parameters.Add("@VendorName", SqlDbType.NVarChar);
            vendorUpdates.Parameters.Add("@CreditRating", SqlDbType.NVarChar);
            vendorUpdates.Parameters.Add("@PreferredVendor", SqlDbType.NVarChar);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorUpdates.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            vendorUpdates.Connection = serverConn;
            vendorSyncAdapter.UpdateCommand = vendorUpdates;


            //Select deletes from the server.
            //This command includes @sync_initialized, which is
            //used to determine whether a client has been 
            //initialized already. If this variable returns 0,
            //this is the first synchronization for this client ID
            //or originator ID.
            SqlCommand vendorIncrDeletes = new SqlCommand();
            vendorIncrDeletes.CommandText =
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " +
                "FROM Sales.Vendor_Tombstone " +
                "WHERE (@sync_initialized = 1 " +
                "AND DeleteTimestamp > @sync_last_received_anchor " +
                "AND DeleteTimestamp <= @sync_new_received_anchor " +
                "AND DeleteId <> @sync_originator_id)";
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit);
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp);
            vendorIncrDeletes.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorIncrDeletes.Connection = serverConn;
            vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes;

            //Apply deletes to the server.            
            SqlCommand vendorDeletes = new SqlCommand();
            vendorDeletes.CommandText =
                "DELETE FROM Sales.Vendor " +
                "WHERE (VendorId = @VendorId) " + 
                "AND (@sync_force_write = 1 " + 
                "OR (UpdateTimestamp <= @sync_last_received_anchor " + 
                "OR UpdateId = @sync_originator_id)) " + 
                "SET @sync_row_count = @@rowcount " + 
                "IF (@sync_row_count > 0)  BEGIN " + 
                "UPDATE Sales.Vendor_Tombstone " + 
                "SET DeleteId = @sync_originator_id " +
                "WHERE (VendorId = @VendorId) " + 
                "END";
            vendorDeletes.Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier);       
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp);
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int);
            vendorDeletes.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);           
            vendorDeletes.Connection = serverConn;
            vendorSyncAdapter.DeleteCommand = vendorDeletes;     


            //Add the SyncAdapter to the server synchronization provider.
            this.SyncAdapters.Add(vendorSyncAdapter);

        }
    }

    //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["Vendor"].Columns["VendorId"].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)

        '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("Vendor")
        Utility.MakeDataChangesOnClient("Vendor")

        'Subsequent synchronization.
        syncStatistics = sampleSyncAgent.Synchronize()
        sampleStats.DisplayStats(syncStatistics, "subsequent")

        'Return the 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 Vendor table: specify a synchronization direction of
        'Bidirectional, and that an existing table should be dropped.
        Dim vendorSyncTable As New SyncTable("Vendor")
        vendorSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
        vendorSyncTable.SyncDirection = SyncDirection.Bidirectional
        Me.Configuration.SyncTables.Add(vendorSyncTable)

    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 timestamp value
        'that is retrieved and stored in the client database.
        '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 + " = min_active_rowversion() - 1"
            .Parameters.Add(newAnchorVariable, SqlDbType.Timestamp)
            .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
            .Connection = serverConn
        End With
        Me.SelectNewAnchorCommand = selectNewAnchorCommand

        'Create a command that enables you to pass in a 
        'client ID (a GUID) and get back the orginator ID (an integer) 
        'that is defined in a mapping table on the server.
        Dim selectClientIdCommand As New SqlCommand()
        With selectClientIdCommand
            .CommandType = CommandType.StoredProcedure
            .CommandText = "usp_GetOriginatorId"
            .Parameters.Add("@" + SyncSession.SyncClientId, SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int).Direction = ParameterDirection.Output
            .Connection = serverConn
        End With
        Me.SelectClientIdCommand = selectClientIdCommand

        'Create a SyncAdapter for the Vendor 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.

        'Create the SyncAdapter
        Dim vendorSyncAdapter As New SyncAdapter("Vendor")

        'Select inserts from the server.
        'This command includes three session variables:
        '@sync_last_received_anchor, @sync_new_received_anchor,
        'and @sync_originator_id. The anchor variables are used with
        'SelectNewAnchorCommand to determine the set of changes to 
        'synchronize. In other example code, the commands use 
        '@sync_client_id instead of @sync_originator_id. In this case, 
        '@sync_originator_id is used because the SelectClientIdCommand 
        'is specified.
        Dim vendorIncrInserts As New SqlCommand()
        With vendorIncrInserts
            .CommandText = _
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
              & "FROM Sales.Vendor " _
              & "WHERE (InsertTimestamp > @sync_last_received_anchor " _
              & "AND InsertTimestamp <= @sync_new_received_anchor " _
              & "AND InsertId <> @sync_originator_id)"
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.SelectIncrementalInsertsCommand = vendorIncrInserts

        'Apply inserts to the server.
        'This command includes @sync_row_count, which returns
        'a count of how many rows were affected by the
        'last database operation. In SQL Server, the variable
        'is assigned the value of @@rowcount. The count is used
        'to determine whether an operation was successful or
        'was unsuccessful due to a conflict or an error.
        Dim vendorInserts As New SqlCommand()
        With vendorInserts
            .CommandText = _
                "INSERT INTO Sales.Vendor (VendorId, VendorName, CreditRating, PreferredVendor, InsertId, UpdateId) " _
              & "VALUES (@VendorId, @VendorName, @CreditRating, @PreferredVendor, @sync_originator_id, @sync_originator_id) " _
              & "SET @sync_row_count = @@rowcount"
            .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@VendorName", SqlDbType.NVarChar)
            .Parameters.Add("@CreditRating", SqlDbType.NVarChar)
            .Parameters.Add("@PreferredVendor", SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.InsertCommand = vendorInserts

        'Select updates from the server
        Dim vendorIncrUpdates As New SqlCommand()
        With vendorIncrUpdates
            .CommandText = _
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
              & "FROM Sales.Vendor " _
              & "WHERE (UpdateTimestamp > @sync_last_received_anchor " _
              & "AND UpdateTimestamp <= @sync_new_received_anchor " _
              & "AND UpdateId <> @sync_originator_id " _
              & "AND NOT (InsertTimestamp > @sync_last_received_anchor " _
              & "AND InsertId <> @sync_originator_id))"
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.SelectIncrementalUpdatesCommand = vendorIncrUpdates

        'Apply updates to the server.
        'This command includes @sync_force_write, which can
        'be used to apply changes in case of a conflict.
        Dim vendorUpdates As New SqlCommand()
        With vendorUpdates
            .CommandText = _
                "UPDATE Sales.Vendor SET " _
              & "VendorName = @VendorName, CreditRating = @CreditRating, " _
              & "PreferredVendor = @PreferredVendor, " _
              & "UpdateId = @sync_originator_id " _
              & "WHERE (VendorId = @VendorId) " _
              & "AND (@sync_force_write = 1 " _
              & "OR (UpdateTimestamp <= @sync_last_received_anchor " _
              & "OR UpdateId = @sync_originator_id)) " _
              & "SET @sync_row_count = @@rowcount"
            .Parameters.Add("@VendorName", SqlDbType.NVarChar)
            .Parameters.Add("@CreditRating", SqlDbType.NVarChar)
            .Parameters.Add("@PreferredVendor", SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.UpdateCommand = vendorUpdates

        'Select deletes from the server.
        'This command includes @sync_initialized, which is
        'used to determine whether a client has been 
        'initialized already. If this variable returns 0,
        'this is the first synchronization for this client ID
        'or originator ID.
        Dim vendorIncrDeletes As New SqlCommand()
        With vendorIncrDeletes
            .CommandText = _
                "SELECT VendorId, VendorName, CreditRating, PreferredVendor " _
              & "FROM Sales.Vendor_Tombstone " _
              & "WHERE (@sync_initialized = 1 " _
              & "AND DeleteTimestamp > @sync_last_received_anchor " _
              & "AND DeleteTimestamp <= @sync_new_received_anchor " _
              & "AND DeleteId <> @sync_originator_id)"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.SelectIncrementalDeletesCommand = vendorIncrDeletes

        'Apply deletes to the server.            
        Dim vendorDeletes As New SqlCommand()
        With vendorDeletes
            .CommandText = _
                "DELETE FROM Sales.Vendor " _
              & "WHERE (VendorId = @VendorId) " _
              & "AND (@sync_force_write = 1 " _
              & "OR (UpdateTimestamp <= @sync_last_received_anchor " _
              & "OR UpdateId = @sync_originator_id)) " _
              & "SET @sync_row_count = @@rowcount " _
              & "IF (@sync_row_count > 0)  BEGIN " _
              & "UPDATE Sales.Vendor_Tombstone " _
              & "SET DeleteId = @sync_originator_id " _
              & "WHERE (VendorId = @VendorId) " _
              & "END"
            .Parameters.Add("@VendorId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.Timestamp)
            .Parameters.Add("@" + SyncSession.SyncOriginatorId, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Connection = serverConn
        End With
        vendorSyncAdapter.DeleteCommand = vendorDeletes


        'Add the SyncAdapter to the server synchronization provider.
        Me.SyncAdapters.Add(vendorSyncAdapter)

    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("Vendor").Columns("VendorId").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

請參閱

概念

撰寫一般用戶端和伺服器同步處理工作