批量更新 (C#)

作者 :Scott Mitchell

下载 PDF

了解如何在单个操作中更新多个数据库记录。 在用户界面层中,我们生成一个 GridView,其中每一行都是可编辑的。 在数据访问层中,我们会在事务中包装多个更新操作,以确保所有更新成功或回滚所有更新。

简介

前面的教程中 ,我们了解了如何扩展数据访问层以添加对数据库事务的支持。 数据库事务保证一系列数据修改语句被视为一个原子操作,这可确保所有修改都将失败或全部成功。 随着这种低级别 DAL 功能的推出,我们准备将注意力转向创建批处理数据修改接口。

在本教程中,我们将生成一个 GridView,其中每一行都是可编辑的 (见图 1) 。 由于每一行都呈现在其编辑界面中,因此不需要“编辑”、“更新”和“取消”按钮列。 相反,页面上有两个“更新产品”按钮,单击这些按钮时,会枚举 GridView 行并更新数据库。

GridView 中的每一行都是可编辑的

图 1:GridView 中的每一行都是可编辑 (单击以查看全尺寸图像)

让我们开始吧!

注意

“执行批处理汇报教程中,我们使用 DataList 控件创建了批处理编辑界面。 本教程与上一教程的不同之处在于,它使用 GridView,批处理更新是在事务范围内执行的。 完成本教程后,建议返回到前面的教程,并将其更新为使用上一教程中添加的数据库事务相关功能。

检查使所有 GridView 行可编辑的步骤

插入、更新和删除数据概述 教程中所述,GridView 为按行编辑其基础数据提供内置支持。 在内部,GridView 通过其 EditIndex 属性记录可编辑的行。 当 GridView 绑定到其数据源时,它会检查每一行以查看行的索引是否等于 的值 EditIndex。 如果是这样,则使用其编辑接口呈现该行的字段。 对于 BoundFields,编辑接口是一个 TextBox,其 Text 属性被分配为 BoundField 属性 DataField 指定的数据字段的值。 对于 TemplateFields, EditItemTemplate 使用 代替 ItemTemplate

回想一下,编辑工作流在用户单击行“编辑”按钮时启动。 这会导致回发,将 GridView 的 EditIndex 属性设置为单击的行索引,并将数据重新绑定到网格。 单击行“取消”按钮时,在回发时, EditIndex 将 设置为 值 -1 ,然后再将数据重新绑定到网格。 由于 GridView 的行从零开始编制索引,将 设置为 EditIndex-1 将具有在只读模式下显示 GridView 的效果。

属性 EditIndex 适用于每行编辑,但不适用于批量编辑。 若要使整个 GridView 可编辑,我们需要使用其编辑界面呈现每一行。 实现此目的的最简单方法是创建每个可编辑字段实现为 TemplateField 的位置,并在 中 ItemTemplate定义其编辑接口。

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

步骤 1:显示产品信息

在担心创建行可编辑的 GridView 之前,让我们先显示产品信息。 BatchUpdate.aspx打开 文件夹中的页面BatchData,并将 GridView 从“工具箱”拖到Designer。 将 GridView ID 设置为 ProductsGrid ,并从其智能标记中选择将其绑定到名为 ProductsDataSource的新 ObjectDataSource。 将 ObjectDataSource 配置为从 ProductsBLL 类方法 GetProducts 检索其数据。

将 ObjectDataSource 配置为使用 ProductsBLL 类

图 2:将 ObjectDataSource 配置为使用 ProductsBLL 类 (单击以查看全尺寸图像)

使用 GetProducts 方法检索产品数据

图 3:使用 GetProducts 方法检索产品数据 (单击以查看全尺寸图像)

与 GridView 一样,ObjectDataSource 的修改功能设计为按行工作。 为了更新一组记录,我们需要在 ASP.NET 页代码隐藏类中编写一些代码,以便对数据进行批处理并将其传递给 BLL。 因此,请将 ObjectDataSource 的“更新”、“插入”和“删除”选项卡中的下拉列表设置为 (None) 。 单击“完成”以完成向导。

将“更新”、“插入”和“删除”选项卡中的 Drop-Down Lists 设置为“ (None”)

图 4:将“更新”、“插入”和“删除”选项卡中的 Drop-Down Lists 设置为“无 () (单击以查看全尺寸图像)

完成“配置数据源”向导后,ObjectDataSource 声明性标记应如下所示:

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

完成“配置数据源”向导还会导致 Visual Studio 为 GridView 中的产品数据字段创建 BoundFields 和 CheckBoxField。 在本教程中,我们仅允许用户查看和编辑产品名称、类别、价格和停产状态。 删除除 、、 和 字段之外ProductName的所有字段,并将前三个字段的属性分别重命名HeaderText为 Product、Category 和 Price。DiscontinuedUnitPriceCategoryName 最后,检查 GridView 智能标记中的“启用分页”和“启用排序”复选框。

此时,GridView 具有三个 BoundField (ProductNameCategoryNameUnitPrice) ,以及一个 CheckBoxField (Discontinued) 。 我们需要将这四个字段转换为 TemplateFields,然后将编辑界面从 TemplateField 移动到 EditItemTemplateItemTemplate

注意

我们在自定义 数据修改接口 教程中介绍了如何创建和自定义 TemplateFields。 我们将逐步完成将 BoundFields 和 CheckBoxField 转换为 TemplateFields 并在其 ItemTemplate 中定义其编辑接口的步骤,但如果遇到困难或需要复习,请随时重新参阅本前面的教程。

在 GridView 智能标记中,单击“编辑列”链接以打开“字段”对话框。 接下来,选择每个字段,然后单击“将此字段转换为 TemplateField”链接。

将现有边界字段和 CheckBoxField 转换为 TemplateField

图 5:将现有边界字段和 CheckBoxField 转换为 TemplateField

现在,每个字段都是一个 TemplateField,我们准备将编辑界面从 EditItemTemplateItemTemplate 移动到 s。

步骤 2:创建ProductNameUnitPrice编辑接口和Discontinued编辑接口

ProductName创建 、 UnitPriceDiscontinued 编辑接口是此步骤的主题,并且非常简单,因为每个接口已在 TemplateField 中EditItemTemplate定义。 CategoryName创建编辑界面有点复杂,因为我们需要创建适用类别的 DropDownList。 此 CategoryName 编辑界面在步骤 3 中处理。

让我们从 TemplateField 开始 ProductName 。 单击 GridView 智能标记中的“编辑模板”链接,向下钻取到 ProductName TemplateField 。EditItemTemplate 选择 TextBox,将其复制到剪贴板,然后将其粘贴到 ProductName TemplateField 中 ItemTemplate。 将 TextBox 属性 ID 更改为 ProductName

接下来,将 RequiredFieldValidator 添加到 , ItemTemplate 以确保用户为每个产品名称提供值。 将 ControlToValidate 属性设置为 ProductName,将 ErrorMessage 属性设置为 必须提供产品名称。 Text将 属性设置为 *。 对 进行这些添加 ItemTemplate后,屏幕应类似于图 6。

ProductName TemplateField 现在包括一个 TextBox 和一个 RequiredFieldValidator

图 6:TemplateField ProductName 现在包括一个 TextBox 和一个 RequiredFieldValidator (单击以查看全尺寸图像)

UnitPrice对于编辑界面,首先将 TextBox 从 EditItemTemplateItemTemplate复制到 。 接下来,在 TextBox 前面放置 $,并将其 ID 属性设置为 UnitPrice,将其 Columns 属性设置为 8。

此外,将 CompareValidator 添加到 UnitPriceItemTemplate 以确保用户输入的值是大于或等于 $0.00 的有效货币值。 将验证程序属性 ControlToValidate 设置为 UnitPrice,将其 ErrorMessage 属性设置为必须输入有效的货币值。 请省略任何货币符号。其 Text 属性为 *,其 Type 属性为 Currency,其 Operator 属性为 GreaterThanEqual,其 ValueToCompare 属性为 0 。

添加 CompareValidator 以确保输入的价格为非负货币值

图 7:添加 CompareValidator 以确保输入的价格为非负货币值 (单击以查看全尺寸图像)

Discontinued对于 TemplateField,可以使用 已在 中ItemTemplate定义的 CheckBox。 只需将其 ID 设置为 Discontinued,将其 Enabled 属性设置为 true

步骤 3:创建CategoryName编辑界面

TemplateField 中的CategoryNameEditItemTemplate编辑界面包含显示数据字段值的 CategoryName TextBox。 我们需要将其替换为列出可能类别的 DropDownList。

注意

自定义数据修改接口教程包含有关自定义模板以包含 DropDownList 而不是 TextBox 的更全面和完整的讨论。 虽然此处的步骤已完成,但它们会简洁地呈现。 若要更深入地了解如何创建和配置类别 DropDownList,请参阅 自定义数据修改接口 教程。

将 DropDownList 从工具箱拖到 CategoryName TemplateField 上 ItemTemplate,将其 ID 设置为 Categories。 此时,我们通常会通过其智能标记定义 DropDownLists 的数据源,从而创建新的 ObjectDataSource。 但是,这会在 中添加 ItemTemplateObjectDataSource,这将导致为每个 GridView 行创建 ObjectDataSource 实例。 相反,让我们在 GridView 的 TemplateFields 外部创建 ObjectDataSource。 结束模板编辑,并将 ObjectDataSource 从工具箱拖到 ObjectDataSource 下的ProductsDataSourceDesigner上。 将新的 ObjectDataSource CategoriesDataSource 命名为 ,并将其配置为使用 CategoriesBLL 类方法 GetCategories

将 ObjectDataSource 配置为使用 CategoriesBLL 类

图 8:将 ObjectDataSource 配置为使用 CategoriesBLL 类 (单击以查看全尺寸图像)

使用 GetCategories 方法检索类别数据

图 9:使用 GetCategories 方法检索类别数据 (单击以查看全尺寸图像)

由于此 ObjectDataSource 仅用于检索数据,因此请将“更新”和“删除”选项卡中的下拉列表设置为 (“无”) 。 单击“完成”以完成向导。

将“更新”和“删除”选项卡中的 Drop-Down Lists 设置为“ (None”)

图 10:将“更新”和“删除”选项卡中的 Drop-Down Lists 设置为“ (无”) (单击查看全尺寸图像)

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

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

CategoriesDataSource创建并配置 后,返回到 CategoryName TemplateField,ItemTemplate并从 DropDownList 的智能标记中单击“选择数据源”链接。 在“数据源配置”向导中,从第一个下拉列表中选择 CategoriesDataSource 选项,并选择 CategoryName 已用于显示 和 CategoryID 作为值。

将 DropDownList 绑定到 CategoriesDataSource

图 11:将 DropDownList CategoriesDataSource 绑定到 (单击以查看全尺寸图像)

此时, Categories DropDownList 会列出所有类别,但它尚未自动为绑定到 GridView 行的产品选择适当的类别。 为此,我们需要将 Categories DropDownList 设置为 SelectedValue product 值 CategoryID 。 单击 DropDownList 智能标记中的“编辑数据绑定”链接,并将 SelectedValue 属性与 CategoryID 数据字段相关联,如图 12 所示。

将 Product s CategoryID 值绑定到 DropDownList s SelectedValue 属性

图 12:将 Product s CategoryID 值绑定到 DropDownList s SelectedValue 属性

最后一个问题仍然存在:如果产品未 CategoryID 指定值,则上的 SelectedValue databinding 语句将导致异常。 这是因为 DropDownList 仅包含类别的项,并且不为数据库 NULL 值为 CategoryID的产品提供选项。 若要解决此问题,请将 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>

请注意 -- Select One -- 将其Value属性显式设置为空字符串的方式<asp:ListItem Value="">。 请参阅 自定义数据修改接口 教程,更全面地讨论为什么需要此附加的 DropDownList 项来处理 NULL 这种情况,以及为什么将 属性分配给 Value 空字符串是必需的。

注意

此处存在一个潜在的性能和可伸缩性问题,值得一提。 由于每一行都有一个使用 CategoriesDataSource 作为其数据源的 DropDownList,CategoriesBLL因此每次页面访问都会调用 n 次类方法GetCategories,其中 n 是 GridView 中的行数。 这些 nGetCategories 调用会导致对数据库的 n 个查询。 通过在每请求缓存中缓存返回的类别,或者通过使用 SQL 缓存依赖项或通过缓存层缓存返回的类别或基于非常短时间的过期时间,可以减轻对数据库的这种影响。

步骤 4:完成编辑界面

我们已对 GridView 模板进行了许多更改,但没有暂停查看进度。 花点时间通过浏览器查看进度。 如图 13 所示,每一行都使用包含 ItemTemplate单元格编辑界面的 呈现。

每个 GridView 行都是可编辑的

图 13:每个 GridView 行都是可编辑的 (单击以查看全尺寸图像)

此时,我们应处理一些次要的格式设置问题。 首先,请注意该值 UnitPrice 包含四个小数点。 若要解决此问题,请返回到 UnitPrice TemplateField, ItemTemplate 并从 TextBox 智能标记中单击“编辑数据绑定”链接。 接下来,指定 Text 属性应设置为数字格式。

将 Text 属性的格式设置为数字

图 14:将 Text 属性的格式设置为数字

其次,让我们在 Discontinued (列中将复选框居中,而不是使其左对齐) 。 单击 GridView 智能标记中的“编辑列” Discontinued ,然后从左下角的字段列表中选择“TemplateField”。 向下 ItemStyle 钻取属性并将其 HorizontalAlign 设置为 Center,如图 15 所示。

停止使用的复选框居中

图 15:将复选框居中Discontinued

接下来,将 ValidationSummary 控件添加到页面,并将其 ShowMessageBox 属性设置为 true ,将其 ShowSummary 属性设置为 false。 此外,添加按钮 Web 控件,单击该控件将更新用户的更改。 具体而言,添加两个按钮 Web 控件,一个位于 GridView 上方,一个位于其下方,将这两个控件 Text 属性设置为“更新产品”。

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

在进行上述格式更改、添加 Button 控件并删除不必要的 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>

图 16 显示了在添加按钮 Web 控件并更改格式设置后通过浏览器查看的此页面。

页面现在包含两个“更新产品”按钮

图 16:页面现在包含两个更新产品按钮 (单击以查看全尺寸图像)

步骤 5:更新产品

当用户访问此页面时,他们将进行修改,然后单击两个“更新产品”按钮之一。 此时,我们需要以某种方式将每一行的用户输入的值保存到实例 ProductsDataTable 中,然后将其传递给 BLL 方法,该方法随后会将该 ProductsDataTable 实例传递给 DAL s 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包含 GridViewRow GridView 中显示的每一行的 实例。 由于每页最多显示 10 行,因此 GridView 集合 Rows 的项不会超过 10 个。

对于每一行, ProductID 从集合中DataKeys抓取,并从 ProductsDataTable中选择适当的 ProductsRow 。 以编程方式引用四个 TemplateField 输入控件,并将其值分配给 ProductsRow 实例的 属性。 使用每个 GridView 行的值更新 ProductsDataTable后,它将传递给 BLL 方法 UpdateWithTransaction ,正如我们在前面的教程中看到的那样,该方法只需向下调用 DAL s UpdateWithTransaction 方法。

本教程使用的批处理更新算法会更新 中 ProductsDataTable 对应于 GridView 中某一行的每一行,而不管产品信息是否已更改。 虽然此类盲目更新通常不是性能问题,但如果重新审核对数据库表的更改,它们可能会导致多余记录。 回到执行批处理汇报教程中,我们探索了使用 DataList 的批量更新界面,并添加了仅更新用户实际修改的记录的代码。 如果需要,可以随意使用执行批处理汇报中的技术更新本教程中的代码。

注意

通过智能标记将数据源绑定到 GridView 时,Visual Studio 会自动将数据源的主键值 () 分配给 GridView 属性 DataKeyNames 。 如果未按照步骤 1 中所述通过 GridView 智能标记将 ObjectDataSource 绑定到 GridView,则需要手动将 GridView 属性 DataKeyNames 设置为 ProductID,以便访问 ProductID 集合中 DataKeys 每一行的值。

中使用的BatchUpdate代码与 BLL 方法UpdateProduct中使用的代码类似,main不同之处在于,UpdateProduct在 方法中,仅从体系结构中检索单个ProductRow实例。 分配 属性的代码ProductRow在 方法与 中的 BatchUpdate循环中的foreach代码之间UpdateProducts相同,与整体模式相同。

若要完成本教程,我们需要在 BatchUpdate 单击“更新产品”按钮之一时调用 方法。 为 Click 这两个 Button 控件的事件创建事件处理程序,并在事件处理程序中添加以下代码:

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

首先调用 BatchUpdate。 接下来, ClientScript property 用于注入 JavaScript,以显示一个显示“产品已更新”的消息框。

花一点时间测试此代码。 通过浏览器访问 BatchUpdate.aspx ,编辑多个行,然后单击其中一个“更新产品”按钮。 假设没有输入验证错误,应会看到一个显示“产品已更新”的消息框。 若要验证更新的原子性,请考虑添加一个随机 CHECK 约束,例如不允许 UnitPrice 值 1234.56 的约束。 然后,在 BatchUpdate.aspx中编辑大量记录,确保将产品 UnitPrice 值之一设置为禁止值 ( 1234.56 ) 。 单击“更新产品”时,该批处理操作期间的其他更改回滚到其原始值时,这会导致错误。

替代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 。 然后,它逐步执行 GridView 集合 Rows ,并使用 BLL 方法 GetProductByProductID(productID) 获取每一行的特定产品信息。 检索到的 ProductsRow 实例的属性更新方式BatchUpdate与 相同,但在更新行后,将通过 DataTable 方法ImportRow(DataRow)将其导入到 products``ProductsDataTable 中。

foreach循环完成后, products 为 GridView 中的每一行包含一个ProductsRow实例。 由于每个 ProductsRow 实例都已添加到 products (而不是更新) ,因此如果我们盲目地将其传递给 UpdateWithTransaction 方法, ProductsTableAdapter 将尝试将每条记录插入数据库中。 相反,我们需要指定其中的每一行都已修改 (未添加) 。

这可以通过向名为 UpdateProductsWithTransaction的 BLL 添加新方法来实现。 UpdateProductsWithTransaction,如下所示,将 中ModifiedProductsDataTable每个实例的 ProductsRow 设置为 RowState ,然后将 传递给 ProductsDataTable DAL s 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 的字段转换为 TemplateFields,并在 中 ItemTemplate 定义编辑界面。 此外,必须将“全部更新”类型的按钮 Web 控件添加到页面,与 GridView 分开。 这些 Buttons Click 事件处理程序需要枚举 GridView 集合 Rows ,将更改存储在 中 ProductsDataTable,并将更新后的信息传递到相应的 BLL 方法中。

在下一教程中,我们将了解如何创建用于批量删除的接口。 具体而言,每个 GridView 行将包含一个复选框,我们将具有“删除所选行”按钮,而不是“全部更新”按钮。

编程愉快!

关于作者

Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。

特别感谢

本教程系列由许多有用的审阅者查看。 本教程的主要审阅者是 Teresa Murphy 和 David Suru。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com。