当前位置: 移动技术网 > IT编程>开发语言>.net > 在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据

2017年12月12日  | 移动技术网IT编程  | 我要评论

青岛小琴系列,快乐病栋,打印机维修视频

导言:

  在前面的教程,我们对数据访问层进行扩展以支持数据库事务.数据库事务确保一系列的操作要么都成功,要么都失败。本文我们将注意力转到创建一个批更新数据界面.

  在本文,我们将创建一个gridview控件,里面的每一行记录都可以进行编辑(见图1),因此我们没有必要多添加一列来包含edit, update,和cancel按钮,而是在页面包含2个“update products”按钮,被点击时,遍历所有的产品并对数据库进行更新.让我们开始吧.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908114397.jpg
图1:gridview控件里的每一行记录都可以编辑

  注意:在第37章《datalist批量更新》里我们用一个datalist控件创建了一个批编辑界面, 那篇文章与本文的区别之一在于本文使用gridview控件且使用了事务.

考察设置所有gridview rows可编辑的步骤

  就像在第16章《》考察的那样,gridview控件使用内置的编辑功能编辑每一行记录。在其内部,gridview控件通过editindex属性来判断哪一行可编辑. 一旦gridview绑定到数据源之后,它就逐行检查,看哪行的index值与editindex的值匹配,如果找到的话,该行就呈现为编辑界面.如果是绑定列(boundfields),则呈现为一个textbox,其text值为对应的boundfield的datafield属性的值;如果是模板列(templatefields),那么呈现为edititemtemplate而不是itemtemplate.

  我们知道当某个用户点击某行的edit按钮时,页面产生回传,将该行的index值为gridview控件的editindex属性赋值,再重新绑定数据.当点击某行的cancel按钮后产生页面回传,在重新绑定数据以前,将editindex属性设置为-1.因为,对gridview控件的rows而言,开始时index值为0,而将editindex设为-1的话就变成只读模式了.

  如果只对行进行编辑,editindex属性工作正常,但不支持批编辑。要对gridview实施批编辑的话,我们必须使每行都呈现为编辑界面.为此,最简单的方法是将要编辑的列,转换为templatefield,然后在itemtemplate模板里创建编辑界面.在接下来的几步,我们将创建一个完整的可批编辑的gridview,在第一步,我们将创建一个gridview及其objectdatasource,并将boundfields和checkboxfield转换为templatefields。在第二步和第三步,我们将编辑界面从itemtemplates模板转移到edititemtemplates.

第一步:展示product信息

  首先,我们先创建一个显示产品信息的gridview.打开batchdata文件夹里的页面batchupdate.aspx,从工具箱拖一个gridview控件到页面,设id值为productsgrid,从其智能标签里绑定到一个名为productsdatasource的objectdatasource,设其调用productsbll class类的getproducts方法.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908115398.jpg
图2:设置objectdatasourc调用productsbll class类

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908111699.jpg
图3: 使用getproducts方法获取产品信息

  像gridview一样,该objectdatasource调用的方法也只能对每行记录起作用。为了批更新记录,我们必须在asp.net页面的后台代码类里多写些代码,批处理数据并传递给bll.因此,在objectdatasource的update, insert,和delete标签里选“(none)”. 点finish完成设置.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908126998.jpg
图4:在update, insert,和delete标签里选“(none)”

完成设置后,objectdatasource控件的声明代码看起来和下面的差不多:

<asp:objectdatasource id="productsdatasource" runat="server"
 oldvaluesparameterformatstring="original_{0}"
 selectmethod="getproducts" typename="productsbll">
