First we try, then we trust

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

本文实验用代码请从这里下载: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;
   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更新,更完整的代码可留给读者自己去设计。贴张图上来:

posted on 2005-08-22 22:32  吕震宇  阅读(2588)  评论(5编辑  收藏  举报