本文章是由機器翻譯。
保持同步
使用 Sync Framework 創建同步提供程式
Joydip Kanjilal
Microsoft Sync Framework 是一個功能完善的平臺,用於同步離線和連線資料,便於應用程式、服務和設備等進行協作和離線訪問。它獨立于協定和資料庫,並提供了支援以下功能的技術和工具:設備漫遊、共用功能,以及離線提取網路化資料,然後在以後的某個時間進行同步的功能。
使用 Sync Framework 構建的應用程式可以在網路上使用任何協定從任何資料來源同步資料。它是一個功能完善的同步平臺,便於應用程式、服務和設備進行離線和連線資料訪問。Sync Framework 具有可擴展的提供程式模型,可供託管和非託管代碼用來同步兩個數據源之間的資料。
本文將介紹同步的概念,以及如何將 Sync Framework 集成到專案中。具體而言,我們將介紹資料同步基礎、Sync Framework 的體系結構元件以及如何使用同步提供程式。
要使用 Sync Framework 和本文中的代碼示例,需要安裝 Visual Studio 2010 和 Sync Framework 運行時 2.0 或更高版本。您可以從 Sync Framework 開發人員中心下載該運行時(包含在 Microsoft Sync Framework 2.0 可再發行元件包中)。
Sync Framework 基礎
Sync Framework 包含四個主要元件:運行時、中繼資料服務、同步提供程式和參與方。
Sync Framework 運行時提供用於在資料來源之間同步資料的基礎結構。它還提供一個 SDK,開發人員可以對其進行擴展以實現自訂提供程式。
中繼資料服務提供存儲同步中繼資料(包含同步會話期間使用的資訊)的基礎結構。同步中繼資料包括版本、錨點和更改檢測資訊。在自訂提供程式的設計和開發過程中,也會使用同步中繼資料。
同步提供程式用於在副本或端點之間同步資料。副本是一個同步單元,用於標識實際的資料存儲。舉例來說,如果同步兩個數據庫之間的資料,每個資料庫都稱為一個副本。副本是用唯一識別碼(稱為副本鍵)標識的。此處的端點也稱為資料存儲。本文稍後將更深入地討論提供程式。
參與方指的是可以檢索待同步資料的位置。參與方可以是完整參與方、部分參與方和簡單參與方。
完整參與方是指具備以下功能的設備:可以創建新的資料存儲,可以存儲同步中繼資料資訊,可以在設備自身上運行同步應用程式。完整參與方包括桌上型電腦、可擕式電腦和 Tablet。完整參與方可以與其他參與方同步資料。
部分參與方是指可以創建新的資料存儲和存儲同步中繼資料資訊,但不能在設備自身上運行同步應用程式的設備。USB 存放裝置或智慧手機可以是部分參與方。請注意,部分參與方可以與完整參與方同步資料,但不能與其他部分參與方同步資料。
簡單參與方包括不能存儲新資料或執行應用程式,只能提供所請求資訊的設備。RSS 源、Amazon 和 Google Web 服務都屬於簡單參與方。
同步提供程式
同步提供程式是一個元件,它可以參與同步過程,使一個副本與其他副本同步資料。每個副本都應該有一個同步提供程式。
要同步資料,需要啟動一個同步會話。應用程式連接會話中的源和目標同步提供程式,以便於副本間進行資料同步。
同步會話期間,目標提供程式向源提供程式提供關於其資料存儲的資訊。源提供程式確定哪些源副本更改對於目標副本而言是未知的,然後將此類更改的清單推送給目標提供程式。目標提供程式檢測自己的項與該清單中的項之間是否存在任何衝突,然後將更改應用到自己的資料存儲。Sync Framework 引擎為所有這些同步過程提供了便利。
Sync Framework 支援三個適用于資料庫、檔案系統和源同步的預設提供程式:
- 適用于支援 ADO.NET 的資料來源的同步提供程式
- 適用于 RSS 和 Atom 源的同步提供程式
- 適用于檔和資料夾的同步提供程式
您還可以擴展 Sync Framework,從而創建自己的自訂同步提供程式,在設備和應用程式之間交換資訊。
資料庫同步提供程式(以前在 Sync Framework 1.0 中稱為 ADO.NET 同步服務)支援啟用了 ADO.NET 的資料來源的同步。通過構建未連接資料應用程式,可以在支援 ADO.NET 的資料來源(如 SQL Server)之間進行同步。它支援漫遊、共用和離���提取資料。任何使用資料庫提供程式的資料庫都可以與 Sync Framework 支援的其他資料來源(包括檔案系統、Web 服務,甚至是自訂資料存儲)一起參與同步過程。
Web 同步提供程式(以前的 FeedSync 同步服務)支援同步 RSS 和 ATOM 源。在 FeedSync 之前,這一技術稱為簡單共用擴展,最初是由 Ray Ozzie 設計的。請注意,Web 同步提供程式不會替代現有技術,如 RSS 或 Atom 源。它只是提供了一種向現有 RSS 或 Atom 源添加同步功能的簡單方法,以便獨立于所用平臺或設備的其他應用程式或服務使用這些源。
檔同步提供程式(以前的檔案系統同步服務)支援對系統中的檔和資料夾進行同步。它可用於在同一系統或網路中不同系統之間同步檔和資料夾。您可以在採用 NTFS、FAT 或 SMB 檔案系統的系統中同步檔和資料夾。提供程式使用 Sync Framework 中繼資料模型實現檔資料的對等同步,同時支援任意拓撲(用戶端/伺服器、交錯和對等),還支援可移動介質。此外,檔同步提供程式還支援增量式同步、衝突和更改檢測、在操作的預覽和非預覽模式中同步,以及在同步過程中篩選和跳過檔。
使用內置同步提供程式
在這部分中,我將演示如何使用內置同步提供程式實現一個簡單的應用程式,用於同步系統中兩個資料夾的內容。
FileSyncProvider 類可用來創建檔同步提供程式。該類擴展了 UnManagedSyncProvider 類,並且實現 IDisposable 介面。FileSyncScopeFilter 類用於包含或排除將參與同步過程的檔和資料夾。
FileSyncProvider 使用同步中繼資料檢測副本中的更改。同步中繼資料包含參與同步過程的所有檔和資料夾的有關資訊。There are actually two kinds of sync metadata:replica metadata and item metadata.檔同步提供程式存儲參與同步過程的所有檔和資料夾的中繼資料。以後,它使用這些檔和資料夾的檔大小、屬性和上次存取時間檢測更改。
打開 Visual Studio 2010,創建一個新的 Windows Presentation Foundation (WPF) 專案。將專案命名為 SyncFiles,然後保存專案。打開 MainWindow.xaml 檔,創建一個類似于圖 1 所示的 WPF 表單。
圖 1 示例同步應用程式
可以看到,您擁有用來選取源和目的檔案夾的控制項。還擁有用來顯示同步統計資訊以及源和目的檔案夾內容的控制項。
在“解決方案資源管理器”中按右鍵專案,按一下“添加引用”,然後添加 Microsoft.Synchronization 程式集。
現在,在 MainWindow.xaml.cs 檔中添加一個新的 GetReplicaID 方法,該方法返回一個 GUID,如圖 2 中的代碼所示。如果對 SyncOrchestrator 實例調用 Synchronize 方法,則會在每個使用該唯一 GUID 的資料夾或副本中創建一個名為 filesync.metadata 的中繼資料檔。GetReplicaID 方法將此 GUID 保存在一個檔中,以便下次調用此方法時,不會為該特定資料夾生成新的 GUID。GetReplicaID 方法首先檢查是否存在包含副本 ID 的檔。如果未找到這樣的檔,則創建一個新的副本 ID 並存儲在檔中。如果存在這樣的檔(因為以前生成了該資料夾的副本 ID),則返回此檔的副本 ID。
圖 2 GetReplicaID
private Guid GetReplicaID(string guidPath) {
if (!File.Exists(guidPath)) {
Guid replicaID = Guid.NewGuid();
using (FileStream fileStream =
File.Open(guidPath, FileMode.Create)) {
using (StreamWriter streamWriter =
new StreamWriter(fileStream)) {
streamWriter.WriteLine(replicaID.ToString());
}
}
return replicaID;
}
else {
using (FileStream fileStream =
File.Open(guidPath, FileMode.Open)) {
using (StreamReader streamReader =
new StreamReader(fileStream)) {
return new Guid(streamReader.ReadLine());
}
}
}
}
接下來,添加一個名為 GetFilesAndDirectories 的方法,該方法返回副本位置下的檔和資料夾清單(請參見圖 3)。 資料夾名應作為參數傳遞給該方法。
圖 3 獲取副本檔和資料夾
private List<string> GetFilesAndDirectories(String directory) {
List<String> result = new List<String>();
Stack<String> stack = new Stack<String>();
stack.Push(directory);
while (stack.Count > 0) {
String temp = stack.Pop();
try {
result.AddRange(Directory.GetFiles(temp, "*.*"));
foreach (string directoryName in
Directory.GetDirectories(temp)) {
stack.Push(directoryName);
}
}
catch {
throw new Exception("Error retrieving file or directory.");
}
}
return result;
}
在同步過程之前和之後,都要使用此方法,用於顯示源和目的檔案夾中的檔和資料夾的清單。 PopulateSourceFileList 和 PopulateDestinationFileList 方法調用 GetFilesAndDirectories,填充顯示源和目的檔案夾中的檔和目錄的清單方塊(有關詳細資訊,請參閱代碼下載)。
btnSource_Click 和 btnDestination_Click 事件處理常式用於選擇源和目的檔案夾。 這兩個方法都使用 FolderBrowser 類來顯示一個對話方塊,供使用者選擇源或目的檔案夾。 FolderBrowser 類的完整原始程式碼可以從本文的代碼下載部分下載。
現在,需要編寫 Button 控制項的 Click 事件處理常式,它在同步開始前通過禁用按鈕啟動。 然後,它使用源和目標路徑作為參數調用 Synchronize 方法。 最後,啟動同步過程,捕捉所有錯誤,在同步完成後啟用按鈕:
btnSyncFiles.IsEnabled = false;
// Disable the button before synchronization starts
Synchronize(sourcePath, destinationPath);
btnSyncFiles.IsEnabled = true;
// Enable the button after synchronization is complete
Synchronize 方法接受源和目標路徑,然後同步兩個副本的內容。 在 Synchronize 方法中,使用 SyncOperationStatistics 類的一個實例檢索同步過程的統計資訊:
SyncOperationStatistics syncOperationStatistics;
此外,還創建源和目標同步提供程式,創建一個名為 synchronizationAgent 的 SyncOrchestrator 實例,將 GUID 分配給源和目標副本,然後將兩個提供程式附加到該實例。 SyncOrchestrator 負責協調同步會話:
sourceReplicaID =
GetReplicaID(Path.Combine(source,"ReplicaID"));
destinationReplicaID =
GetReplicaID(Path.Combine(destination,"ReplicaID"));
sourceProvider =
new FileSyncProvider(sourceReplicaID, source);
destinationProvider =
new FileSyncProvider(destinationReplicaID, destination);
SyncOrchestrator synchronizationAgent =
new SyncOrchestrator();
synchronizationAgent.LocalProvider = sourceProvider;
synchronizationAgent.RemoteProvider = destinationProvider;
最後,啟動同步過程,捕捉所有錯誤,釋放相應的資源,如圖 4 所示。 本文的代碼下載提供完整的源專案,其中包含錯誤處理和其他實現詳細資訊。
图 4 同步副本
try {
syncOperationStatistics = synchronizationAgent.Synchronize();
// Assign synchronization statistics to the lstStatistics control
lstStatistics.Items.Add("Download Applied: " +
syncOperationStatistics.DownloadChangesApplied.ToString());
lstStatistics.Items.Add("Download Failed: " +
syncOperationStatistics.DownloadChangesFailed.ToString());
lstStatistics.Items.Add("Download Total: " +
syncOperationStatistics.DownloadChangesTotal.ToString());
lstStatistics.Items.Add("Upload Total: " +
syncOperationStatistics.UploadChangesApplied.ToString());
lstStatistics.Items.Add("Upload Total: " +
syncOperationStatistics.UploadChangesFailed.ToString());
lstStatistics.Items.Add("Upload Total: " +
syncOperationStatistics.UploadChangesTotal.ToString());
}
catch (Microsoft.Synchronization.SyncException se) {
MessageBox.Show(se.Message, "Sync Files - Error");
}
finally {
// Release resources once done
if (sourceProvider != null)
sourceProvider.Dispose();
if (destinationProvider != null)
destinationProvider.Dispose();
}
您還可以報告同步會話的同步進度。 為此,請執行以下步驟:
- 為 ApplyingChange 事件註冊一個事件處理常式。
- 將 FileSyncProvider 的 PreviewMode 屬性設置為 true,啟用預覽模式。
- 採用一個整數計數器,在每次觸發 ApplyingChange 事件時遞增計數。
- 啟動同步過程。
- 將 FileSyncProvider 的 PreviewMode 屬性設置為 false,禁用預覽模式。
- 再次啟動同步過程。
篩選和跳過檔
使用 Sync Framework 進行同步時,會自動跳過某些檔,包括 Desktop.ini、Thumbs.db、具有系統和隱藏屬性的檔,以及中繼資料檔。 您可以應用靜態篩選器來控制要同步的檔和資料夾。 具體而言,這些篩選器可以排除不需要參與同步過程的檔。
要使用靜態篩選器,需要創建 FileSyncScopeFilter 類的一個實例,然後將包含和排除篩選器作為參數傳遞給該實例的構造函數。 此外,也可以對 FileSyncScopeFilter 實例使用 FileNameExcludes.Add 方法,從同步會話中篩選出一個或多個檔。 然後,在創建 FileSyncProvider 實例時傳入此 FileSyncScopeFilter 實例。 例如:
FileSyncScopeFilter fileSyncScopeFilter =
new FileSyncScopeFilter();
fileSyncScopeFilter.FileNameExcludes.Add("filesync.id");
FileSyncProvider fileSyncProvider =
new FileSyncProvider(Guid.NewGuid(),
"D:\\MyFolder",fileSyncScopeFilter,FileSyncOptions.None);
同樣,可以從同步過程中排除所有 .lnk 檔:
FileSyncScopeFilter fileSyncScopeFilter =
new FileSyncScopeFilter();
fileSyncScopeFilter.FileNameExcludes.Add("*.lnk");
甚至可以使用 FileSyncOptions 顯式設置同步會話選項:
FileSyncOptions fileSyncOptions =
FileSyncOptions.ExplicitDetectChanges |
FileSyncOptions.RecycleDeletedFiles |
FileSyncOptions.RecyclePreviousFileOnUpdates |
FileSyncOptions.RecycleConflictLoserFiles;
要在同步過程中跳過一個或多個檔,請註冊 ApplyingChange 事件的一個事件處理常式,將 SkipChange 屬性設置為 true:
FileSyncProvider fileSyncProvider;
fileSyncProvider.AppliedChange +=
new EventHandler (OnAppliedChange);
destinationProvider.SkippedChange +=
new EventHandler (OnSkippedChange);
現在,可以實現 OnAppliedChange 事件處理常式來顯示發生的更改:
public static void OnAppliedChange(
object sender, AppliedChangeEventArgs args) {
switch (args.ChangeType) {
case ChangeType.Create:
Console.WriteLine("Create " + args.NewFilePath);
break;
case ChangeType.Delete:
Console.WriteLine("Delete" + args.OldFilePath);
break;
case ChangeType.Overwrite:
Console.WriteLine("Overwrite" + args.OldFilePath);
break;
default:
break;
}
}
請注意,為了清晰起見,此示例進行了簡化。 代碼下載中包含功能更完備的實現。
要瞭解同步過程中跳過某一特定檔的原因,可以實現 OnSkippedChange 事件處理常式:
public static void OnSkippedChange(
object sender, SkippedChangeEventArgs args) {
if (args.Exception != null)
Console.WriteLine("Synchronization Error: " +
args.Exception.Message);
}
生成並執行應用程式。按一下“Source Folder”按鈕可選擇原始檔案夾。使用“Destination Folder”可選擇目的檔案夾。可以看到,在同步之前,每個資料夾中的檔的清單都顯示在各自的清單方塊中(請參見圖 1)。因為同步尚未開始,“Synchronization Statistics”清單方塊不顯示任何內容。
現在,按一下“Synchronize”按鈕啟動同步過程。源和目的檔案夾同步後,它們各自的清單方塊中會顯示同步後兩個資料夾的內容。“Synchronization Statistics”清單方塊現在顯示所完成任務的有關資訊(請參見圖 5)。
图 5 同步完成
處理衝突
Sync Framework 管理基於時間戳記的同步所涉及的所有複雜問題,包括推遲衝突、失敗、中斷和迴圈。為在同步會話過程中處理資料衝突,Sync Framework 遵循以下策略之一:
- 源獲勝:在本策略中,出現衝突時,總是採用來源資料存儲中的更改。
- 目標獲勝:在本策略中,出現衝突時,總是採用目標資料存儲中的更改。
- 合併:在本策略中,出現衝突時,將合併更改。
- 記錄衝突:在本策略中,將推遲或記錄衝突。
瞭解同步流程
SyncOrchestrator 實例控制同步會話和會話期間的資料流程。同步流程始終是單向的,您將一個源提供程式附加到源副本,將一個目標提供程式附加到目標副本。第一步是創建源和目標提供程式,為它們分配唯一的副本 ID,將兩個提供程式附加到源和目標副本:
FileSyncProvider sourceProvider =
new FileSyncProvider(sourceReplicaID, @"D:\Source");
FileSyncProvider destinationProvider =
new FileSyncProvider(destinationReplicaID, @"D:\Destination");
接下來,創建一個 SyncOrchestrator 實例,將兩個提供程式附加給它。 對 SyncOrchestrator 實例調用 Synchronize 方法,在源和目標提供程式之間創建一個連結:
SyncOrchestrator syncAgent = new SyncOrchestrator();
syncAgent.LocalProvider = sourceProvider;
syncAgent.RemoteProvider = destProvider;
syncAgent.Synchronize();
此後,Sync Framework 可以在進行同步會話時進行很多調用。 我們來看一看。
對源和目標提供程式都調用 BeginSession,指示同步提供程式要加入同步會話。 請注意,如果會話無法啟動或提供程式初始化不正確,BeginSession 方法會引發 InvalidOperationException:
public abstract void BeginSession(
SyncProviderPosition position,
SyncSessionContext syncSessionContext);
Sync Framework 對目標提供程式的實例調用 GetSyncBatchParameters。 目標提供程式返回其知識(特定副本所知的版本或更改的緊湊表示形式)和請求的批次處理大小。 此方法接受兩個輸出參數,即 batchSize 和 knowledge:
public abstract void GetSyncBatchParameters(
out uint batchSize,
out SyncKnowledge knowledge);
Sync Framework 對源提供程式調用 GetChangeBatch。 此方法接受兩個輸入參數,即目標的批次處理大小和知識:
public abstract ChangeBatch GetChangeBatch(
uint batchSize,
SyncKnowledge destinationKnowledge,
out object changeDataRetriever);
現在,源同步提供程式以 changeDataRetriever 物件的形式向目標提供程式發送更改的版本和知識的摘要。
對目標提供程式調用 ProcessChangeBatch 方法來處理更改:
public abstract void ProcessChangeBatch(
ConflictResolutionPolicy resolutionPolicy,
ChangeBatch sourceChanges,
object changeDataRetriever,
SyncCallbacks syncCallbacks,
SyncSessionStatistics sessionStatistics);
對批次處理中的每個更改,都對目標同步提供程式調用 SaveItemChange。 如果要實現自己的自訂提供程式,應使用源副本發送的更改更新目標副本,然後用源知識更新中繼資料存儲中的中繼資料:
void SaveItemChange(SaveChangeAction saveChangeAction,
ItemChange change, SaveChangeContext context);
對目標同步提供程式調用 StoreKnowledgeForScope,在中繼資料存儲中保存知識:
public void StoreKnowledgeForScope(
SyncKnowledge knowledge,
ForgottenKnowledge forgottenKnowledge)
對源和目標提供程式都調用 EndSession,指示同步提供程式將離開它先前加入的同步會話:
public abstract void EndSession(
SyncSessionContext syncSessionContext);
自訂同步提供程式
現在,您已經瞭解了預設同步提供程式的工作原理。 如前所述,您還可以實現自訂同步提供程式。 自訂同步提供程式可擴展內置同步提供程式的功能。 如果要同步的資料存儲沒有提供程式,就需要自訂同步提供程式。 此外,也可以創建自訂同步提供程式來實現更改單元,從而更好地控制更改跟蹤,減少衝突數量。
要設計自己的同步提供程式,需要創建一個類來擴展 KnowledgeSyncProvider 抽象類別,並實現 IChangeDataRetriever 和 INotifyingChangeApplierTarget 介面。 請注意,這些類和介面是 Microsoft.Synchronization 命名空間的一部分。
舉一個自訂提供程式的示例,假定要實現一個同步提供程式在資料庫之間同步資料。 這只是一個簡單示例的概述,可以對它進行擴展來實現更為複雜的方案。
首先,在 SQL Server 2008 中創建三個數據庫(我將它們命名為 ReplicaA、ReplicaB 和 ReplicaC),在每個資料庫中都創建一個名為 Student 的表。 自訂提供程式將同步這三個 Student 表中的記錄。 接下來,創建一個名為 Student 的實體,用於對 Student 表執行 CRUD 操作。
創建一個名為 Student 的類,該類具有欄位 StudentID、FirstName 和 LastName,然後創建在資料庫中執行 CRUD 操作必需的説明程式方法:
public class Student {
public int StudentID { get; set; }
public String FirstName { get; set; }
public String LastName { get; set; }
//Helper methods for CRUD operations
...
}
創建一個名為 CustomDBSyncProvider 的類,並從 KnowledgeSyncProvider、IChangeDataRetriever、INotifyingChangeApplierTarget 和 IDisposable 介面對它進行擴展:
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Synchronization;
using Microsoft.Synchronization.MetadataStorage;
public class CustomDBSyncProvider : KnowledgeSyncProvider,
IChangeDataRetriever,
INotifyingChangeApplierTarget,IDisposable {
...
實現自訂資料庫同步提供程式中必需的方法,創建 UI 來顯示每個 Student 表的內容(有關詳細資訊,請參閱本文的代碼下載)。
現在,創建自訂同步提供程式的三個實例,將它們附加到每個 Student 資料庫表。 最後,在自訂同步提供程式的説明下,將一個副本的內容與其他副本的內容同步:
private void Synchronize(
CustomDBSyncProvider sourceProvider,
CustomDBSyncProvider destinationProvider) {
syncAgent.Direction =
SyncDirectionOrder.DownloadAndUpload;
syncAgent.LocalProvider = sourceProvider;
syncAgent.RemoteProvider = destinationProvider;
syncStatistics = syncAgent.Synchronize();
}
同步處理
可以看到,Sync Framework 提供了一個簡單但功能完善的同步平臺,這個平臺可以在離線和連線資料之間進行無縫同步。它可獨立于所用協定和數��存儲對資料進行同步。它可用於進行簡單的檔案備份,也便於針對基於協作的網路進行擴展。您還可以創建自訂同步提供程式來支援沒有現成同步提供程式的資料來源。
Joydip Kanjilal 是一位獨立軟體顧問,也是 2007 年度以來 ASP.NET 領域的 Microsoft MVP。他還是一位演講家和作家,您可以通過 aspadvice.com/blogs/joydip 瞭解他的書籍、文章和博客。
衷心感謝以下技術專家對本文的審閱:Liam Cavanagh