</asp:objectdatasource>

  完成设置后,visual studio会向gridview控件添加boundfields以及一个 checkboxfield.就本文而言,我们只允许用户查看和编辑产品的名称、类别、价格、以及discontinued状态.将productname, categoryname, unitprice和 discontinued以外的列全部删除,并分别将头3个列的headertext属性设置为“product”, “category”,“price”。最后,启用gridview的分页、排序功能.

  此时,gridview控件含有3个boundfields(productname,categoryname,和unitprice)以及一个checkboxfield (discontinued).我们希望将这4个列转换为templatefields,并将编辑界面从templatefield的edititemtemplate模板转移到itemtemplate模板.

  注意:我们在第20章《》里探讨了如何创建并定制templatefields.我们将boundfields和checkboxfield转换成templatefields,然后再在itemtemplates模板里定制其编辑界面。如果有什么不清楚的,可参考前面的文章.

  从gridview的智能标签里,点“编辑列”,这将打开fields对话框,然后选中每一列,点击“convert this field into a templatefield”。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908129756.jpg
图5:将现有的boundfields和checkboxfield转换为templatefield

  现在每一列都是templatefield,我们将把编辑界面从edititemtemplates模板转移到itemtemplates模板.

第2步:创建productname, unitprice,和discontinued列的编辑界面

  创建productname, unitprice,和discontinued这3列的编辑界面是比较简单的,因为它们都在templatefield的edititemtemplate模板里定义好了的;而创建categoryname的编辑界面比较麻烦,因为我们需要创建一个dropdownlist控件来显示可用的categories,我们将在第3步实现.

  我们首先创建productname的编辑界面。在gridview控件的智能标签里点“编辑模板”,再点productname templatefield的edititemtemplate项.选中其中的textbox,将其复制、粘贴到productname templatefield的itemtemplate模板.将该textbox的id属性设置为productname.

  然后,在itemtemplate模板里添加一个requiredfieldvalidator控件,以确保用户输入的产品name不为空.将其controltovalidate属性设置为“productname”;errormessage属性为“you must provide the product's name.”;text属性为“*”.添加完后,屏幕看起来应该像图6那样:

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908129285.jpg
图6:productname templatefield现在包含一个textbox控件和一个 requiredfieldvalidator控件

  对unitprice编辑界面而言,先从edititemtemplate模板里将textbox拷贝到itemtemplate模板.然后,在textbox前面放置一个“$”符合,将其id属性设置为“unitprice”;columns属性设置为“8”.

  然后再添加一个comparevalidator控件,确保用户输入的是大于或等于$0.00的货币值.设其controltovalidate属性为“unitprice”;errormessage 属性为“you must enter a valid currency value. please omit any currency symbols.”;text属性为“*”;type属性为currency;operator属性为greaterthanequal;valuetocompare属性为“0”.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908125585.jpg
图7:添加一个comparevalidator控件以确保用户输入的是非负的货币值

  对discontinued templatefield而言,直接使用已经在itemtemplate模板里定义好了的checkbox,只需要设其id为“discontinued”,enabled属性为true.

第三步:创建categoryname的编辑界面

  categoryname templatefield的edititemtemplate模板里的编辑界面里包含一个textbox,其用来显示categoryname列的值,我们要将其替换为一个dropdownlist控件以显示categories.
注意:在第20章《》里我们详细地探讨了如何用dropdownlist控件来替换textbox控件。在此我们将过程一略而过,具体创建和设置dropdownlist控件的细节可参考第20章.

  从工具箱里拖一个dropdownlist控件到categorynametemplatefield的itemtemplate模板, 设其id为categories.通常情况下,我们会通过其智能标签来定义dropdownlists的数据源,来创建一个新的objectdatasource.然而,这将在itemtemplate模板里新添一个objectdatasource,后果是每一个gridview row都会创建一个objectdatasource实例.因此,我们在gridview的templatefields外创建objectdatasource.结束模板编辑,从工具箱拖一个objectdatasource到页面,放置在名为productsdatasource的objectdatasource控件下面。将该新o用getcategories method bjectdatasource命名为categoriesdatasource,设其使用categoriesbll class类的getcategories方法.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908125114.jpg
图8:设置该objectdatasource使用categoriesbll类

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908131414.jpg
图9:从getcategories方法获取数据

  因为该objectdatasource仅仅是用来检索数据,在update 和 delete标签里选 “(none)”.  点finish完成设置.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908138185.jpg
图10:在update和delete标签里选“(none)”

完成设置后,categoriesdatasource的声明代码看起来根下面的差不多:

<asp:objectdatasource id="categoriesdatasource" runat="server"
 oldvaluesparameterformatstring="original_{0}"
 selectmethod="getcategories" typename="categoriesbll">
</asp:objectdatasource>

  设置好后,返回categoryname templatefield的itemtemplate模板,在dropdownlist的智能标签里点“choose data source”,在数据源设置向导里,在第一个下拉列表里选categoriesdatasource;再下面的2个下拉列表里分别选categoryname和categoryid.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908131257.jpg
图11:将dropdownlist控件绑定到categoriesdatasource

  此时,dropdownlist控件虽然列出了所有的categories,但对绑定到gridviewrow里的产品而言,其并没有自动的选择产品对应的category.为此,我们将dropdownlist的selectedvalue值设置为产品的categoryid值。在dropdownlist的智能标签里点“edit databindings”,并将selectedvalue属性赋值为categoryid ,如图12:

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908136556.jpg
图12:将产品的categoryid值绑定到dropdownlist的selectedvalue属性

  还有最后一个问题,如果产品的categoryid为空的话,对selectedvalue的数据绑定将会抛出异常. 因为dropdownlist只列出了那些指定了categoryid值的产品,但不会列出那些categoryid值为null的产品.怎样解决呢?将dropdownlist的appenddataboundit属性设为rue,并向dropdownlist新添加一个item,忽略其value属性就像下面的声明代码那样:

<asp:dropdownlist id="categories" runat="server" appenddatabounditems="true"
 datasourceid="categoriesdatasource" datatextfield="categoryname"
 datavaluefield="categoryid" selectedvalue='<%# bind("categoryid") %>'>
 <asp:listitem value="">-- select one --</asp:listitem>
</asp:dropdownlist>

  我们注意到<asp:listitem value=""> “-- select one --”里,将value属性设置为一个空字符串.为什么要新添该item来处理值为null的情况?为什么要将value属性设置为一个空字符串呢?这些疑问可参考前面第20章《》

  注意:这里有一个关乎性能的潜在问题要提一下。因为每行记录都包含一个dropdownlist,其数据源为categoriesdatasource.每次登录页面时,都会调用categoriesbll class类的getcategories方法n次,这里n为gridview控件里行的数目.对getcategories的n次调用就会导致对数据库的n次查询.我们可以对返回结果进行缓存以减轻对数据库造成的影响;至于方式嘛,可以运用per-request caching策略,也可以在缓存层caching layer里使用sql高速缓存依赖性(sql caching dependency)或基于短时间缓存周期(a very short time-based expiry)的策略。对per-request caching策略的更多信息可参考文章《httpcontext.items – a per-request cache store》()

第四步:完善编辑界面

  在浏览器里查看该页面,就像图13所示,每行都使用itemtemplate模板,以包含其编辑页面。

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908142101.jpg
图13:每个gridview row都是可编辑的

  不过仍有一些问题。首先,unitprice值为四个小数点,为此,返回unitprice templatefield的itemtemplate模板, 在textbox的智能标签里点“edit databindings”,然后,将text属性格式指定为number.

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908144201.jpg
图14:将text格式指定为number

  然后,将discontinued列里的checkbox控件居中(而不是居左),在gridview的智能标签里点“编辑列”,选取左边方框里的discontinued,再在右边方框里的itemstyle里将horizontalalign属性设置为center,如图15所示:

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908155486.jpg
图15:将discontinued列里的checkbox居左

  接下来在页面上添加一个validationsummar控件,将其showmessagebox属性设置为true;showsummary属性设置为false. 同时再添加一个button web控件,用来更新用户所做的更该。特别的,添加2个,一个在gridview控件上面,一个在下面,将它们的text属性设置为“update products”.由于我们已经在templatefields模板定义了编辑界面,那么edititemtemplates模板就显得多余了,将其删除.

完成上述修改后,你的页面声明代码看起来应该和下面的差不多:

<p>
 <asp:button id="updateallproducts1" runat="server" text="update products" />
</p>
<p>
 <asp:gridview id="productsgrid" runat="server" autogeneratecolumns="false"
 datakeynames="productid" datasourceid="productsdatasource"
 allowpaging="true" allowsorting="true">
 <columns>
  <asp:templatefield headertext="product" sortexpression="productname">
  <itemtemplate>
   <asp:textbox id="productname" runat="server"
   text='<%# bind("productname") %>'></asp:textbox>
   <asp:requiredfieldvalidator id="requiredfieldvalidator1"
   controltovalidate="productname"
   errormessage="you must provide the product's name."
   runat="server">*</asp:requiredfieldvalidator>
  </itemtemplate>
  </asp:templatefield>
  <asp:templatefield headertext="category"
  sortexpression="categoryname">
  <itemtemplate>
   <asp:dropdownlist id="categories" runat="server"
   appenddatabounditems="true"
   datasourceid="categoriesdatasource"
   datatextfield="categoryname"
   datavaluefield="categoryid"
   selectedvalue='<%# bind("categoryid") %>'>
   <asp:listitem>-- select one --</asp:listitem>
   </asp:dropdownlist>
  </itemtemplate>
  </asp:templatefield>
  <asp:templatefield headertext="price"
  sortexpression="unitprice">
  <itemtemplate>
   $<asp:textbox id="unitprice" runat="server" columns="8"
   text='<%# bind("unitprice", "{0:n}") %>'></asp:textbox>
   <asp:comparevalidator id="comparevalidator1" runat="server"
   controltovalidate="unitprice"
   errormessage="you must enter a valid currency value.
     please omit any currency symbols."
   operator="greaterthanequal" type="currency"
   valuetocompare="0">*</asp:comparevalidator>
  </itemtemplate>
  </asp:templatefield>
  <asp:templatefield headertext="discontinued" sortexpression="discontinued">
  <itemtemplate>
   <asp:checkbox id="discontinued" runat="server"
   checked='<%# bind("discontinued") %>' />
  </itemtemplate>
  <itemstyle horizontalalign="center" />
  </asp:templatefield>
 </columns>
 </asp:gridview>
</p>
<p>
 <asp:button id="updateallproducts2" runat="server" text="update products" />

 <asp:objectdatasource id="productsdatasource" runat="server"
 oldvaluesparameterformatstring="original_{0}"
 selectmethod="getproducts" typename="productsbll">
 </asp:objectdatasource>

 <asp:objectdatasource id="categoriesdatasource" runat="server"
 oldvaluesparameterformatstring="original_{0}"
 selectmethod="getcategories" typename="categoriesbll">
 </asp:objectdatasource>

 <asp:validationsummary id="validationsummary1" runat="server"
 showmessagebox="true" showsummary="false" />
</p>

当添加button web控件并对相关格式进行修改后,页面如下图所示:

http://www.lhsxpumps.com/_images/10qianwan/20171212/b_1_201712121908114397.jpg
图16:页面现在包含了2个“update products”按钮

第五步:更新产品

  当用户登录该页面进行修改时并点击“update products”按钮时,我们需要将用户输入的值保存为一个productsdatatable instance实例;再将该实例传递给一个bll method方法,进而将该实例传递给dal层的updatewithtransaction  method方法。该方法是在前面的文章里创建的,确保对批处理进行原子操作.

在batchupdate.aspx.cs文件里创建一个名为batchupdate的方法,代码如下:

private void batchupdate()
{
 // enumerate the gridview's rows collection and create a productrow
 productsbll productsapi = new productsbll();
 northwind.productsdatatable products = productsapi.getproducts();

 foreach (gridviewrow gvrow in productsgrid.rows)
 {
 // find the productsrow instance in products that maps to gvrow
 int productid = convert.toint32(productsgrid.datakeys[gvrow.rowindex].value);

 northwind.productsrow product = products.findbyproductid(productid);
 if (product != null)
 {
  // programmatically access the form field elements in the
  // current gridviewrow
  textbox productname = (textbox)gvrow.findcontrol("productname");
  dropdownlist categories =
  (dropdownlist)gvrow.findcontrol("categories");
  textbox unitprice = (textbox)gvrow.findcontrol("unitprice");
  checkbox discontinued =
  (checkbox)gvrow.findcontrol("discontinued");

  // assign the user-entered values to the current productrow
  product.productname = productname.text.trim();
  if (categories.selectedindex == 0)
  product.setcategoryidnull();
  else
  product.categoryid = convert.toint32(categories.selectedvalue);
  if (unitprice.text.trim().length == 0)
  product.setunitpricenull();
  else
  product.unitprice = convert.todecimal(unitprice.text);
  product.discontinued = discontinued.checked;
 }
 }

 // now have the bll update the products data using a transaction
 productsapi.updatewithtransaction(products);
}

  该方法调用bll层的getproducts method方法,通过一个productsdatatable来获取所有的产品.然后遍历gridview控件的rows collection集,该rows collection集包含了gridview里每行所对应的gridviewrow instance实例。由于gridview里每页最多显示了10行,所以gridview控件的rows collection集包含的条码最多不超过10条.

  每行记录的productid来源于datakeys collection集,并从productsdatatable里选出对应的productsrow.这4个templatefield input控件的值赋值给productsrow instance实例的属性。当对productsdatatable更新完成后,又转到bll业务逻辑层的updatewithtransaction method方法,就像我们在前面的教程看到的一样,该方法仅仅调用dal数据访问层的updatewithtransaction方法.

  本文使用的批更新策略是:将productsdatatable里对应于gridview里每行记录的所有row进行更新,不管用户有没有改动过产品信息.这种盲目的更改虽然执行起来没什么问题,但将会导致database table里出现多余的记录.在前面的第37章《datalist批量更新》里,我们考察里datalist控件的批更新界面,在那篇文章里我们使用饿代码只更新那些确实被用户改动过的记录.如果愿意的话,你可以使用37章的方法.
注意:当通过gridview的智能标签来绑定数据源时,visual studio会自动的将数据源的主键值指定为gridview的datakeynames属性.如果你没有通过gridview的智能标签来绑定objectdatasource的话,我们需要手工设置gridview控件datakeynames属性为“productid”, 以便通过datakeys collection集来访问productid值.

  batchupdate方法里的代码和bll业务逻辑层里的updateproduct methods方法的代码很相似,主要的区别在于updateproduct methods方法仅仅获取一个单一的productrow instance实例.updateproducts methods方法里对productrow的属性赋值的代码与batchupdate方法里foreach循环里的代码是一模一样的.

  最后,当点击任意一个“update products”按钮时,将调用batchupdate方法,为这2个按钮的click events事件创建事件处理器,在里面添加如下的代码:

batchupdate();

clientscript.registerstartupscript(this.gettype(), "message",
 "alert('the products have been updated.');", true);

  以上代码首先调用batchupdate()方法;再使用clientscript property属性来注入javascript,以显示一个messagebox,提示“the products have been updated.”

  花几分钟测试代码.在浏览器的登录batchupdate.aspx页面,编辑几行记录,点任意一个“update products”按钮。假定输入无误,你会看到一个消息框显示“the products have been updated.”为了测试原子操作,你可以任意添加一个check约束,比如不接受unitprice的值为“1234.56”。然后再登录batchupdate.aspx页面,编辑几行记录,确保设置其中的一条记录的unitprice值为“1234.56”. 当点“update products”按钮时,将会出错。结果是所有的操作回滚,回到原来的值.

另一种可供选择的batchupdate方法

  上面我们探讨的batchupdate方法从bll业务逻辑层的getproducts方法获取所有的产品.
如果gridview没有启用分页的话,一切都很完美.如果启用了分页了呢?比如可能总共有几百、几千、几万条产品记录,而gridview里每页只显示了10条记录。在这种情况下,该方法获取了所有的记录,但只更新其中的10条记录,实在是难称完美.

