处理微软MSF同步框架中的数据冲突

其实我是一演员 2009-04-29

首先,我们需要理解什么是数据冲突,它是怎么产生的。图1显示了最常见的数据冲突,当两个源同时更新一行数据时的情况。例如,假设有两个客户端(客户端A和客户端B)更新了本地数据缓存,然后同时同步到同一个服务器,此时客户端A和B接收到的是表x的同一个拷贝,客户端A更新了表x中第17行的电话号码,然后同步回服务器,与此同时,客户端B更新了表x中第17行的email地址,当客户端B同步回服务器时,冲突就发生了,此时服务器要决定究竟那个要写入主拷贝中。象这样的例子在应用程序逻辑中也存在,MSF提供了两个不同的方法来定义这个逻辑。

处理微软MSF同步框架中的数据冲突

图- 1 常见的同步冲突实例

冲突类型和解决办法

MSF定义了五个不同的冲突类型,它定义为ConflictType枚举量:

◆ ClientInsertServerInsert 以相同的主键创建一个新行。

◆ ClientUpdateServerUpdate 相同的行被更新,这是最常见的冲突,如图1所示。

◆ ClientUpdateServerDelete 在客户端更新的行,但在服务器上已经被删除了。

◆ ClientDeleteServerUpdate 在客户端删除的行,但在服务器上已经更新了。

◆ ErrorsOccurred 当发生错误时使用“catch all”(停止一切)预防行被插入、更新或删除。

当你看了图1中的例子后,你可能会认为只有当同步计划是双向的时候才会引发冲突,但并不仅仅是这样。设想一个只上载的情况,客户端不关心在服务器上的更新,它只是想将新的信息提交给服务器,该客户端会创建一行数据并同步到客户端。在某些情况下,有些讨厌的用户会打乱服务器判断数据行的行为,可能造成服务器认为这一行数据不再需要,因此将其删除了。在那个时候,客户端会对那一行做出修改并同步回服务器,但这也会造成冲突,因为客户端更新的行,在服务器上已经被删除了。这种情况下,只有将更新操作改为插入操作才能解决这个冲突。

除上面描述的冲突类型外,MSF还定义了三个内置的行为来解决冲突,它们定义在ApplyAction枚举量中:

◆ Continue 这是默认的行为,它允许你继续到列表中下一个冲突。

◆ RetryApplyingRow 将会重新尝试应用对行的修改,除非你使用某种方法修改了数据,否则就会失败(通常是在代码中使用自定义的冲突解决方案,匹配业务逻辑解决冲突)。

◆ RetryWithForceWrite 将会强制应用程序修改行(覆盖任何冲突的数据)。

在接下来的内容中,我们将会看到这些行为对某些冲突类型的处理结果。

使用ApplyChangeFailed解决冲突

知道冲突类型很重要,但你也需要知道是哪一行发生了冲突,MSF在DbServerSyncProvider和SqlCeClientSyncProvider上都提供了ApplyChangeFailed事件,允许你审查冲突信息,然后决定如何处理。在服务器和客户端SyncProvider上都有可能引发事件,取决于同步的阶段,ApplyChangeFailedEventArgs对象具有Action和Conflict属性,Action属性用于解决冲突,只要将其设为前一小节描述的ApplyAction类型的一个值即可;Conflict属性描述了更详细的冲突信息,如它的类型和发生冲突的行。

ApplyChangeFailedEventArgs对象还有一个Context属性,它允许你修改正在同步的数据,你可以使用它创建自己的冲突解决方案,这样你在处理复杂数据冲突时可以更加得心应手。

我们来看一些例子吧,我们创建了一个项目,显示一个跟踪顾客喜欢的数字的表,窗体的上半部分显示了服务端数据拷贝,窗体的下半部分显示了客户端缓存中的数据拷贝,我们可以修改任何一半的数据,然后保存数据到服务器和客户端,接着尝试同步数据,如果遇到数据冲突,会显示一个自定义窗体,让我们选择一个ApplyAction类型来解决冲突。

处理微软MSF同步框架中的数据冲突

图- 2 冲突解决方案示例窗体

我们通过修改数据缓存文件(.sync)的后端代码使用partial类将ApplyChangeFailed事件联系起来,在我们的例子代码如下:

我们还必须将下面的代码添加到客户端SyncProvider的构造器中(你可以在.designer.cs代码文件中找到它):

this.ApplyChangeFailed +=new<p>System.EventHandler</p>

(DataConflictsDataCacheClientSyncProvider_ApplyChangeFailed);

在ConflictResolverForm中,我们通过设置合适的ApplyAction告诉同步框架如何解决冲突,如下:

