【51CTO独家特稿】如果你刚刚接触微软同步框架,你可以通过本文开始学习,在下面的示例中,我们假设你已经对MSF框架有所了解。
首先,我们需要理解什么是数据冲突,它是怎么产生的。图1显示了最常见的数据冲突,当两个源同时更新一行数据时的情况。例如,假设有两个客户端(客户端A和客户端B)更新了本地数据缓存,然后同时同步到同一个服务器,此时客户端A和B接收到的是表x的同一个拷贝,客户端A更新了表x中第17行的电话号码,然后同步回服务器,与此同时,客户端B更新了表x中第17行的email地址,当客户端B同步回服务器时,冲突就发生了,此时服务器要决定究竟那个要写入主拷贝中。象这样的例子在应用程序逻辑中也存在,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类型来解决冲突。
图- 2 冲突解决方案示例窗体
我们通过修改数据缓存文件(.sync)的后端代码使用partial类将ApplyChangeFailed事件联系起来,在我们的例子代码如下:
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的构造器中(你可以在.designer.cs代码文件中找到它):
this.ApplyChangeFailed +=new |
(DataConflictsDataCacheClientSyncProvider_ApplyChangeFailed);
在ConflictResolverForm中,我们通过设置合适的ApplyAction告诉同步框架如何解决冲突,如下:
applyChangeEventArgs.Action = Microsoft.Synchronization.Data.ApplyAction.Continue; |
这些行为将会一一为你展示。
#p#
例1. ClientUpdateServerUpdate
在这个例子中,我们同时修改了服务端和客户端CustomerId等于2的行,然后尝试重新同步,在服务器端,我们修改了FirstName字段的值为Chad,在客户端,我们修改FavoriteNumber字段的值为5。在服务器端SyncProvider上我们遇到了ClientUpdateServerUpdate冲突:
图- 3 在服务器上遇到了ClientUpdateServerUpdate冲突
如果我们选择继续,服务器端的修改就会胜出,客户端的修改将会丢失:
图- 4 在服务器上遇到ClientUpdateServerUpdate冲突选择了ApplyAction.Continue行为后的结果
如果我们选择了RetryApplyingRow,会重新陷入错误,因为我们还没有修改数据,因此我们仅仅需要再次显示我们的冲突解决窗体。
如果我们选择RetryWithForceWrite,客户端的修改会胜出,而服务器端的修改就会丢失:
图- 5 在服务器上遇到ClientUpdateServerUpdate冲突选择了ApplyAction.RetryWithForceWrite行为后的结果
例2. ClientInsertServerInsert
在这个例子中,我们在服务器上插入了一行数据,CustomerId(主键)等于3,我们也在客户端插入了一行数据,CustomerId(主键)也等于3。
图- 6 在服务器和客户端都插入一行数据
当我们尝试同步时,在服务器SyncProvider上我们遇到了ClientInsertServerInsert冲突:
图- 7 在服务器上遇到的ClientInsertServerInsert冲突
如果我们选择继续,我们会遇到另一个冲突ClientInsertServerInsert,不过这次冲突是在客户端SyncProvider上:
图- 8 在客户端上的ClientInsertServerInsert冲突
如果我们再次选择继续,你会看到两边的数据都没有改变,每个数据库都保持了它们自己的修改,实际上就是忽略了冲突。
图- 9 在服务器上遇到ClientInsertServerInsert冲突,在客户端上选择ApplyAction.Continue行为后的结果
和前面的例子一样,如果我们选择RetryApplyingRow,我们将会继续得到一个冲突对话框。
如果我们选择RetryWithForceWrite,在客户端上不会再出现冲突,客户端的修改将会上载,覆盖服务器上的修改:
图- 10 在服务器上遇到ClientInsertServerInsert冲突,选择ApplyAction.RetryWithForceWrite行为后的结果
使用ConflictResolver解决客户端冲突
如果指定一种解决方案不能满足你的需要,并且你想精简你的代码,MSF在SqlCeClientSyncProvider上提供了一个附加属性ConflictResolver,你可以单独设置它的属性为下面三个值的一个来解决所有五种冲突:
◆ ClientWins
◆ ServerWins
◆ FireEvent
默认情况下,最后一个选项FireEvent是默认值。
设置ConflictResolver的代码可以和ApplyChangeFailed处理程序一起添加到客户端SyncProvider的构造器中:
this.ConflictResolver.ClientDeleteServerUpdateAction = |
this.ApplyChangeFailed +=new |
在上面的例子中,我们总是允许更新胜过删除,我们通过在为ApplyChangeFailed事件定义的处理程序中自定义业务逻辑让更新冲突得到解决。
小结
所有数据同步解决方案都需要数据冲突解决方案,使用微软的同步框架(MSF),它有一套内置的冲突解决机制,让你可以定义简单的冲突解决方案(ConflictResolver),也可以定义复杂的冲突的解决方案(通过ApplyChangeFailed事件自定义业务逻辑解决方案)。
【编辑推荐】