本文档是 Visual C# 教程 (切换到 Visual Basic 教程)

了解如何在单个操作中更新多条数据库记录。在用户界面层,我们创建一个 GridView,该 GridView 允许对每行记录进行编辑。在数据访问层,我们使用事务封装多个更新操作,以便确保所有更新都能成功或回滚。

« 前一篇教程  |  下一篇教程 »

简介

在前一篇教程 中 , 我们探讨了如何扩展数据访问层以支持数据库事务。数据库事务确保将一系列数据修改语句作为一个原子操作进行处理,这样这些修改要么全部失败,要么全部成功。但由于底层的 DAL 功能不适用,我们将注意力转向创建批量数据修改界面。

在本教程中,我们将创建一个 GridView ,该 GridView 允许对每行记录进行编辑(参见图 1 )。由于每行记录都呈现在自己的编辑界面中,因此没有必要创建 Edit 、 Update 和 Cancel 按钮列,而是在页面上增加两个 “Update Products” 按钮,单击时,系统枚举所有 GridView 记录并更新数据库。

图1 :GridView 中的每行记录都可以编辑

让我们开始吧 !

注意 : 在执行批量更新 教程中 , 我们使用DataList 控件创建了一个批量编辑界面。本教程与上述教程的区别在于,本教程使用 GridView 且在事务中执行批量更新。完成本教程后,我建议大家返回到上述教程,使用前一篇教程中增加的与数据库事务相关的功能完成更新。

探讨使所有 GridView 记录可编辑的步骤

正如我们在数据插入、更新和删除概述 教程中讨论的那样 ,GridView 提供内置的功能来编辑每行基础数据。在其内部, GridView 通过 EditIndex 属性 来判断可进行编辑的行。 GridView 绑定到数据源后,它进行逐行检查,查看哪行的索引值等于 EditIndex 值。如果找到,该行的字段就呈现在编辑界面中。对于 BoundField ,编辑界面是一个文本框,其 Text 属性为 BoundField 的 DataField 属性指定的数据字段的值。对于 TemplateField ,编辑界面为 EditItemTemplate (而非 ItemTemplate )。

我们知道,当某个用户单击某行的 Edit 按钮时,编辑流程开始。这将导致页面回传,将 GridView 的 EditIndex 属性设置为所单击行的索引,然后重新绑定数据。当单击某行的 Cancel 按钮后产生页面回传时,在重新绑定数据之前, EditIndex 被设置为 -1 。因为 GridView 的行索引从 0 开始,将 EditIndex 设置为 -1 就可达到以只读模式显示 GridView 的效果。

EditIndex 属性可以对每行进行编辑,但不支持批量编辑。要对整个 GridView 进行编辑,我们必须让每一行都使用编辑界面呈现。为此,最简单的方法是将要编辑的字段创建为 TemplateField ,并在 ItemTemplate 中定义编辑界面。

在接下来的几步中,我们将创建一个可完全编辑的 GridView 。在步骤 1 中,我们将创建 GridView 及其 ObjectDataSource ,并将 BoundField 和 CheckBoxField 转换为 TemplateField 。在步骤 2 和 3 中,我们将编辑界面从 TemplateField 的 EditItemTemplate 转移到 ItemTemplate 。

步骤1 : 显示产品信息

在开始创建可编辑行记录的 GridView 之前,我们先简单地显示产品信息。打开 BatchData 文件夹中的 BatchUpdate.aspx 页面,从工具箱中将一个 GridView 拖放到设计器。将 GridView 的 ID 设置为 ProductsGrid ,并从智能标记中选择将它绑定到一个名为 ProductsDataSource 的新 ObjectDataSource 上。配置 ObjectDataSource 使用ProductsBLL 类的 GetProducts 方法获取数据。

图2 : 配置 ObjectDataSource 使用 ProductsBLL 类

图3 : 使用 GetProducts 方法获取产品数据