applyChangeEventArgs.Action = Microsoft.Synchronization.Data.ApplyAction.Continue;<p>applyChangeEventArgs.Action = Microsoft.Synchronization.Data.ApplyAction.RetryApplyingRow;
</p><p>applyChangeEventArgs.Action = Microsoft.Synchronization.Data.ApplyAction.RetryWithForceWrite;</p>

这些行为将会一一为你展示。

例1. ClientUpdateServerUpdate

在这个例子中,我们同时修改了服务端和客户端CustomerId等于2的行,然后尝试重新同步,在服务器端,我们修改了FirstName字段的值为Chad,在客户端,我们修改FavoriteNumber字段的值为5。在服务器端SyncProvider上我们遇到了ClientUpdateServerUpdate冲突:

处理微软MSF同步框架中的数据冲突

图- 3 在服务器上遇到了ClientUpdateServerUpdate冲突

如果我们选择继续,服务器端的修改就会胜出,客户端的修改将会丢失:

处理微软MSF同步框架中的数据冲突

图- 4 在服务器上遇到ClientUpdateServerUpdate冲突选择了ApplyAction.Continue行为后的结果

如果我们选择了RetryApplyingRow,会重新陷入错误,因为我们还没有修改数据,因此我们仅仅需要再次显示我们的冲突解决窗体。

如果我们选择RetryWithForceWrite,客户端的修改会胜出,而服务器端的修改就会丢失:

处理微软MSF同步框架中的数据冲突

图- 5 在服务器上遇到ClientUpdateServerUpdate冲突选择了ApplyAction.RetryWithForceWrite行为后的结果

例2. ClientInsertServerInsert

在这个例子中,我们在服务器上插入了一行数据,CustomerId(主键)等于3,我们也在客户端插入了一行数据,CustomerId(主键)也等于3。

处理微软MSF同步框架中的数据冲突

图- 6 在服务器和客户端都插入一行数据

当我们尝试同步时,在服务器SyncProvider上我们遇到了ClientInsertServerInsert冲突:

处理微软MSF同步框架中的数据冲突

图- 7 在服务器上遇到的ClientInsertServerInsert冲突

如果我们选择继续,我们会遇到另一个冲突ClientInsertServerInsert,不过这次冲突是在客户端SyncProvider上:

处理微软MSF同步框架中的数据冲突

图- 8 在客户端上的ClientInsertServerInsert冲突

如果我们再次选择继续,你会看到两边的数据都没有改变,每个数据库都保持了它们自己的修改,实际上就是忽略了冲突。

处理微软MSF同步框架中的数据冲突

图- 9 在服务器上遇到ClientInsertServerInsert冲突,在客户端上选择ApplyAction.Continue行为后的结果

和前面的例子一样,如果我们选择RetryApplyingRow,我们将会继续得到一个冲突对话框。

如果我们选择RetryWithForceWrite,在客户端上不会再出现冲突,客户端的修改将会上载,覆盖服务器上的修改:

处理微软MSF同步框架中的数据冲突

图- 10 在服务器上遇到ClientInsertServerInsert冲突,选择ApplyAction.RetryWithForceWrite行为后的结果

使用ConflictResolver解决客户端冲突

如果指定一种解决方案不能满足你的需要,并且你想精简你的代码,MSF在SqlCeClientSyncProvider上提供了一个附加属性ConflictResolver,你可以单独设置它的属性为下面三个值的一个来解决所有五种冲突:

◆ ClientWins

◆ ServerWins

◆ FireEvent

默认情况下,最后一个选项FireEvent是默认值。

设置ConflictResolver的代码可以和ApplyChangeFailed处理程序一起添加到客户端SyncProvider的构造器中:

this.ConflictResolver.ClientDeleteServerUpdateAction = <p>Microsoft.Synchronization.Data.ResolveAction.ServerWins;
</p><p>this.ConflictResolver.ClientUpdateServerDeleteAction = 
</p><p>Microsoft.Synchronization.Data.ResolveAction.ClientWins;
</p><p>this.ConflictResolver.ClientInsertServerInsertAction = 
</p><p>Microsoft.Synchronization.Data.ResolveAction.FireEvent;
</p><p>this.ConflictResolver.ClientUpdateServerUpdateAction = 
</p><p>Microsoft.Synchronization.Data.ResolveAction.FireEvent;</p>
this.ApplyChangeFailed +=new <p>System.EventHandler
</p><p>(DataConflictsDataCacheClientSyncProvider_ApplyChangeFailed);</p>

在上面的例子中,我们总是允许更新胜过删除,我们通过在为ApplyChangeFailed事件定义的处理程序中自定义业务逻辑让更新冲突得到解决。

相关推荐