處理微軟MSF同步框架中的數(shù)據(jù)沖突
原創(chuàng)【51CTO獨家特稿】如果你剛剛接觸微軟同步框架,你可以通過本文開始學(xué)習(xí),在下面的示例中,我們假設(shè)你已經(jīng)對MSF框架有所了解。
首先,我們需要理解什么是數(shù)據(jù)沖突,它是怎么產(chǎn)生的。圖1顯示了最常見的數(shù)據(jù)沖突,當(dāng)兩個源同時更新一行數(shù)據(jù)時的情況。例如,假設(shè)有兩個客戶端(客戶端A和客戶端B)更新了本地數(shù)據(jù)緩存,然后同時同步到同一個服務(wù)器,此時客戶端A和B接收到的是表x的同一個拷貝,客戶端A更新了表x中第17行的電話號碼,然后同步回服務(wù)器,與此同時,客戶端B更新了表x中第17行的email地址,當(dāng)客戶端B同步回服務(wù)器時,沖突就發(fā)生了,此時服務(wù)器要決定究竟那個要寫入主拷貝中。象這樣的例子在應(yīng)用程序邏輯中也存在,MSF提供了兩個不同的方法來定義這個邏輯。
圖- 1 常見的同步?jīng)_突實例
沖突類型和解決辦法
MSF定義了五個不同的沖突類型,它定義為ConflictType枚舉量:
◆ ClientInsertServerInsert 以相同的主鍵創(chuàng)建一個新行。
◆ ClientUpdateServerUpdate 相同的行被更新,這是最常見的沖突,如圖1所示。
◆ ClientUpdateServerDelete 在客戶端更新的行,但在服務(wù)器上已經(jīng)被刪除了。
◆ ClientDeleteServerUpdate 在客戶端刪除的行,但在服務(wù)器上已經(jīng)更新了。
◆ ErrorsOccurred 當(dāng)發(fā)生錯誤時使用“catch all”(停止一切)預(yù)防行被插入、更新或刪除。
當(dāng)你看了圖1中的例子后,你可能會認(rèn)為只有當(dāng)同步計劃是雙向的時候才會引發(fā)沖突,但并不僅僅是這樣。設(shè)想一個只上載的情況,客戶端不關(guān)心在服務(wù)器上的更新,它只是想將新的信息提交給服務(wù)器,該客戶端會創(chuàng)建一行數(shù)據(jù)并同步到客戶端。在某些情況下,有些討厭的用戶會打亂服務(wù)器判斷數(shù)據(jù)行的行為,可能造成服務(wù)器認(rèn)為這一行數(shù)據(jù)不再需要,因此將其刪除了。在那個時候,客戶端會對那一行做出修改并同步回服務(wù)器,但這也會造成沖突,因為客戶端更新的行,在服務(wù)器上已經(jīng)被刪除了。這種情況下,只有將更新操作改為插入操作才能解決這個沖突。
除上面描述的沖突類型外,MSF還定義了三個內(nèi)置的行為來解決沖突,它們定義在ApplyAction枚舉量中:
◆ Continue 這是默認(rèn)的行為,它允許你繼續(xù)到列表中下一個沖突。
◆ RetryApplyingRow 將會重新嘗試應(yīng)用對行的修改,除非你使用某種方法修改了數(shù)據(jù),否則就會失?。ㄍǔJ窃诖a中使用自定義的沖突解決方案,匹配業(yè)務(wù)邏輯解決沖突)。
◆ RetryWithForceWrite 將會強制應(yīng)用程序修改行(覆蓋任何沖突的數(shù)據(jù))。
在接下來的內(nèi)容中,我們將會看到這些行為對某些沖突類型的處理結(jié)果。
使用ApplyChangeFailed解決沖突
知道沖突類型很重要,但你也需要知道是哪一行發(fā)生了沖突,MSF在DbServerSyncProvider和SqlCeClientSyncProvider上都提供了ApplyChangeFailed事件,允許你審查沖突信息,然后決定如何處理。在服務(wù)器和客戶端SyncProvider上都有可能引發(fā)事件,取決于同步的階段,ApplyChangeFailedEventArgs對象具有Action和Conflict屬性,Action屬性用于解決沖突,只要將其設(shè)為前一小節(jié)描述的ApplyAction類型的一個值即可;Conflict屬性描述了更詳細(xì)的沖突信息,如它的類型和發(fā)生沖突的行。
ApplyChangeFailedEventArgs對象還有一個Context屬性,它允許你修改正在同步的數(shù)據(jù),你可以使用它創(chuàng)建自己的沖突解決方案,這樣你在處理復(fù)雜數(shù)據(jù)沖突時可以更加得心應(yīng)手。
我們來看一些例子吧,我們創(chuàng)建了一個項目,顯示一個跟蹤顧客喜歡的數(shù)字的表,窗體的上半部分顯示了服務(wù)端數(shù)據(jù)拷貝,窗體的下半部分顯示了客戶端緩存中的數(shù)據(jù)拷貝,我們可以修改任何一半的數(shù)據(jù),然后保存數(shù)據(jù)到服務(wù)器和客戶端,接著嘗試同步數(shù)據(jù),如果遇到數(shù)據(jù)沖突,會顯示一個自定義窗體,讓我們選擇一個ApplyAction類型來解決沖突。
圖- 2 沖突解決方案示例窗體
我們通過修改數(shù)據(jù)緩存文件(.sync)的后端代碼使用partial類將ApplyChangeFailed事件聯(lián)系起來,在我們的例子代碼如下:
public partial class DataConflictsDataCacheServerSyncProvider
{
partial void OnInitialized()
{
this.ApplyChangeFailed += new
System.EventHandler
( DataConflictsDataCacheServerSyncProvider_ApplyChangeFailed);}
void DataConflictsDataCacheServerSyncProvider_ApplyChangeFailed(object sender,
Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs e)
{
ConflictResolverForm cr = new ConflictResolverForm();
cr.Text = "Server Data Conflict Detected";
cr.ApplyChangeEventArgs = e;
cr.ShowDialog();
}
}
public partial class DataConflictsDataCacheClientSyncProvider
{
void DataConflictsDataCacheClientSyncProvider_ApplyChangeFailed(
object sender, Microsoft.Synchronization.Data.ApplyChangeFailedEventArgs e)
{
ConflictResolverForm cr = new ConflictResolverForm();
cr.Text = "Client Data Conflict Detected";
cr.ApplyChangeEventArgs = e;
cr.ShowDialog();
}
}
我們還必須將下面的代碼添加到客戶端SyncProvider的構(gòu)造器中(你可以在.designer.cs代碼文件中找到它):
this.ApplyChangeFailed +=new |
(DataConflictsDataCacheClientSyncProvider_ApplyChangeFailed);
在ConflictResolverForm中,我們通過設(shè)置合適的ApplyAction告訴同步框架如何解決沖突,如下:
applyChangeEventArgs.Action = Microsoft.Synchronization.Data.ApplyAction.Continue; |
這些行為將會一一為你展示。
#p#
例1. ClientUpdateServerUpdate
在這個例子中,我們同時修改了服務(wù)端和客戶端CustomerId等于2的行,然后嘗試重新同步,在服務(wù)器端,我們修改了FirstName字段的值為Chad,在客戶端,我們修改FavoriteNumber字段的值為5。在服務(wù)器端SyncProvider上我們遇到了ClientUpdateServerUpdate沖突:
圖- 3 在服務(wù)器上遇到了ClientUpdateServerUpdate沖突
如果我們選擇繼續(xù),服務(wù)器端的修改就會勝出,客戶端的修改將會丟失:
圖- 4 在服務(wù)器上遇到ClientUpdateServerUpdate沖突選擇了ApplyAction.Continue行為后的結(jié)果
如果我們選擇了RetryApplyingRow,會重新陷入錯誤,因為我們還沒有修改數(shù)據(jù),因此我們僅僅需要再次顯示我們的沖突解決窗體。
如果我們選擇RetryWithForceWrite,客戶端的修改會勝出,而服務(wù)器端的修改就會丟失:
圖- 5 在服務(wù)器上遇到ClientUpdateServerUpdate沖突選擇了ApplyAction.RetryWithForceWrite行為后的結(jié)果
例2. ClientInsertServerInsert
在這個例子中,我們在服務(wù)器上插入了一行數(shù)據(jù),CustomerId(主鍵)等于3,我們也在客戶端插入了一行數(shù)據(jù),CustomerId(主鍵)也等于3。
圖- 6 在服務(wù)器和客戶端都插入一行數(shù)據(jù)
當(dāng)我們嘗試同步時,在服務(wù)器SyncProvider上我們遇到了ClientInsertServerInsert沖突:
圖- 7 在服務(wù)器上遇到的ClientInsertServerInsert沖突
如果我們選擇繼續(xù),我們會遇到另一個沖突ClientInsertServerInsert,不過這次沖突是在客戶端SyncProvider上:
圖- 8 在客戶端上的ClientInsertServerInsert沖突
如果我們再次選擇繼續(xù),你會看到兩邊的數(shù)據(jù)都沒有改變,每個數(shù)據(jù)庫都保持了它們自己的修改,實際上就是忽略了沖突。
圖- 9 在服務(wù)器上遇到ClientInsertServerInsert沖突,在客戶端上選擇ApplyAction.Continue行為后的結(jié)果
和前面的例子一樣,如果我們選擇RetryApplyingRow,我們將會繼續(xù)得到一個沖突對話框。
如果我們選擇RetryWithForceWrite,在客戶端上不會再出現(xiàn)沖突,客戶端的修改將會上載,覆蓋服務(wù)器上的修改:
圖- 10 在服務(wù)器上遇到ClientInsertServerInsert沖突,選擇ApplyAction.RetryWithForceWrite行為后的結(jié)果
使用ConflictResolver解決客戶端沖突
如果指定一種解決方案不能滿足你的需要,并且你想精簡你的代碼,MSF在SqlCeClientSyncProvider上提供了一個附加屬性ConflictResolver,你可以單獨設(shè)置它的屬性為下面三個值的一個來解決所有五種沖突:
◆ ClientWins
◆ ServerWins
◆ FireEvent
默認(rèn)情況下,最后一個選項FireEvent是默認(rèn)值。
設(shè)置ConflictResolver的代碼可以和ApplyChangeFailed處理程序一起添加到客戶端SyncProvider的構(gòu)造器中:
this.ConflictResolver.ClientDeleteServerUpdateAction = |
this.ApplyChangeFailed +=new |
在上面的例子中,我們總是允許更新勝過刪除,我們通過在為ApplyChangeFailed事件定義的處理程序中自定義業(yè)務(wù)邏輯讓更新沖突得到解決。
小結(jié)
所有數(shù)據(jù)同步解決方案都需要數(shù)據(jù)沖突解決方案,使用微軟的同步框架(MSF),它有一套內(nèi)置的沖突解決機制,讓你可以定義簡單的沖突解決方案(ConflictResolver),也可以定義復(fù)雜的沖突的解決方案(通過ApplyChangeFailed事件自定義業(yè)務(wù)邏輯解決方案)。
【編輯推薦】