与GridView 一样 ,ObjectDataSource 的修改功能对行记录起作用。要更新记录集,我们需要在 ASP.NET 页面的代码文件类中编写一些代码,对数据进行批处理并传递给 BLL 。因此,将 ObjectDataSource 的 UPDATE 、 INSERT 和 DELETE 选项卡中的下拉列表设置为 “(None)” 。单击Finish 完成向导。

图4 : 将 UPDATE 、INSERT 和 DELETE 选项卡中的下拉列表设置为 “(None)”

完成 Configure Data Source 向导后 ,ObjectDataSource 的声明式标记应如下所示 :

<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>

完成Configure Data Source 向导后 ,Visual Studio 会为 GridView 中的产品数据字段创建 BoundField 和 CheckBoxField 。在本教程中,我们将只允许用户查看和编辑产品的名称、类别、价格和断货状态。删除 ProductName 、 CategoryName 、 UnitPrice 和 Discontinued 字段外的其它字段,将前三个字段的 HeaderText 属性分别重命名为 “Product” 、 “Category” 和 “Price” 。最后,在 GridView 的智能标记中,选中 “Enable Paging” 和 “Enable Sorting” 复选框。

此时, GridView 有三个 BoundField ( ProductName 、 CategoryName 和 UnitPrice )以及一个 CheckBoxField (Discontinued) 。我们需要将这四个字段转换为 TemplateField ,并将编辑界面从 TemplateField 的 EditItemTemplate 转移到 ItemTemplate 。

注意 : 我们在自定义数据修改界面 教程中探讨了如何创建和自定义 TemplateField 。我们将 BoundField 和 CheckBoxField 转换成 TemplateField ,然后再在 ItemTemplate 中定义编辑界面。如有疑问或想复习相关内容,请参考该教程。

在GridView 的智能标记中 , 单击 “Edit Columns” 链接 , 打开 Fields 对话框。然后选中每个字段 , 并单击 “Convert this field into a TemplateField” 链接。

图5 : 将现有的 BoundField 和 CheckBoxField 转换成 TemplateFields

由于每个字段都是一个 TemplateField , 我们可以将编辑界面从 EditItemTemplate 转移到 ItemTemplate 了。

步骤2 : 创建ProductName 、UnitPrice 和Discontinued 的编辑界面

创建ProductName 、UnitPrice 和Discontinued 的编辑界面是本步骤的主题 , 并且非常简单 , 因为每个界面都已在 TemplateField 的 EditItemTemplate 中定义。而创建 CategoryName 编辑界面比较麻烦,因为我们需要创建一个可用类别的 DropDownList 。我们将在步骤 3 中实现 CategoryName 编辑界面。

我们首先创建 ProductName TemplateField 。在 GridView 的智能标记中,单击 “Edit Templates” 链接,进入 ProductName TemplateField 的 EditItemTemplate 。选中 TextBox ,将其复制到剪贴板,然后粘贴到 ProductName TemplateField 的 ItemTemplate 。将该 TextBox 的 ID 属性更改为 ProductName 。

然后 , 在ItemTemplate 中增加一个 RequiredFieldValidator , 以确保用户输入的产品名称不为空。将 ControlToValidate 属性设置为 “ProductName” , ErrorMessage 属性设置为 “You must provide the product's name.” ,而 Text 属性为 “*” 。向 ItemTemplate 添加完成后,屏幕看起来应如图 6 所示。

图6 :ProductName TemplateField 现在包含一个 TextBox 和一个 RequiredFieldValidator

对于UnitPrice 编辑界面 , 先将 TextBox 从 EditItemTemplate 复制到ItemTemplate 。然后 , 在 TextBox 的前面放置一个 “$” , 将其 ID 属性设置为 “UnitPrice” ,Columns 属性设置为 “8” 。

同样 , 为UnitPrice 的 ItemTemplate 添加一个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” 。

图7 : 添加一个 CompareValidator 以确保输入的价格是非负的货币值

对于Discontinued TemplateField , 我们可使用已在 ItemTemplate 中定义好的CheckBox 。只需要将其 ID 设置为 “Discontinued” ,Enabled 属性设置为 True 。

步骤3 : 创建CategoryName 的编辑界面