面对这种情况,可以考虑使用下面的batchupdatealternate代替:

private void batchupdatealternate()
{
 // enumerate the gridview's rows collection and create a productrow
 productsbll productsapi = new productsbll();
 northwind.productsdatatable products = new northwind.productsdatatable();

 foreach (gridviewrow gvrow in productsgrid.rows)
 {
 // create a new productrow instance
 int productid = convert.toint32(productsgrid.datakeys[gvrow.rowindex].value);
 
 northwind.productsdatatable currentproductdatatable =
  productsapi.getproductbyproductid(productid);
 if (currentproductdatatable.rows.count > 0)
 {
  northwind.productsrow product = currentproductdatatable[0];

  // programmatically access the form field elements in the
  // current gridviewrow
  textbox productname = (textbox)gvrow.findcontrol("productname");
  dropdownlist categories =
  (dropdownlist)gvrow.findcontrol("categories");
  textbox unitprice = (textbox)gvrow.findcontrol("unitprice");
  checkbox discontinued =
  (checkbox)gvrow.findcontrol("discontinued");

  // assign the user-entered values to the current productrow
  product.productname = productname.text.trim();
  if (categories.selectedindex == 0)
  product.setcategoryidnull();
  else
  product.categoryid = convert.toint32(categories.selectedvalue);
  if (unitprice.text.trim().length == 0)
  product.setunitpricenull();
  else
  product.unitprice = convert.todecimal(unitprice.text);
  product.discontinued = discontinued.checked;

  // import the productrow into the products datatable
  products.importrow(product);
 }
 }

 // now have the bll update the products data using a transaction
 productsapi.updateproductswithtransaction(products);
}

  该方法首先创建一个名为products的空白的productsdatatable,再通过bll业务逻辑层的getproductbyproductid(productid)方法来获取具体的产品信息.获取的productsrow instance实例更新其属性,就像batchupdate()做的那样。更新完后,通过importrow(datarow)method方法将row导入名为products的productsdatatable.

  foreach循环完成后, products将包含那些对应于gridview里每行记录的productsrowinstance实例,由于这些实例是添加(而不是更新)到products,如果我们盲目的传递给updatewithtransaction method方法的话,productstableadatper会将每条记录插入数据库.在此,我们必须声明只对这些行进行更新(而不是添加).

  为此,我们需要在业务逻辑层里添加一个名为updateproductswithtransaction的方法来达到上述目的。该方法,就像下面代码显示的那样,将productsdatatable里的每一个productsrow instances实例的rowstate设置为modified,然后将该productsdatatable传递给dal数据访问层的updatewithtransaction method方法.

public int updateproductswithtransaction(northwind.productsdatatable products)
{
 // mark each product as modified
 products.acceptchanges();
 foreach (northwind.productsrow product in products)
 product.setmodified();

 // update the data via a transaction
 return updatewithtransaction(products);
}

总结:

  gridview控件内置的编辑功能只能对每行进行编辑,对批编辑无能为力.就像本文探讨的那样,要创建一个批处理界面我们要多做一些工作。为此,我们需要将gridview里的列转换为templatefields,并在itemtemplates模板里定义编辑界面,另外要在页面添加“update all”按钮,该按钮与gridview彼此分开.该按钮的click event事件必须要确保遍历gridview的rows collection集、在一个productsdatatable里存储改动信息,然后再传递给相应的bll业务逻辑层的方法.

  下一篇,我们将考察如何创建一个批删除的界面,具体来说,每个gridview row都会包含一个checkbox。另外, 我们将用一个“delete selected rows”按钮来替换“update all”按钮.

  祝编程快乐!

作者简介

  本系列教程作者 scott mitchell,著有六本asp/asp.net方面的书,是4guysfromrolla.com的创始人,自1998年以来一直应用 微软web技术。大家可以点击查看全部教程《[翻译]scott mitchell 的asp.net 2.0数据教程》,希望对大家的学习asp.net有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网