执行批量更新 (C#)

作者 :Scott Mitchell

下载 PDF

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

简介

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

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

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

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

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

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

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

DataList 的 EditItemIndex 属性指示 DataListItem 使用 EditItemTemplate呈现任何) 时 (。 具体而言, DataListItem 使用 呈现其 ItemIndex 值与 DataList 属性匹配的 EditItemIndexEditItemTemplate。 如果一次只能编辑一个项,但在创建完全可编辑的 DataList 时,此模型就很有效。

对于完全可编辑的 DataList,我们希望使用可编辑接口呈现 所有DataListItem s。 实现此目的的最简单方法是在 中定义可编辑的 ItemTemplate接口。 为了修改供应商地址信息,可编辑的界面包含供应商名称作为文本,然后包含地址、城市和国家/地区值的 TextBox。

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

创建新的对象DataSource 名为 SuppliersDataSource

图 2:新建名为 SuppliersDataSource 的对象数据源 (单击以查看全尺寸图像)

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

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

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

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

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

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

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

<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,并配置为使用与 和 ProductPropertyValue CSS 类相同的样式设置ProductPropertyLabel

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

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

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

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

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

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

首先,在 DataList 上添加一个 Button Web 控件,并将其 ID 属性设置为 UpdateAll1。 接下来,在 DataList 下添加第二个 Button 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 的实例集合DataListItemItems。 通过对 的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 该方法会循环访问 DataListItem DataList 中的每个 Suppliers 按钮,并调用 SuppliersBLL 类方法 UpdateSupplierAddress ,并传入相应的值。 地址、城市或国家/地区传递的非输入值是 (而不是 NothingUpdateSupplierAddress 空白字符串) ,这会导致基础记录字段的数据库 NULL

注意

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

仅更新已修改的地址

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

ADO.NET DataTable 和 DataAdapter 类旨在支持批处理更新,其中只有修改、删除和新记录会导致任何数据库通信。 DataTable 中的每一行都有一个 RowState 属性 ,该属性指示该行是否已添加到 DataTable、从其中删除、已修改还是保持不变。 最初填充 DataTable 时,所有行都标记为未更改。 更改任何行的值 s 列会将该行标记为已修改。

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

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

但是,假设我们向此方法添加了代码,以便仅分配传入的地址、城市和国家/地区值(如果它们与 SuppliersRow 现有值不同)。 如果地址、城市和国家/地区与现有数据相同,则不会进行更改,并且 SupplierRowRowState 会将 标记为未更改。 最终结果是,调用 DAL s 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 直接方法,此方法非常有效,因为数据库直接方法不会传递 SuppliersRow 实例,该实例可以检查该 RowState 实例以确定实际是否需要数据库调用。

注意

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

总结

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

编程愉快!

关于作者

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

特别感谢

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