CategoryName TemplateField 的 EditItemTemplate 中的编辑界面包含一个 TextBox , 用于显示 CategoryName 数据字段的值。我们需要将它替换为一个 DropDownList ,以列出可能的类别。

注意 : 在自定义数据修改界面 教程中 , 我们详细探讨了如何在自定义的模板中包含DropDownList ( 而非 TextBox ) 。在此,我们将过程一略而过。有关创建和配置类别 DropDownList 的细节,请参考 自定义数据修改界面 教程。

从工具箱中将一个 DropDownList 拖放到 CategoryName TemplateField 的 ItemTemplate , 将其 ID 设置为 Categories 。通常情况下,我们在智能标记中定义 DropDownList 的数据源,从而创建一个新的 ObjectDataSource 。然而,这将在 ItemTemplate 中增加 ObjectDataSource ,这会为 GridView 的每一行都创建一个 ObjectDataSource 实例。因此,我们在 GridView 的 TemplateField 外部创建 ObjectDataSource 。结束模板编辑,从工具箱中将一个 ObjectDataSource 拖放到设计器中,放在 ProductsDataSource ObjectDataSource 的下面。将新的 ObjectDataSource 命名为 CategoriesDataSource ,配置它使用 CategoriesBLL 类的 GetCategories 方法。

图8 : 将 ObjectDataSource 配置为使用 CategoriesBLL 类

图9 : 通过 GetCategories 方法获取类别数据

因为该ObjectDataSource 仅用于提取数据 , 所以我们将 UPDATE 和 DELETE 选项卡中的下拉列表设置为 “(None)” 。单击 Finish 完成向导。

图10 : 将 UPDATE 和 DELETE 选项卡中的下拉列表设置为“(None)”

完成此向导后 ,CategoriesDataSource 的声明式标记应如下所示 :

<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

创建并配置了CategoriesDataSource 之后 , 返回 CategoryName TemplateField 的 ItemTemplate , 从 DropDownList 的智能标记中单击 “Choose Data Source” 链接。在 Data Source Configuration 向导中,从第一个下拉列表中选择 CategoriesDataSource ,然后选择在下拉列表中显示 CategoryName 字段,并使用 CategoryID 字段作为下拉列表的值。

图11 : 将 DropDownList 绑定到 CategoriesDataSource

此时 ,Categories DropDownList 列出所有的类别 , 但还没有为绑定到 GridView 行的产品自动选择对应的类别。为此,我们需要将 Categories DropDownList 的 SelectedValue 设置为产品的 CategoryID 值。在 DropDownList 的智能标记中,单击 “Edit DataBindings” 链接,将 SelectedValue 属性与 CategoryID 数据字段关联起来,如图 12 所示。

图12 : 将产品的 CategoryID 值绑定到 DropDownList 的 SelectedValue 属性

现在还有最后一个问题 : 如果产品的 CategoryID 值没有指定 , 那么对 SelectedValue 进行数据绑定会出现异常。这是因为 DropDownList 只列出指定了 CategoryID 值的产品,不会列出那些 CategoryID 值为 NULL 的产品。为避免此问题,我们将 DropDownList 的 AppendDataBoundItems 属性设置为 True ,并为 DropDownList 增加一个新项目,从声明性语法中删除 Value 属性。也就是说 , 确保 Categories DropDownList 的声明性语法如下面的代码所示 :

<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 属性明确地设置为一个空字符串。为什么需要增加一个 DropDownList 项目来处理 NULL 值得情况?为什么要将 Value 属性设置为一个空字符串?有关这些疑问的详细讨论,请参考前面的教程 自定义数据修改界面 。

注意 : 这里我们要提一下一个关乎性能和可扩展性的潜在问题。由于每行记录都有一个 DropDownList (其数据源为 CategoriesDataSource ), CategoriesBLL 类的 GetCategories 方法在访问每个页面时都要被调用 n 次,其中 n 是 GridView 中的记录数。对 GetCategories 的 n 次调用导致对数据库的 n 次查询。我们可以对返回的类别进行缓存来减轻对数据库的影响,可以使用每次请求缓存,也可在缓存层中使用 SQL 缓存依赖或基于短时间的缓存周期。有关每次请求缓存的更多信息,请参考HttpContext.Items – a Per-Request Cache Store

步骤4 : 完善编辑界面

我们已对 GridView 的模板进行了大量更改,但还没有停下来查看我们的进度。现在,让我们花些时间在浏览器中查看一下进度。如图 13 所示,每行都使用包含该单元编辑界面的 ItemTemplate 呈现。

图13 :GridView 的每行都是可编辑的

此时 , 还有几个需要注意的格式方面的小问题。首先,我们注意到 UnitPrice 的值包含四位小数。为解决此问题,返回 UnitPrice TemplateField 的 ItemTemplate ,在 TextBox 的智能标记中,单击 “Edit DataBindings” 链接。然后,将 Text 属性的格式指定为数字。

图14 : 将 Text 属性的格式指定为数字

然后 , 将Discontinued 列的复选框居中 ( 而不是左对齐 ) 。在GridView 的智能标记中单击 “Edit Columns” , 从左下角的字段列表中选择 Discontinued TemplateField 。深入到 ItemStyle 中,将 HorizontalAlign 属性设置为 Center ,如图 15 所示。

图15 : 将 Discontinued 复选框居中

接下来 , 在页面上增加一个 ValidationSummary 控件 , 将其 ShowMessageBox 属性设置为True ,ShowSummary 属性设置为 False 。同时增加一些 Web 按钮控件 , 单击时 , 将更新用户的更改。特别要增加两个 Web 按钮控件,一个在 GridView 的上方,一个在下方,将这两个控件的 Text 属性都设置为 “Update Products” 。

由于GridView 的编辑界面是在其 TemplateField 的 ItemTemplate 中定义的 , 因此 EditItemTemplate 是多余的 , 可以删除。

完成上述格式方面的更改、增加按钮控件并删除不必要的EditItemTemplate 后 , 页面的声明性语法应如下所示 :

<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>

增加了Web 按钮控件并进行了相关格式修改后,从浏览器查看时,页面如图 16 所示。

图16 : 页面现在包含两个 “Update Products” 按钮

步骤5 : 更新产品

用户访问该页面时 , 他们可能进行修改 , 然后单击两个“Update Products” 按钮中的任意一个。这时,我们需要将用户给每行输入的值保存在一个 ProductsDataTable 实例中,再将该实例传给一个 BLL 方法,该方法然后将 ProductsDataTable 实例传递给 DAL 的 UpdateWithTransaction 方法。我们在前一篇教程 中创建的 UpdateWithTransaction 方法可确保批量更改将作为原子操作执行。

在 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 方法 , 将所有的产品数据放在 ProductsDataTable 中。然后枚举 ProductGrid GridView 的 Rows 集 。该 Rows 集包含 GridView 中显示的每行记录的 GridViewRow 实例 。由于每页最多显示 10 行, GridView 的 Rows 集包含的条目不超过 10 条。

每行记录的ProductID 来源于 DataKeys 集 , 并从ProductsDataTable 选择对应的 ProductsRow 。通过编码方式引入这四个 TemplateField 输入控件 , 并将它们的值指定给 ProductsRow 实例的属性。 GridView 的每行记录的值都更新到 ProductsDataTable 后, ProductsDataTable 被传递给 BLL 的 UpdateWithTransaction 方法,就像我们在前一篇教程中看到的那样,该方法仅调用 DAL 的 UpdateWithTransaction 方法。

本教程使用的批量更新算法是 : 将与GridView 中记录对应的 ProductsDataTable 中的每行记录进行更新 , 而不管产品的信息是否变动。这种盲目更新虽然不会造成性能问题,但用户核实数据库表的更改时会发现多余的记录。在前面的 执行批量更新 教程中,我们探讨了使用 DataList 的批量更新界面,并增加了一些代码,从而只更新那些确实被用户更改过的记录。如果需要,你可以随意使用 执行批量更新 教程中的方法来更新本教程中的代码。

