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 使用同步元數據檢測副本中的更改。 同步元數據包含參與同步過程的所有文件和文件夾的有關信息。 實際上,有兩種同步元數據:副本元數據和項元數據。 文件同步提供程序存儲參與同步過程的所有文件和文件夾的元數據。 以後,它使用這些文件和文件夾的文件大小、屬性和上次訪問時間檢測更改。
打開 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 提供了一個簡單但功能完善的同步平台,這個平台可以在脫機和聯機數據之間進行無縫同步。 它可獨立於所用協議和數據存儲對數據進行同步。 它可用於進行簡單的文件備份,也便於針對基於協作的網絡進行擴展。 您還可以創建自定義同步提供程序來支持沒有現成同步提供程序的數據源。
本文配套源碼