在 Visual Studio 2008 中,[本機資料庫快取] 會設定 SQL Server Compact 資料庫和一組啟用 Sync Framework.的部分類別。由於 Visual Studio 會產生部分類別,您可以撰寫程式碼加入同步處理功能,而仍然在 [設定資料同步處理] 對話方塊中保留檢視及變更設定的功能。如需有關 [本機資料庫快取] 及部分類別的詳細資訊,請參閱 Visual Studio 2008 文件。
[設定資料同步處理] 對話方塊是預設為讓您只能設定已下載案例的 Sync Framework。也就是說,設定資料同步處理之後,呼叫 Synchronize 將僅從伺服器下載變更至用戶端資料庫。擴充同步處理程式碼最常用的其中一種方式是設定雙向同步處理,這種方式可以讓您從用戶端上傳變更至伺服器。若要啟用雙向同步處理,建議您以下列方式擴充產生的程式碼:
將同步處理方向設定為雙向。
加入程式碼以應付同步處理衝突。
從同步處理命令移除伺服器追蹤資料行。
注意
Visual Studio 2008 會使用 Sync Framework for ADO.NET 1.0 來為 [本機資料庫快取] 產生程式碼。
必要條件
啟動此逐步解說之前,必須先完成 Visual Studio 2008 文件中的以下逐步解說:<逐步解說:建立偶爾連接的應用程式>。完成該逐步解說之後,就會有包含 [本機資料庫快取] 的專案,並具有可讓您從 Northwind Customers 資料表下載變更至 SQL Server Compact 資料庫的 Windows Form 應用程式。現在,您已準備好,可以載入此逐步解說方案,並加入雙向功能。
若要開啟 OCSWalkthrough 方案
開啟 Visual Studio。
在 [檔案] 功能表上開啟現有方案或專案,或找出 [OCSWalkthrough] 方案。這是 OCSWalkthrough.sln 檔案。
設定同步處理的方向
[設定資料同步處理] 對話方塊會將 SyncDirection 屬性設定為 DownloadOnly 或 Snapshot。若要啟用雙向同步處理,請在您要啟用變更上傳的每一個資料表上,將 SyncDirection 屬性設定為 Bidirectional。
若要設定同步處理的方向
以滑鼠右鍵按一下 [NorthwindCache.sync],然後選取 [檢視程式碼]。第一次執行這項作業時,Visual Studio 會在 [方案總管] 中的 [NorthwindCache.sync] 節點底下,建立 [NorthwindCache] 類別檔案。這個檔案會包含一個
NorthwindCacheSyncAgent部分類別,您也可以視需要加入其他類別。在 [NorthwindCache] 類別檔案中,加入一行程式碼至
NorthwindCacheSyncAgent.OnInitialized()方法:public partial class NorthwindCacheSyncAgent { partial void OnInitialized() { this.Customers.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional; } }Partial Public Class NorthwindCacheSyncAgent Partial Sub OnInitialized() Me.Customers.SyncDirection = Microsoft.Synchronization.Data.SyncDirection.Bidirectional End Sub End Class在程式碼編輯器中開啟 [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。
在表單中,更新記錄,然後按一下工具列上的 [儲存] 按鈕。
按一下 [開始同步處理]。
包含同步處理記錄相關資訊的訊息方塊隨即出現。統計資料會顯示已上傳一個資料列並下載一個資料列,但伺服器上並沒有進行任何變更。之所以會發生額外下載的現象,是因為用戶端的變更在伺服器上套用之後,會再傳回用戶端。如需詳細資訊,請參閱 HOW TO:使用自訂變更追蹤系統中的<判斷哪個用戶端進行資料變更>一節。
按一下 [確定] 以關閉訊息方塊,但讓應用程式繼續執行。
若要同步處理並檢視衝突解決
在表單中,更新記錄,然後按一下 [儲存] 按鈕。
在應用程式持續執行下,使用 [伺服器總管/資料庫總管] (或其他資料庫管理工具),連接到伺服器資料庫。
若要示範衝突解決的預設行為,請在 [伺服器總管/資料庫總管] 中,更新您在表單中更新的同一筆記錄,但更新為不同的值,然後認可變更(移出已修改的記錄)。
回到表單中,然後按一下 [開始同步處理]。
在應用程式方格和伺服器資料庫中確認更新。請注意,您在伺服器端所做的更新已覆寫用戶端的更新。如需有關如何變更此衝突解決行為的詳細資訊,請參閱本主題下一節「加入程式碼以應付同步處理衝突」。
加入程式碼以應付同步處理衝突
在 Sync Framework 中,當資料列於同步處理之間,同時在用戶端與伺服器進行變更時,即產生衝突,Sync Framework 提供一組功能,讓您用來偵測並解決衝突。在此逐步解說中,您要加入衝突的基本處理,以處理資料列同時在用戶端和伺服器上更新的情形。其他衝突包括資料列在一個資料庫中被刪除,而在另一個資料庫中進行更新,或者具有重複主索引鍵的資料列同時插入兩個資料庫中。如需有關偵測並解決衝突的詳細資訊,請參閱 HOW TO:處理資料衝突和錯誤。
若要加入衝突處理
加入程式碼以處理伺服器 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 中的AddHandlers方法: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:指定快照集、下載、上傳及雙向同步處理。
注意
如果使用 SQL Server 變更追蹤,追蹤資料行不會加入您的資料表中。在此情況下,您不需要將變更套用至伺服器的命令。
若要從同步處理命令移除追蹤資料行
請將下列程式碼加入至
NorthwindCache類別 (NorthwindCache.vb 或 NorthwindCache.cs),在NorthwindCacheServerSyncProvider類別的End Class陳述式之後。這段程式碼會重新定義兩個命令,這兩個命令是在Customers資料表的 SyncAdapter 物件上設定為屬性:InsertCommand 和 UpdateCommand 屬性。這兩個命令是由 [設定資料同步處理] 對話方塊產生,其中包含CreationDate和LastEditDate資料行的參考。這兩個命令是在CustomersSyncAdapter類別的OnInitialized方法中重新定義。DeleteCommand 屬性則不會重新定義,因為它不會影響CreationDate或LastEditDate資料行。每個 SQL 命令中的變數都用來在 Sync Framework、用戶端與伺服器之間,傳遞資料和中繼資料。下列工作階段變數是用於如下的命令中:
@sync_row_count:傳回受伺服器端最後作業所影響的資料列數目。在 SQL Server 資料庫中,@@ROWCOUNT 提供此變數的值。@sync_force_write:用於強制套用因為發生衝突或錯誤而失敗的變更。@sync_last_received_anchor:用以定義在工作階段期間要同步處理的變更集合。
如需有關工作階段變數的詳細資訊,請參閱 HOW TO:使用工作階段變數。
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資料行以更新記錄,然後按一下工具列上的 [儲存] 按鈕。回到表單中,然後按一下 [開始同步處理]。
在應用程式方格和伺服器資料庫中確認更新。請注意,伺服器端的資料行值已覆寫用戶端的更新。更新程序如下:
Sync Framework 會識別已在用戶端變更的資料列。
在同步處理期間,資料列已上傳並套用至伺服器資料庫中的資料表。但是,追蹤資料行並未包含於更新陳述式中,Sync Framework 會很有效率地對資料表執行「虛擬更新」。
資料列現在已傳回至用戶端,但從伺服器選取變更的命令確實包含追蹤資料行。因此,在用戶端所做的變更已由伺服器的值覆寫。
結論
在此逐步解說中,您已設定了雙向同步處理,並含基本的衝突處理,而且已處理用戶端資料庫中伺服器追蹤資料行的潛在問題。您可以使用部分類別,以其他有效的方式擴充 [本機資料庫快取] 程式碼。例如,您可以重新定義從伺服器資料庫選取變更的 SQL 命令,以便在將資料下載至用戶端時進行篩選。建議您閱讀本文件中的 HOW-TO 主題,了解可以加入或變更同步處理程式碼的方式,以符合應用程式的需求。如需詳細資訊,請參閱撰寫一般用戶端和伺服器同步處理工作。