本文实验用代码请从这里下载:keyandmodifiedfieldindataadapter.rar。
先在sql server 2000中建立一名为dbapp的数据库,然后用查询分析器执行sql-gendb目录下的.sql文件建立student表。
使用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更新,更完整的代码可留给读者自己去设计。贴张图上来:
新闻热点
疑难解答