微软同步框架入门之三--分析生成的同步类文件
在前两篇文章当中,我介绍了SnapShot(快照方式)和“只同步新更改和增量更改”这两种同步方
式。将使用设计器同步向导生成了两个相应的DEMO。今天我们会一起分析一下这两个DEMO中相应的同
步文件内容和相互差异(DEMO下载,请点击这里)。
首先要分析的DEMO是“只同步新更改和增量更改”,我们用VS2008打开DEMO的解决方案方案,如
下图:
之前所说的关于MSF为我们生成的主要的类代码就保存在了BiDirectSyncData.sync文件中,下面
先分析一下BiDirectSyncDataClientSyncProvider这个类,如下:
Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider {
public BiDirectSyncDataClientSyncProvider() {
this.ConnectionString = global::MSF_WinFormDemo.Properties.Settings.
Default.ClientBiDirectSynce2ConnectionString;
}
public BiDirectSyncDataClientSyncProvider(string connectionString) {
this.ConnectionString = connectionString;
}
}
其实现代码相对简单,只定义和配置了获取本地数据库文件链接的方法,另外就是其是继承自:
Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider,而最终的父类指向是
SyncProvider(注:与BiDirectSyncDataServerSyncProvider最终的父类相同)。
这类的功能就是针对 SQL Server Compact 对要与客户端进行通信的(客户端)同步提供程序支持,
并将同步代理(sync Agent)与客户端数据库的特定实现(本文中为SQLCE)分离开来。
而图中的Sync Agent(代理)是通过BiDirectSyncDataSyncAgent类提供的。在之前我们所做的
DEMO中,进行数据同步时,都是通过此代理进行的。可以说这是我们同步的核功能代码,下面是其构造
方法,如下:
this.InitializeSyncProviders();
this.InitializeSyncTables();
this.OnInitialized();
}
首先它会调用InitializeSyncProviders方法来构造两个对象实例,如下:
private void InitializeSyncProviders() {
// Create SyncProviders.
this.RemoteProvider = new BiDirectSyncDataServerSyncProvider();//远程服务器
this.LocalProvider = new BiDirectSyncDataClientSyncProvider();//本地数据
}
注:RemoteProvider,LocalProvider属性继承自BiDirectSyncDataSyncAgent的父类(SyncAgent)
这样该代理就会拥有同时对客户端和服务器端进行数据操作的对象实例。然后就是要清楚同步时
的要操作(同步)的内容了,也就是上面初始化代码的第二行,InitializeSyncTables方法。如下:
private void InitializeSyncTables() {
// Create SyncTables.
this._dnt_posts1SyncTable = new dnt_posts1SyncTable();
this._dnt_posts1SyncTable.SyncGroup = new Microsoft.Synchronization.
Data.SyncGroup("dnt_posts1SyncTableSyncGroup");
this.Configuration.SyncTables.Add(this._dnt_posts1SyncTable);
}
在这个方法中首先是声明一个dnt_posts1SyncTable对象实例,而该实例的类声明时是继承自
Microsoft.Synchronization.Data.SyncTable(也就是我们上图中所说的那个SyncTable)。其自身是
表示在同步过程中涉及的表的客户端设置。而dnt_posts1SyncTable的构造方法中会设置同步数据时所
要同步的本地数据库中的表名称,以及定义可用于在客户端数据库中创建表的选项(CreationOption):
CreationOption = Microsoft.Synchronization.Data.TableCreationOption.DropExistingOrCreateNewTable;
在这里有发现写说明一下的就是“在客户端数据库中创建表的选项”有五种,分别对不同的使用
意思,依次是:
DropExistingOrCreateNewTable:在客户端数据库中创建表。如果某个现有表具有相同的名称,则
先删除现有表。
TruncateExistingOrCreateNewTable:如果客户端数据库中不存在要创建的表,则在客户端数据库
中创建该表。如果某个现有表具有相同的名称,则删除此表中的所有行。
UploadExistingOrCreateNewTable:如果客户端数据库中不存在要创建的表,则在客户端数据库中
创建该表。如果某个现有表具有相同的名称,则在首次同步时上载此表中的所有行。
此选项仅在 SyncDirection 为 Bidirectional 或 UploadOnly 时有效。
UseExistingTableOrFail:使用客户端数据库中与要创建的表同名的现有表。如果该表不存在,则
引发异常。
通过该项设置,就可以在同步数据时,对本地数据表中已存在同名表示时,进行相应的后续操作了。
当然,上面的InitializeSyncTables方法中还包括对SyncTables.Add(this._dnt_posts1SyncTable);
方法的调用, 因为SyncTables返回是SyncTableCollection类型(同步表对象的集合),通过这个设置会
最终完成本次要同步那些数据(表)的设置。
好的,到这里,基本上完成了对客户端同步程序的代码介绍。接下来就是服务器端的同步代码了。
服务器端同步代码主要包括两个类:
1.dnt_posts1SyncAdapter,封装一组命令,用于获取架构信息以及在服务器数据库中检索和应用更
改等操作。
2.BiDirectSyncDataServerSyncProvider,提供与服务器数据库同步通信的程序,并将同步代理与
数据库的特定实现进行分离(上面所说的Agent中的RemoteProvider属)。
首先要说明的就是SyncAdapter类:dnt_posts1SyncAdapter,其主要提供了对于服务器端数据进行
CRUD的SQL对象的初始化和参数声明,其构造方法如下:
this.InitializeCommands();
this.InitializeAdapterProperties();
this.OnInitialized();
}
上面的InitializeCommands()方法即是对SQL对象的初始化(SQL对象由父类中提供):
public IDbCommand InsertCommand { get; set; }
public IDbCommand SelectConflictDeletedRowsCommand { get; set; }
public IDbCommand SelectConflictUpdatedRowsCommand { get; set; }
public IDbCommand SelectIncrementalDeletesCommand { get; set; }
public IDbCommand SelectIncrementalInsertsCommand { get; set; }
public IDbCommand SelectIncrementalUpdatesCommand { get; set; }
public IDbCommand UpdateCommand { get; set; }
看来只要实现相应的命令对象实例绑定即可了。因为该方法的内容有些长,但并不复杂,下面简单对里面
的更新操作进行说明,其余的代码大家一看便知:
private void InitializeCommands() {
//之前是添回和删除SQL对象的设置
// dnt_posts1SyncTableUpdateCommand command.
this.UpdateCommand = new System.Data.SqlClient.SqlCommand();
this.UpdateCommand.CommandText = @"UPDATE dbo.dnt_posts1 SET [fid] = @fid, [tid] = @tid,
[parentid] = @parentid, [layer] = @layer, [poster] = @poster, [posterid] = @posterid,
[title] = @title, [postdatetime] = @postdatetime, [message] = @message, [ip] = @ip,
[lastedit] = @lastedit, [invisible] = @invisible, [usesig] = @usesig, [htmlon] = @htmlon,
[smileyoff] = @smileyoff, [parseurloff] = @parseurloff, [bbcodeoff] = @bbcodeoff,
[attachment] = @attachment, [rate] = @rate, [ratetimes] = @ratetimes, [LastEditDate] =
@LastEditDate, [CreationDate] = @CreationDate WHERE ([pid] = @pid) AND (@sync_force_write = 1
OR ([LastEditDate] <= @sync_last_received_anchor)) SET @sync_row_count = @@rowcount";
this.UpdateCommand.CommandType = System.Data.CommandType.Text;
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@fid", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@tid", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@parentid", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@layer", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@poster", System.Data.SqlDbType.NVarChar));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@posterid", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@title", System.Data.SqlDbType.NVarChar));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@postdatetime",
System.Data.SqlDbType.SmallDateTime));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@message", System.Data.SqlDbType.NText));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@ip", System.Data.SqlDbType.NVarChar));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@lastedit", System.Data.SqlDbType.NVarChar));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@invisible", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@usesig", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@htmlon", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@smileyoff", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@parseurloff", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@bbcodeoff", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@attachment", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@rate", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@ratetimes", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@LastEditDate", System.Data.SqlDbType.DateTime));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@CreationDate", System.Data.SqlDbType.DateTime));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@pid", System.Data.SqlDbType.Int));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@sync_force_write", System.Data.SqlDbType.Bit));
this.UpdateCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@sync_last_received_anchor",
System.Data.SqlDbType.DateTime));
System.Data.SqlClient.SqlParameter updatecommand_sync_row_countParameter =
new System.Data.SqlClient.SqlParameter("@sync_row_count", System.Data.SqlDbType.Int);
updatecommand_sync_row_countParameter.Direction = System.Data.ParameterDirection.Output;
this.UpdateCommand.Parameters.Add(updatecommand_sync_row_countParameter);
}
上面代码首先是对命令(SqlCommand)实例的相关属性绑定,包括CommandText,CommandType, Parameters。
当然细心的朋友会发现,上面的UpdateCommand.CommandText绑定时在SQL命令行的后半部有如下内容(摘录):
[LastEditDate] = @LastEditDate, [CreationDate] = @CreationDate WHERE ([pid] = @pid)
AND (@sync_force_write = 1 OR ([LastEditDate] <= @sync_last_received_anchor))
SET @sync_row_count = @@rowcount";
其中我们看到是[LastEditDate],[CreationDate]这两个表字段实际上就是在上一篇文章中我们使用同步
向导进行设计时所做的相应设置如下:
而@sync_force_write参数与@sync_last_received_anchor参数则是Synchronization Services提
供的一组会话变量中的两个,其中@sync_force_write会强制应用由于冲突或错误而未能应用的更改(强制写
入服务端数据库),而@sync_last_received_anchor则是将对在上次收到的定位点值(修改时间)之后和新
收到的定位点值(修改时间)之前所做的更改进行同步,说白了就是将最近一定修改的时间与当前修改发生的时
间进行同步,以便于本次只修改那些介于这两个时间点中间的数据即可。同时在完成本次同步时,将这个变量修
改为本次修改的时间(定位点),以便下次同步时做为新的时间依据(定位点)。
当然这种类型的变量的主要用场就是在同步期间通过它们将值传递给SyncAdapter 命令。这些变量的指定
方式与 ADO.NET 命令中查询或存储过程的其他参数相似(上面代码中已给出)。
当然Synchronization Services还提供了一些别的变量,这里为了大家开发方便,直接罗列一下了。
用法: 用于标识当前正在同步的客户端。ID 通常用于冲突检测,并防止在双向同步过程中将更改
发送回客户端。有关更多信息,请参见如何:在客户端和服务器间交换双向增量数据更改。
默认情况下,Synchronization Services 用 GUID 标识每个客户端,而 GUID 由 sync_client_id
返回。此外,还可以创建 ID 的散列值并在查询中使用 sync_client_id_hash;或者将 GUID 映射到一个
整数值并使用 sync_originator_id。
会话变量:sync_last_received_anchor, sync_new_received_anchor
用法: 用于定义在会话期间要同步的更改集。在当前同步期间,为 SelectNewAnchorCommand 属性
指定的命令提供一个新的定位点值。将对在上次收到的定位点值之后和新收到的定位点值之前所做的更
改进行同步。然后,存储新收到的定位点并在下一次同步时将其用作上一次收到的定位点值。
会话变量:sync_force_write
用法: 与 RetryWithForceWrite 的 ApplyAction 一起使用,强制应用由于冲突或错误而未能应用
的更改。
会话变量:sync_row_count
用法:返回服务器上受上一次操作影响的行数。在 SQL Server 数据库中,@@ROWCOUNT 提供此变量
的值。行数为 0 指示操作失败,通常是由于冲突或错误。
会话变量:sync_initialized
用法:返回一个值,指示当前同步是初始同步(值为 0)还是后续同步(值为 1)。
会话变量:sync_table_name 和sync_group_name
用法:当必须在查询中指定表名或组名时使用。
会话变量:sync_batch_count、sync_batch_size 和 sync_max_received_anchor
用法:当进行批量更改时使用。有关更多信息,请参见如何:指定更改的顺序和批大小。
会话变量:sync_session_id
用法:返回标识当前同步会话的 GUID 值。
看到这里,大家也就清楚了,所谓“只同步新更改和增量更改”只是在 CUD这类SQLCOMAND中加入
相应的判断条件即完成了相应的数据操作了,这的确要比“整表”同步(下面我们会看到)在效率速度上要
高多了。
当然,除了必要的CUD操作,同步设计器还会为我们生成一些其它的SQL命令,比如说:
SelectConflictDeletedRowsCommand: 获取或设置查询或存储过程,用于标识与其他更改相
冲突的已删除行。 在本DEMO中的部分设置如下:
this.SelectConflictDeletedRowsCommand.CommandText = "";
this.SelectConflictDeletedRowsCommand.CommandType = System.Data.CommandType.Text;
this.SelectConflictDeletedRowsCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@pid", System.Data.SqlDbType.Int));
SelectConflictUpdatedRowsCommandl: 获取或设置查询或存储过程,用于标识与其他更改
相冲突的已更新行。在本DEMO中的部分设置如下:
this.SelectConflictUpdatedRowsCommand.CommandText = @"SELECT [pid], [fid], [tid], [parentid],
[layer], [poster], [posterid], [title], [postdatetime], [message], [ip], [lastedit],
[invisible], [usesig], [htmlon], [smileyoff], [parseurloff], [bbcodeoff], [attachment],
[rate], [ratetimes], [LastEditDate], [CreationDate] FROM dbo.dnt_posts1 WHERE ([pid] = @pid)";
this.SelectConflictUpdatedRowsCommand.CommandType = System.Data.CommandType.Text;
this.SelectConflictUpdatedRowsCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@pid", System.Data.SqlDbType.Int));
SelectIncrementalInsertsCommand: 获取或设置查询或存储过程,用于检索自上次同步之后
在服务器数据库中进行的插入。本DEMO中的部分设置如下:
this.SelectIncrementalInsertsCommand.CommandText = @"SELECT [pid], [fid], [tid], [parentid],
[layer], [poster], [posterid], [title], [postdatetime], [message], [ip], [lastedit],
[invisible], [usesig], [htmlon], [smileyoff], [parseurloff], [bbcodeoff], [attachment],
[rate], [ratetimes], [LastEditDate], [CreationDate] FROM dbo.dnt_posts1 WHERE ([CreationDate] >
@sync_last_received_anchor AND [CreationDate] <= @sync_new_received_anchor)";
this.SelectIncrementalInsertsCommand.CommandType = System.Data.CommandType.Text;
this.SelectIncrementalInsertsCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@sync_last_received_anchor", System.Data.SqlDbType.DateTime));
this.SelectIncrementalInsertsCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@sync_new_received_anchor", System.Data.SqlDbType.DateTime));
SelectIncrementalDeletesCommand: 获取或设置查询或存储过程,用于检索自上次同步之后
在服务器数据库中进行的删除。在本DEMO中的部分设置如下:
this.SelectIncrementalDeletesCommand.CommandText = "SELECT [pid], [DeletionDate] FROM
dbo.dnt_posts1_Tombstone WHERE (@sync_initializ" +
"ed = 1 AND [DeletionDate] > @sync_last_received_anchor AND [DeletionDate] <= @sy" +
"nc_new_received_anchor)";
this.SelectIncrementalDeletesCommand.CommandType = System.Data.CommandType.Text;
this.SelectIncrementalDeletesCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@sync_initialized", System.Data.SqlDbType.Bit));
this.SelectIncrementalDeletesCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@sync_last_received_anchor", System.Data.SqlDbType.DateTime));
this.SelectIncrementalDeletesCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@sync_new_received_anchor", System.Data.SqlDbType.DateTime));
SelectIncrementalUpdatesCommand: 获取或设置查询或存储过程,用于检索自上次同步之后
在服务器数据库中进行的更新。在本DEMO中的部分设置如下:
this.SelectIncrementalUpdatesCommand.CommandText = @"SELECT [pid], [fid], [tid], [parentid],
[layer], [poster], [posterid], [title], [postdatetime], [message], [ip], [lastedit], [invisible],
[usesig], [htmlon], [smileyoff], [parseurloff], [bbcodeoff], [attachment], [rate], [ratetimes],
[LastEditDate], [CreationDate] FROM dbo.dnt_posts1 WHERE ([LastEditDate] > @sync_last_received_anchor AND [LastEditDate] <= @sync_new_received_anchor AND [CreationDate] <= @sync_last_received_anchor)";
this.SelectIncrementalUpdatesCommand.CommandType = System.Data.CommandType.Text;
this.SelectIncrementalUpdatesCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@sync_last_received_anchor", System.Data.SqlDbType.DateTime));
this.SelectIncrementalUpdatesCommand.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@sync_new_received_anchor", System.Data.SqlDbType.DateTime));
因为语句很简单,这里不一一介绍了。更多信息大家可以查看SDK中的文档即可。
说到这里,关于dnt_posts1SyncAdapter这个服务器端的数据Adapter操作类就介绍的差不多了。
下面是BiDirectSyncDataServerSyncProvider类,其实这两个服务器端类的关系是一个封装引用关系,
因为BiDirectSyncDataServerSyncProvider类中提供了对dnt_posts1SyncAdapter的属性引用,所
以如果我们有对dnt_posts1SyncAdapter的访问操作,最好通过这个类进行访问。我们可以通过对该
类的构造方法来大致了解一下这个类的工作流程:
string connectionString = global::MSF_WinFormDemo.Properties.Settings.Default.ServertestConnectionString;
this.InitializeConnection(connectionString);
this.InitializeSyncAdapters();
this.InitializeNewAnchorCommand();
this.OnInitialized();
}
从代码中可以看到,其构造方法首先会调用config文件中的设置来初始化一个SQL链接如下:
然后就是对相应的Adapter进行初始化了,代码如下:
private void InitializeSyncAdapters() {
this._dnt_posts1SyncAdapter = new dnt_posts1SyncAdapter();
this.SyncAdapters.Add(this._dnt_posts1SyncAdapter);
}
紧接着就是对之前所说的Synchronization Services进行初始化:
private void InitializeNewAnchorCommand() {
this.SelectNewAnchorCommand = new System.Data.SqlClient.SqlCommand();
this.SelectNewAnchorCommand.CommandText = "Select @sync_new_received_anchor = GETUTCDATE()";
this.SelectNewAnchorCommand.CommandType = System.Data.CommandType.Text;
System.Data.SqlClient.SqlParameter selectnewanchorcommand_sync_new_received_anchorParameter = new System.Data.SqlClient.SqlParameter("@sync_new_received_anchor", System.Data.SqlDbType.DateTime);
selectnewanchorcommand_sync_new_received_anchorParameter.Direction = System.Data.ParameterDirection.Output;
this.SelectNewAnchorCommand.Parameters.Add(selectnewanchorcommand_sync_new_received_anchorParameter);
}
这里要注意的是"Select @sync_new_received_anchor = GETUTCDATE()" 这一句,因为这里使
用的是GETUTCDATE(),即以UTC 时间(通用协调时间或格林尼治标准时间)表示的系统当前日期。所以在本
地数据库中的相应的[LastEditDate],[CreationDate]字段中的数据都比我机器上的时间早个小时(因为我的
机器时间设置在“东八区”) 。这里可修改为GETDATE()即可。
好了,聊到这里,今天的内容就差不多了,相应大家对“只同步新更改和增量更改”这种同步方式所生成的
代码清楚了一些。下面再简要说明一下SnapShot(快照方式)方式的生成文件(SyncSnapData.sync)。
相比较“只同步新更改和增量更改”方式,快照方式的结构与其大同小异。区别主要体现在了服务器端的
SyncAdapter类上。大家可以看一下源码中的dnt_topicsSyncAdapter类的InitializeCommands方法即可,
因为快照方式不用创建[LastEditDate],[CreationDate] 等用于比较差异的字段。所以在查询条件上(where)
要精简了不少(没有了关于[LastEditDate] , [CreationDate]等字段的比较和更新操作)。另外就是其只有
SelectIncrementalInsertsCommand命令对象而没有其余的几个对象(SelectConflictDeletedRowsCommand,
SelectIncrementalUpdatesCommand等,因为整表同步时用不上了)。
好了,今天的内部代码很多,但对照ado.net同步框架图,相应大家心里已经有数了。其余在MSF设计向
导所生成的代码中,还包括一类文件,即相应表的数据集(dataset)文件。这类文件中包括了对客户端数据
进行操作(CRUD)的一些通用方法(本地Adapter提供),以及序列化,数据集的Clone,相应表的实体类
对象(TypedTableBase类型),本地事务绑定等,并最终以TableAdapterManager类的方式进行相关实
例封装和绑定。相关内容可以参数DEMO中的如下即可:
SqlCeDB\BiDirectSynceDataSet.Designer.cs文件
SqlCeDB\LocalDataSet_Topic.Designer.cs文件
今天的内容就先到这里了,有兴趣的朋友可以在回复中进行讨论。
原文链接:http://www.cnblogs.com/daizhj/archive/2008/11/17/1335185.html
作者: daizhj, 代震军
Tags: 微软同步框架,ado.net,sqlce
网址: http://daizhj.cnblogs.com/
DEMO下载,请点击这里:)