注意 : 通过 GridView 的智能标记将数据源绑定到 GridView 时 ,Visual Studio 会自动地将数据源的主键值分配给 GridView 的 DataKeyNames 属性。如果没有按照步骤 1 中的说明通过 GridView 的智能标记来将 ObjectDataSource 绑定到 GridView ,则需要手动将 GridView 的 DataKeyNames 属性设置为 “ProductID” ,以便通过 DataKeys 集访问每行记录的 ProductID 值。

BatchUpdate 中使用的代码类似于 BLL 的 UpdateProduct 方法中使用的代码 , 主要的区别在于 UpdateProduct 方法只从架构中获取一个ProductRow 实例。 UpdateProducts 方法中对 ProductRow 属性赋值的代码和 BatchUpdate 中 For Each 循环内的代码是相同的,这是普遍方式。

最后 , 在单击任意一个 “Update Products” 按钮时 , 我们需要调用 BatchUpdate 方法。为这两个按钮控件的 Click 事件创建 Event Hanlder , 并在Event Hanlder 中增加下面的代码 :

BatchUpdate();
ClientScript.RegisterStartupScript(this.GetType(), "message", 
    "alert('The products have been updated.');", true);

首先调用BatchUpdate 。然后使用 ClientScript 属性 来插入 JavaScript , 以显示一个消息框 , 提示“The products have been updated” 。

花几分钟测试代码。在浏览器中访问 BatchUpdate.aspx , 编辑几行记录 , 然后单击一个 “Update Products” 按钮。假定输入无误,我们应该会看到一个消息框,显示 “The products have been updated” 。为验证该更新操作的原子性,我们可任意增加一个 CHECK 约束,比如不接受值为 “1234.56” 的 UnitPrice 。然后在 BatchUpdate.aspx 中,编辑几行记录,确保其中一个产品的 UnitPrice 值为禁用值 (“1234.56”) 。单击 “Update Products” 时,将会出错,批量操作中进行的其它更改会回滚为原来的值。

可选择的BatchUpdate 方法

我们刚刚探讨的 BatchUpdate 方法从 BLL 的 GetProducts 方法获取所有 产品数据 , 然后仅更新出现在 GridView 中的记录。如果 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);
}

BatchMethodAlternate 首先创建一个名为 products 的空白的 ProductsDataTable 。再通过BLL 的 GetProductByProductID(productID) 方法来从 GridView 的 Rows 集中为每行记录获取特定的产品信息。获取的 ProductsRow 实例更新其属性,更新方法与 BatchUpdate 相同。记录更新后,该方法通过 DataTable 的 ImportRow(DataRow) 方法 将记录导入名为 products 的 ProductsDataTable 。

For Each 循环完成后 ,products 将包含对应 GridView 中每行记录的ProductsRow 实例。由于这些 ProductsRow 实例已添加到products ( 不是更新 ), 如果我们盲目地将实例传递给 UpdateWithTransaction 方法 ,ProductsTableAdatper 会尝试将每条记录插入数据库。因此,我们需要指明这些记录进行了修改操作(而非添加操作)。

为此 , 我们需要在 BLL 中增加一个名为 UpdateProductsWithTransaction 的新方法。UpdateProductsWithTransaction , 如下面所示 , 将ProductsDataTable 中每个ProductsRow 实例的 RowState 设置为 Modified , 然后将ProductsDataTable 传递给DAL 的UpdateWithTransaction 方法。

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 ,我们需要将 GridView 的字段转换成 TemplateField ,并在 ItemTemplates 内定义编辑界面。此外,必须向页面添加 “Update All” 类型的 Web 按钮控件,与 GridView 分开。这些按钮的 Click 事件的 Event Handler 必须要确保枚举 GridView 的 Rows 集,在一个 ProductsDataTable 中存储更改信息,然后将更改信息传递给相应的 BLL 方法。

在下一篇教程中,我们将探讨如何创建一个进行批量删除的界面。具体来说, GridView 的每一行都会包含一个复选框。我们将使用 “Delete Selected Rows” 按钮代替 “Update All” 类型的按钮。

快乐编程!

下一篇教程