本文實驗用代碼請從這里下載: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更新,更完整的代碼可留給讀者自己去設計。貼張圖上來:

新聞熱點
疑難解答