执行批量更新 (C#)

作者 :斯科特·米切尔

下载 PDF

了解如何创建完全可编辑的 DataList,其中所有项都处于编辑模式,并且可以通过单击页面上的“全部更新”按钮保存其值。

介绍

前面的教程中,我们讲解了如何创建项目级 DataList。 与标准可编辑 GridView 一样,DataList 中的每个项都包含一个“编辑”按钮,单击该按钮会使项目可编辑。 虽然此项级编辑适用于仅偶尔更新的数据,但某些用例方案要求用户编辑许多记录。 如果用户需要编辑数十条记录,并被迫单击“编辑”,进行更改,然后单击每个记录的“更新”,则单击次数可能会阻碍她的工作效率。 在这种情况下,更好的选择是提供完全可编辑的 DataList,其中 所有 项都处于编辑模式,并且可以通过单击页面上的“全部更新”按钮来编辑其值(请参阅图 1)。

完全可编辑的 DataList 中的每个项目可以修改

图 1:可以修改完全可编辑 DataList 中的每个项(单击以查看全尺寸图像

本教程介绍如何让用户使用完全可编辑的 DataList 更新供应商地址信息。

步骤 1:在 DataList s ItemTemplate 中创建可编辑用户界面

在前面的教程中,我们在创建标准项级可编辑 DataList 时使用了两个模板:

  • ItemTemplate 包含只读用户界面(用于显示每个产品名称和价格的标签 Web 控件)。
  • EditItemTemplate 包含编辑模式用户界面(两个 TextBox Web 控件)。

DataList 的EditItemIndex属性决定如果有内容呈现,则使用EditItemTemplateDataListItem。 具体而言,使用 EditItemTemplate 来呈现其 ItemIndex 值与 DataList 的 EditItemIndex 属性匹配的 DataListItem 。 该模型在一次只能编辑一个项目时效果很好,但在创建完全可编辑的 DataList 时会出现问题。

对于完全可编辑的 DataList,我们希望 所有DataListItem s 都使用可编辑接口进行呈现。 实现此目的的最简单方法是在 中 ItemTemplate定义可编辑接口。 要修改供应商的地址信息,可编辑接口以文本形式显示供应商名称,并包含用于输入地址、城市和国家/地区的文本框。

首先打开 BatchUpdate.aspx 页面,添加 DataList 控件,并将其属性设置为 IDSuppliers。 从 DataList 的智能标记中,选择添加名为 SuppliersDataSource 的新 ObjectDataSource 控件。

创建一个名为 SuppliersDataSource 的新 ObjectDataSource

图 2:创建名为 SuppliersDataSource 的新 ObjectDataSource (单击可查看全尺寸图像

将 ObjectDataSource 配置为使用 SuppliersBLL 类 s GetSuppliers() 方法检索数据(请参阅图 3)。 与前面的教程一样,我们将直接使用业务逻辑层,而不是通过 ObjectDataSource 更新供应商信息。 因此,将“更新”选项卡中的下拉列表设置为“无”(请参阅图 4)。

使用 GetSuppliers() 方法检索供应商信息

图 3:使用 GetSuppliers() 方法检索供应商信息(单击以查看全尺寸图像

在“更新”选项卡中将 Drop-Down 列表设置为“无”

图 4:在“更新”选项卡中将 Drop-Down 列表设置为“无”(单击以查看全尺寸图像

完成向导后,Visual Studio 自动生成 DataList, ItemTemplate 以显示标签 Web 控件中数据源返回的每个数据字段。 我们需要修改此模板,以便它改为提供编辑界面。 ItemTemplate可以使用 DataList 智能标记中的“编辑模板”选项或通过声明性语法直接通过设计器自定义。

花点时间创建一个编辑界面,该界面将供应商的名称显示为文本,但地址、城市和国家/地区等内容则显示在文本框中。 进行这些更改后,页面的声明性语法应如下所示:

<asp:DataList ID="Suppliers" runat="server" DataKeyField="SupplierID"
    DataSourceID="SuppliersDataSource">
    <ItemTemplate>
        <h4><asp:Label ID="CompanyNameLabel" runat="server"
            Text='<%# Eval("CompanyName") %>' /></h4>
        <table border="0">
            <tr>
                <td class="SupplierPropertyLabel">Address:</td>
                <td class="SupplierPropertyValue">
                    <asp:TextBox ID="Address" runat="server"
                        Text='<%# Eval("Address") %>' />
                </td>
            </tr>
            <tr>
                <td class="SupplierPropertyLabel">City:</td>
                <td class="SupplierPropertyValue">
                    <asp:TextBox ID="City" runat="server"
                        Text='<%# Eval("City") %>' />
                </td>
            </tr>
            <tr>
                <td class="SupplierPropertyLabel">Country:</td>
                <td class="SupplierPropertyValue">
                    <asp:TextBox ID="Country" runat="server"
                        Text='<%# Eval("Country") %>' />
                </td>
            </tr>
        </table>
        <br />
    </ItemTemplate>
</asp:DataList>
<asp:ObjectDataSource ID="SuppliersDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
</asp:ObjectDataSource>

注释

与前面的教程一样,本教程中的 DataList 必须启用其视图状态。

ItemTemplate中,我使用了两个新的 CSS 类:SupplierPropertyLabelSupplierPropertyValue,它们已被添加到Styles.css类中,并配置为使用与ProductPropertyLabelProductPropertyValue CSS 类相同的样式设置。

.ProductPropertyLabel, .SupplierPropertyLabel
{
    font-weight: bold;
    text-align: right;
}
.ProductPropertyValue, .SupplierPropertyValue
{
    padding-right: 35px;
}

进行这些更改后,通过浏览器访问此页面。 如图 5 所示,每个 DataList 项将供应商名称显示为文本,并使用 TextBoxes 显示地址、城市和国家/地区。

DataList 中的每个供应商都是可编辑的

图 5:DataList 中的每个供应商都是可编辑的(单击以查看全尺寸图像

步骤 2:添加“全部更新”按钮

虽然图 5 中的每个供应商都有其地址、城市和国家/地区字段显示在 TextBox 中,但目前没有可用的“更新”按钮。 通常情况下,页面上会有一个“全部更新”按钮,而不是为每个项目设置一个“更新”按钮。点击“全部更新”按钮后,DataList 中的所有记录会被更新。 在本教程中,让我们添加两个“全部更新”按钮-一个位于页面顶部,一个位于底部(尽管单击任一按钮将具有相同的效果)。

首先,在 DataList 上方添加一个 Button Web 控件,并将其 ID 属性设置为 UpdateAll1。 接下来,在 DataList 下面添加第二个按钮 Web 控件,将其 ID 设置为 UpdateAll2。 将 Text 两个按钮的属性设置为“全部更新”。 最后,为这两个 Buttons Click 事件创建事件处理程序。 让我们将该逻辑重构到第三个方法,而不是复制每个事件处理程序中的更新逻辑, UpdateAllSupplierAddresses让事件处理程序只调用第三种方法。

protected void UpdateAll1_Click(object sender, EventArgs e)
{
    UpdateAllSupplierAddresses();
}
protected void UpdateAll2_Click(object sender, EventArgs e)
{
    UpdateAllSupplierAddresses();
}
private void UpdateAllSupplierAddresses()
{
    // TODO: Write code to update _all_ of the supplier addresses in the DataList
}

图 6 显示添加“全部更新”按钮后的页面。

已将两个更新所有按钮添加到页面

图 6:已将两个更新所有按钮添加到页面(单击以查看全尺寸图像

步骤 3:更新所有供应商地址信息

通过使所有 DataList 项目显示编辑界面,并添加“全部更新”按钮,现在剩下的任务就是编写代码以执行批量更新。 具体而言,我们需要循环访问 DataList 项,并为每个项调用 SuppliersBLL 类 s UpdateSupplierAddress 方法。

可以通过 DataList 属性访问构成 DataList 的Items实例集合DataListItem。 通过引用DataListItem,我们可以从DataKeys集合中获取相应的SupplierID,并在ItemTemplate中以编程方式引用 TextBox Web 控件,如以下代码所示:

private void UpdateAllSupplierAddresses()
{
    // Create an instance of the SuppliersBLL class
    SuppliersBLL suppliersAPI = new SuppliersBLL();
    // Iterate through the DataList's items
    foreach (DataListItem item in Suppliers.Items)
    {
        // Get the supplierID from the DataKeys collection
        int supplierID = Convert.ToInt32(Suppliers.DataKeys[item.ItemIndex]);
        // Read in the user-entered values
        TextBox address = (TextBox)item.FindControl("Address");
        TextBox city = (TextBox)item.FindControl("City");
        TextBox country = (TextBox)item.FindControl("Country");
        string addressValue = null, cityValue = null, countryValue = null;
        if (address.Text.Trim().Length > 0)
            addressValue = address.Text.Trim();
        if (city.Text.Trim().Length > 0)
              cityValue = city.Text.Trim();
        if (country.Text.Trim().Length > 0)
            countryValue = country.Text.Trim();
        // Call the SuppliersBLL class's UpdateSupplierAddress method
        suppliersAPI.UpdateSupplierAddress
            (supplierID, addressValue, cityValue, countryValue);
    }
}

当用户单击“全部更新”按钮之一时,该方法 UpdateAllSupplierAddresses 会遍历 Suppliers DataList 中的每一个 DataListItem,并调用 SuppliersBLL 类的 UpdateSupplierAddress 方法,传入相应的值。 地址、城市或国家/地区的未输入值将作为值 NothingUpdateSupplierAddress (而不是空白字符串)传递,从而导致数据库对底层记录字段进行 NULL

注释

作为增强功能,你可能希望向页面添加状态标签 Web 控件,该页面在执行批处理更新后提供一些确认消息。

仅更新已修改的地址

本教程中使用的批处理更新算法调用 UpdateSupplierAddress DataList 中每个 供应商的方法,无论其地址信息是否已更改。 虽然此类盲目更新通常不是性能问题,但如果正在审核数据库表的更改,它们可能会导致多余的记录。 例如,如果使用触发器将所有 UPDATE 记录到 Suppliers 审核表中,则每当用户单击“全部更新”按钮时,系统中的每个供应商都会创建新的审核记录,而不管用户是否进行任何更改。

ADO.NET DataTable 和 DataAdapter 类专为支持批量更新而设计,仅修改、删除和新增记录时才进行数据库通信。 DataTable 中的每个行都有一个 RowState 属性 ,该属性指示该行是否已添加到 DataTable、从其中删除、修改或保持不变。 最初填充 DataTable 时,所有行都标记为未更改。 更改任意一列的值都会将该行标记为已修改。

在类中SuppliersBLL,我们先在单个供应商记录SuppliersDataTable中读取指定的供应商地址信息,然后使用以下代码设置AddressCityCountry列值:

public bool UpdateSupplierAddress
    (int supplierID, string address, string city, string country)
{
    Northwind.SuppliersDataTable suppliers =
        Adapter.GetSupplierBySupplierID(supplierID);
    if (suppliers.Count == 0)
        // no matching record found, return false
        return false;
    else
    {
        Northwind.SuppliersRow supplier = suppliers[0];
        if (address == null)
            supplier.SetAddressNull();
        else
            supplier.Address = address;
        if (city == null)
            supplier.SetCityNull();
        else
            supplier.City = city;
        if (country == null)
            supplier.SetCountryNull();
        else
            supplier.Country = country;
        // Update the supplier Address-related information
        int rowsAffected = Adapter.Update(supplier);
        // Return true if precisely one row was updated,
        // otherwise false
        return rowsAffected == 1;
    }
}

无论值是否已更改,此代码都会将传入的地址、城市和国家/地区值直接赋给SuppliersDataTable中的SuppliersRow。 这些修改会导致 SuppliersRow s RowState 属性被标记为已修改。 当调用数据访问层的方法Update时,会检测到SupplierRow已被修改,因此向数据库发送UPDATE命令。

但是,假设我们向此方法添加了代码,以便在传入地址、城市和国家/地区值与 SuppliersRow 现有值不同时仅分配传入地址、城市和国家/地区值。 如果地址、城市和国家/地区与现有数据相同,则不会进行 SupplierRow 任何更改,并且 s RowState 将标记为未更改。 净结果是,调用 DAL 方法 Update 时,不会进行任何数据库调用,因为 SuppliersRow 尚未修改数据库。

若要执行此更改,请将盲目分配传入地址、城市和国家/地区值的语句替换为以下代码:

// Only assign the values to the SupplierRow's column values if they differ
if (address == null && !supplier.IsAddressNull())
    supplier.SetAddressNull();
else if ((address != null && supplier.IsAddressNull()) ||
         (!supplier.IsAddressNull() &&
         string.Compare(supplier.Address, address) != 0))
    supplier.Address = address;
if (city == null && !supplier.IsCityNull())
    supplier.SetCityNull();
else if ((city != null && supplier.IsCityNull()) ||
         (!supplier.IsCityNull() && string.Compare(supplier.City, city) != 0))
    supplier.City = city;
if (country == null && !supplier.IsCountryNull())
    supplier.SetCountryNull();
else if ((country != null && supplier.IsCountryNull()) ||
         (!supplier.IsCountryNull() &&
         string.Compare(supplier.Country, country) != 0))
    supplier.Country = country;

借助此添加的代码,DAL s Update 方法仅针对地址相关值已更改的记录向数据库发送 UPDATE 语句。

或者,我们可以跟踪传入的地址字段与数据库数据之间是否存在任何差异,如果没有,只需绕过对 DAL 方法的 Update 调用。 如果使用 DB 直接方法,则此方法非常有效,因为 DB 直接方法没有接收一个可以检查的SuppliersRow实例来确定是否实际需要数据库调用。

注释

每次调用该方法时 UpdateSupplierAddress ,都会调用数据库以检索有关更新记录的信息。 然后,如果数据有任何更改,则会对数据库进行另一次调用以更新表行。 可以通过创建一个UpdateSupplierAddress方法重载来优化工作流,该方法重载接受一个EmployeesDataTable实例,该实例具有来自BatchUpdate.aspx页面的所有更改。 然后,它可以对数据库进行一次调用,以便从 Suppliers 表中获取所有记录。 然后,可以枚举这两个结果集,并且只能更新发生更改的记录。

概要

本教程介绍了如何创建完全可编辑的 DataList,允许用户快速修改多个供应商的地址信息。 我们首先在 DataList s ItemTemplate中为供应商地址、城市和国家/地区值定义 TextBox Web 控件的编辑界面。 接下来,我们在 DataList 前面和下方添加了“更新所有”按钮。 用户进行更改并单击“全部更新”按钮之一后, DataListItem 将枚举 s 并调用 SuppliersBLL 类的方法 UpdateSupplierAddress

快乐编程!

关于作者

斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams Teach Yourself ASP.NET 2.0 in 24 Hours》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。

特别致谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Zack Jones 和 Ken Pespisa。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com