日期:2011-07-01  浏览次数:20467 次

本文实验用代码请从这里下载:KeyAndModifiedFieldInDataAdapter.rar

先在SQL Server 2000中建立一名为DBApp的数据库,然后用查询分析器执行SQL-GenDB目录下的.sql文件建立Student表。

让DataAdapter实现KeyAndModifiedField更新

使用DataAdapter(这里我用的是SqlDataAdapter,后面所有DataAdapter的地方均指SqlDataAdapter)进行数据库更新时,可以很容易的实现"只包括主键"、"在WHERE短语中包含所有列"以及"主键和时间戳列"的并发方式,但DataAdapter并没有为我们提供"主键和已修改列"的并发模式。因为该种并发模式尽管可以产生精简的UPDATE命令,但设计代价比较高。David Sceppa在它的《ADO.NET Core Reference》一书中仅仅说了一句可以在DataAdapter的RowUpdateing事件中进行处理,却没有详细的论述。为此我尝试了以此思路实现KeyAndModifiedFiled并发模式。

在代码实现过程中,主要遇到的问题包括:

1、缺乏有效的Schema信息。由于更新命令中SET短语包含的字段以及WHERE短语包含的字段都是动态创建出来的,要根据DataRow中修改的列进行创建,因此需要了解字段类型以及长度和其它相关信息。这些信息本应包含在Table的Schema信息中,但这些信息是设计时有生成器生成的(我反编译了一下DataAdapter的Designer设计器代码,发现微软通过了COM完成的底层实现并用.net进行调用),并且无法在RowUpdating事件中获取,因此如何保存足够的Schema信息就成为实现的一个难题。

2、动态生成更新命令后要动态的将DataRow中的数据存入不同的DataParameter中,如何动态获取不同版本的字段值并填入DataParameter中也具有一定难度。

3、由于缺少必要的Schema信息,所以很难获得主键信息。

4、DataSet中字段名可能与实际Table的字段名不同,它们之间是通过TableMapping完成映射的。在动态生成SQL命令时要根据TableMapping中的信息进行处理,不能出现字段名不相符的差错。

针对上面问题,在程序代码实现中主要采取了以下策略:

1、在向导生成DataAdapter时采用开放式并发,这样DataAdapter的Designer会生成所有字段的Current与Original类型的参数,并且保存在UpdateCommand的Parameters属性中。我的程序在执行时首先备份这些信息到自定义的paramCollection中,将来用Parameter名进行检索(DataParameters支持string类型的Indexer),这样就省去了了解Schema信息的麻烦。但主键信息仍然无法得到很好的解决,只能手工指定。

在程序初始化时会有类似如下几条命令,就是用来保存足够的Parameter信息和主键信息的。

private SqlParameterCollection paramCollection;private string keyFieldName = "id";paramCollection = sqlUpdateCommand1.Parameters;

在后面的AddParameterToCommand方法中,我们只需要根据参数名检索paramCollection,就可以得到对应参数的SqlType,而不再需要Schema信息了。

private void AddParameterToCommand(IDbCommand cmd, string paraName){   SqlParameter tmpSqlParameter;   tmpSqlParameter = new SqlParameter();   tmpSqlParameter.ParameterName = paraName;   tmpSqlParameter.SqlDbType = this.paramCollection[paraName].SqlDbType;   tmpSqlParameter.SourceVersion = this.paramCollection[paraName].SourceVersion;   tmpSqlParameter.SourceColumn = this.paramCollection[paraName].SourceColumn;   tmpSqlParameter.Size = this.paramCollection[paraName].Size;   tmpSqlParameter.Direction = this.paramCollection[paraName].Direction;   tmpSqlParameter.Precision = this.paramCollection[paraName].Precision;   cmd.Parameters.Add(tmpSqlParameter);}

2、通过使用Reflactor反编译DataAdapter类,可以看到里面已经有了一个名为ParameterInput的方法就是根据DataRow中的数据向SqlCommand里面填写参数用的,只是为internal类型。我将其拷贝出来,放到了我的程序代码中发挥作用,不过还需要做一些小的改动。

3、主键信息只能自己手工指定,由于在程序中没有获取数据库的Schema信息,所以只能手工指定。如果需要了解Schema信息,也可以自己设计程序实现。David Sceppa在《ADO.NET Core Reference》一书提供的工具中给了一个DataAdapter Builder工具,是用VB.net写的,里面实现的读取数据库表的Schema信息功能,可供参考。代码可以从书配套光盘CD下载(http://www.wenyuan.com.cn/Soft_Show.asp?SoftID=34)。

4、在RowUpdating事件中,我们可以通过SqlRowUpdatingEventArgs得到所需的DataRow和TableMapping以及相关的StatementType信息。然后利用这些信息实现动态生成更新命令,最后填入所需参数并执行。于是,我们便实现了KeyAndModified方式更新数据。

private void daStudent_RowUpdating(object sender, System.Data.SqlClient.SqlRowUpdatingEventArgs args){   //-- 在这段程序中我们只拦截UPDATE命令   if(args.StatementType != StatementType.Update)      return;   string strMsg;   strMsg = "Beginning Update...\r\n";   strMsg += "\r\n----------------------------\r\n";            SqlCommand cmd = GenerateUpdateCommand(args.Row, args.TableMapping, true);   cmd.Connection = args.Command.Connection;   cmd.Transaction = args.Command.Transaction;   args.Command = cmd;   string p = ParameterInput(args.Command.Parameters, args.StatementType, args.Row, args.TableMapping);   strMsg += "Command Text:\r\n\r\n";   strMsg += args.Command.CommandText + "\r\n\r\n----------------------------\r\n\r\n";   strMsg += p;   this.txtMessages.Text = strMsg;}private SqlCommand GenerateUpdateCommand(DataRow row, DataTableMapping mappings, bool RefreshRowAfterUpdate){   SqlCommand cmd = new SqlCommand();   string paraName="";   string TableName = mappings.DataSetTable;