让DataAdapter实现KeyAndModifiedField更新

80酷酷网    80kuku.com

  

本文实验用代码请从这里下载:。

先在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;   StringBuilder commandTextBuilder = new StringBuilder();   StringBuilder modifiedFieldsBuilder = new StringBuilder();   StringBuilder whereClauseBuilder = new StringBuilder();   StringBuilder tableFieldsBuilder = new StringBuilder();   commandTextBuilder.Append("UPDATE " + Delimit(TableName));   foreach(DataColumnMapping map in mappings.ColumnMappings)   {      // 判断该列是否发生修改      if(!row[map.DataSetColumn, DataRowVersion.Current].Equals(row[map.DataSetColumn, DataRowVersion.Original]))      {         if (modifiedFieldsBuilder.ToString() != "")          {            modifiedFieldsBuilder.Append(", ");         }                        paraName = "" + map.SourceColumn + "Current";         modifiedFieldsBuilder.Append(Delimit(map.SourceColumn) + " = " + paraName);         AddParameterToCommand(cmd, paraName);      }   }   commandTextBuilder.Append(" SET " + modifiedFieldsBuilder.ToString());   // 添加主键约束   paraName = "" + this.keyFieldName + "Original";   whereClauseBuilder.Append(Delimit(this.keyFieldName) + " = " + paraName);   AddParameterToCommand(cmd, paraName);   foreach(DataColumnMapping map in mappings.ColumnMappings)   {      // 判断该列是否发生修改      if(!row[map.DataSetColumn, DataRowVersion.Current].Equals(row[map.DataSetColumn, DataRowVersion.Original]))      {         if (whereClauseBuilder.ToString() != "")          {            whereClauseBuilder.Append(" AND ");         }         paraName = "" + map.SourceColumn + "Original";         whereClauseBuilder.Append(Delimit(map.SourceColumn) + " = " + paraName);         AddParameterToCommand(cmd, paraName);      }   }   commandTextBuilder.Append(" WHERE " + whereClauseBuilder.ToString());            if (RefreshRowAfterUpdate)    {      foreach(DataColumnMapping map in mappings.ColumnMappings)      {         if (tableFieldsBuilder.ToString() != "")          {            tableFieldsBuilder.Append(", ");         }         tableFieldsBuilder.Append(Delimit(map.SourceColumn));      }      tableFieldsBuilder.Append(" FROM " + TableName + " WHERE " +           Delimit(this.keyFieldName) + " = " + this.keyFieldName + "Original");      commandTextBuilder.Append("; \r\n\r\nSELECT " +             tableFieldsBuilder.ToString());   }   cmd.CommandText = commandTextBuilder.ToString();   return cmd;}

通过这种方式更新数据可以减少Update命令的复杂度,尤其是在网络带宽受到限制的时候,能够减少命令长度,提高通讯效率。但程序编写比较麻烦。在上面的程序中仅仅实现了UPDATE命令的KeyAndModifiedField更新,更完整的代码可留给读者自己去设计。贴张图上来:

分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: