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