欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

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

导言:

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

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

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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方法.

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图2:设置objectdatasourc调用productsbll class类

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图3: 使用getproducts方法获取产品信息

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

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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”。

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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那样:

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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”.

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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方法.

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图8:设置该objectdatasource使用categoriesbll类

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图9:从getcategories方法获取数据

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

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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.

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图11:将dropdownlist控件绑定到categoriesdatasource

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

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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模板,以包含其编辑页面。

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图13:每个gridview row都是可编辑的

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

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图14:将text格式指定为number

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

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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控件并对相关格式进行修改后,页面如下图所示:

在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据
图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有所帮助。

上一篇:

下一篇: