包装事务内的数据库修改 (C#)

作者 :斯科特·米切尔

下载 PDF

本教程是查看更新、删除和插入批处理数据的四个教程中的第一个。 在本教程中,我们将了解数据库事务如何允许批量修改作为原子操作执行,这可确保所有步骤都成功或所有步骤都失败。

简介

“插入、更新和删除数据 概述”开始,GridView 提供了对行级编辑和删除的内置支持。 只需单击鼠标即可创建丰富的数据修改界面,而无需编写一行代码,只要你对每行进行编辑和删除的内容即可。 但是,在某些情况下,这是不够的,我们需要为用户提供编辑或删除一批记录的能力。

例如,大多数基于 Web 的电子邮件客户端使用网格列出每封邮件,其中每一行都包含一个复选框以及电子邮件信息(主题、发件人等)。 此界面允许用户通过选中多个消息并单击“删除所选邮件”按钮来删除多个消息。 在用户通常编辑许多不同的记录的情况下,批处理编辑界面是理想的选择。 批编辑界面呈现每一行,而不是强制用户单击“编辑”,进行更改,然后针对需要修改的每个记录单击“更新”。 用户可以快速修改需要更改的行集,然后单击“全部更新”按钮保存这些更改。 在本教程集中,我们将介绍如何创建用于插入、编辑和删除数据批处理的接口。

执行批处理操作时,请务必确定批处理中的某些操作是否应该成功,而另一些操作则失败。 考虑批处理删除接口 - 如果第一个选定记录已成功删除,但第二个记录因外键约束冲突而失败,会发生什么情况? 第一条记录的删除应回滚还是第一条记录保持删除是否可接受?

如果希望将批处理操作视为 原子操作,其中所有步骤都成功或所有步骤都失败,则需要扩充数据访问层,以包括对 数据库事务的支持。 数据库事务保证事务集INSERTUPDATEDELETE原子性和在事务保护下执行的语句,并且是大多数新式数据库系统支持的一项功能。

本教程介绍如何扩展 DAL 以使用数据库事务。 后续教程将检查实现网页,以便批量插入、更新和删除接口。 让我们开始吧!

注意

在批处理事务中修改数据时,并不总是需要原子性。 在某些情况下,某些数据修改可能会成功,其他数据修改会失败,例如从基于 Web 的电子邮件客户端删除一组电子邮件时。 如果在删除过程中出现数据库错误,则可能可以接受这些记录处理且未出错时仍会被删除。 在这种情况下,不需要修改 DAL 以支持数据库事务。 但是,还有其他批处理操作方案,其中原子性至关重要。 当客户将资金从一个银行账户移到另一个银行账户时,必须执行两项操作:必须将资金从第一个帐户中扣除,然后添加到第二个帐户。 虽然银行可能不介意第一步成功,但第二步失败,但其客户可能会感到不安。 我鼓励你完成本教程,并实现对 DAL 的增强功能,以支持数据库事务,即使你不打算在批处理插入、更新和删除接口中使用它们,我们将在以下三个教程中构建。

事务概述

大多数数据库都支持 事务,这些事务允许将多个数据库命令分组到单个逻辑工作单元中。 保证构成事务的数据库命令是原子命令,这意味着所有命令都将失败或全部成功。

通常,事务通过 SQL 语句使用以下模式实现:

  1. 指示事务的开始。
  2. 执行构成事务的 SQL 语句。
  3. 如果在步骤 2 中的某个语句中出错,请回滚事务。
  4. 如果步骤 2 中的所有语句都完成且没有错误,请提交事务。

编写 SQL 脚本或创建存储过程时,可以使用 ADO.NET 或命名空间中的类以编程方式手动输入用于创建、提交和回滚事务的 System.Transactions SQL 语句。 在本教程中,我们将仅检查使用 ADO.NET 管理事务。 在将来的教程中,我们将了解如何在数据访问层中使用存储过程,此时我们将探讨用于创建、回滚和提交事务的 SQL 语句。

注意

命名空间TransactionScope中的System.Transactions使开发人员能够以编程方式包装事务范围内的一系列语句,并支持涉及多个源的复杂事务,例如两个不同的数据库,甚至异类类型的数据存储,例如Microsoft SQL Server 数据库、Oracle 数据库和 Web 服务。 我决定对本教程使用 ADO.NET 事务而不是类, TransactionScope 因为 ADO.NET 对数据库事务更具体,在许多情况下,资源密集型要少得多。 此外,在某些情况下 TransactionScope ,类使用Microsoft分布式事务处理协调器(MSDTC)。 围绕 MSDTC 的配置、实现和性能问题使其成为一个相当专业和高级的主题,超出了这些教程的范围。

在 ADO.NET 中使用 SqlClient 提供程序时,通过调用SqlConnection方法BeginTransaction(返回对象SqlTransaction)来启动事务。 构成事务的数据修改语句放置在块 try...catch 中。 如果在块中的try语句中发生错误,则执行将传输到catch可以通过对象方法Rollback回滚SqlTransaction事务的块。 如果所有语句都成功完成,则对块末尾try的对象方法Commit的调用SqlTransaction将提交事务。 以下代码片段演示了此模式。 请参阅 维护数据库与事务的一致性。

// Create the SqlTransaction object
SqlTransaction myTransaction = SqlConnectionObject.BeginTransaction();
try
{
    /*
     * ... Perform the database transaction�s data modification statements...
     */
    // If we reach here, no errors, so commit the transaction
    myTransaction.Commit();
}
catch
{
    // If we reach here, there was an error, so rollback the transaction
    myTransaction.Rollback();
    throw;
}

默认情况下,类型化数据集中的 TableAdapters 不使用事务。 为了为事务提供支持,我们需要扩充 TableAdapter 类,以包含使用上述模式的其他方法在事务范围内执行一系列数据修改语句。 在步骤 2 中,我们将了解如何使用分部类添加这些方法。

步骤 1:创建使用批处理数据网页

在开始探索如何扩充 DAL 以支持数据库事务之前,让我们先花点时间创建本教程所需的 ASP.NET 网页,再创建以下三个网页。 首先添加一 BatchData 个名为的新文件夹,然后添加以下 ASP.NET 页,将每个页面与 Site.master 母版页相关联。

  • Default.aspx
  • Transactions.aspx
  • BatchUpdate.aspx
  • BatchDelete.aspx
  • BatchInsert.aspx

为 SqlDataSource 相关教程添加 ASP.NET 页

图 1:为 SqlDataSource 相关教程添加 ASP.NET 页

与其他文件夹一样, Default.aspx 将使用 SectionLevelTutorialListing.ascx 用户控件在其部分中列出教程。 因此,通过将此用户控件从解决方案资源管理器拖动到Default.aspx页面的设计视图中,将其添加到该控件。

将 SectionLevelTutorialListing.ascx 用户控件添加到 Default.aspx

图 2:将用户控件Default.aspx添加到 SectionLevelTutorialListing.ascx单击以查看全尺寸图像

最后,将这四个页面作为条目添加到 Web.sitemap 文件中。 具体而言,在自定义网站地图 <siteMapNode>后添加以下标记:

<siteMapNode title="Working with Batched Data" 
    url="~/BatchData/Default.aspx" 
    description="Learn how to perform batch operations as opposed to 
                 per-row operations.">
    
    <siteMapNode title="Adding Support for Transactions" 
        url="~/BatchData/Transactions.aspx" 
        description="See how to extend the Data Access Layer to support 
                     database transactions." />
    <siteMapNode title="Batch Updating" 
        url="~/BatchData/BatchUpdate.aspx" 
        description="Build a batch updating interface, where each row in a 
                      GridView is editable." />
    <siteMapNode title="Batch Deleting" 
        url="~/BatchData/BatchDelete.aspx" 
        description="Explore how to create an interface for batch deleting 
                     by adding a CheckBox to each GridView row." />
    <siteMapNode title="Batch Inserting" 
        url="~/BatchData/BatchInsert.aspx" 
        description="Examine the steps needed to create a batch inserting 
                     interface, where multiple records can be created at the 
                     click of a button." />
</siteMapNode>

更新 Web.sitemap后,请花点时间通过浏览器查看教程网站。 左侧菜单现在包含用于处理批处理数据教程的项目。

站点地图现在包含使用批处理数据教程的条目

图 3:网站地图现在包含使用批处理数据教程的条目

步骤 2:更新数据访问层以支持数据库事务

如第一教程中所述,创建数据访问层,DAL 中的类型化数据集由 DataTables 和 TableAdapters 组成。 DataTable 保存数据,而 TableAdapters 提供将数据从数据库读取到 DataTable 的功能,以使用对数据表所做的更改来更新数据库,依此类推。 回想一下,TableAdapters 提供了两种更新数据的模式,我称之为 Batch Update 和 DB-Direct。 使用 Batch 更新模式,TableAdapter 将传递 DataRows 的数据集、DataTable 或集合。 将枚举此数据,并针对每个插入、修改或删除的行、InsertCommandUpdateCommandDeleteCommand执行数据。 使用 DB-Direct 模式时,TableAdapter 改为传递插入、更新或删除单个记录所需的列的值。 然后,DB Direct 模式方法使用这些传入值来执行相应的InsertCommandUpdateCommandDeleteCommand语句。

无论使用的更新模式如何,TableAdapters 自动生成的方法都不会使用事务。 默认情况下,TableAdapter 执行的每个插入、更新或删除都被视为单个离散操作。 例如,假设 BLL 中的某些代码使用 DB-Direct 模式将十条记录插入数据库中。 此代码将调用 TableAdapter 方法 Insert 十次。 如果前五次插入成功,但第六次插入导致异常,前五个插入的记录将保留在数据库中。 同样,如果使用 Batch 更新模式对 DataTable 中的插入、修改和删除行执行插入、更新和删除操作,如果前几个修改成功,但后来遇到错误,则已完成的早期修改将保留在数据库中。

在某些情况下,我们希望确保一系列修改的原子性。 为此,必须手动扩展 TableAdapter,方法是添加执行事务保护下的和下面执行InsertCommand的新UpdateCommandDeleteCommand方法。 在创建数据访问层,我们使用分部类来扩展类型化数据集中 DataTable 的功能。 此方法还可用于 TableAdapters。

类型化数据集 Northwind.xsd 位于 App_Code 文件夹子 DAL 文件夹中。 在 DAL 命名 TransactionSupport 的文件夹中创建一个子文件夹,并添加名为 ProductsTableAdapter.TransactionSupport.cs 的新类文件(请参阅图 4)。 此文件将保留部分实现 ProductsTableAdapter ,其中包括使用事务执行数据修改的方法。

添加名为 TransactionSupport 的文件夹和名为 ProductsTableAdapter.TransactionSupport.cs 的类文件

图 4:添加命名 TransactionSupport 的文件夹和名为 的类文件 ProductsTableAdapter.TransactionSupport.cs

在文件中输入以下代码 ProductsTableAdapter.TransactionSupport.cs

using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace NorthwindTableAdapters
{
    public partial class ProductsTableAdapter
    {
        private SqlTransaction _transaction;
        private SqlTransaction Transaction
        {
            get
            {                
                return this._transaction;
            }
            set
            {
                this._transaction = value;
            }
        }
        public void BeginTransaction()
        {
            // Open the connection, if needed
            if (this.Connection.State != ConnectionState.Open)
                this.Connection.Open();
            // Create the transaction and assign it to the Transaction property
            this.Transaction = this.Connection.BeginTransaction();
            // Attach the transaction to the Adapters
            foreach (SqlCommand command in this.CommandCollection)
            {
                command.Transaction = this.Transaction;
            }
            this.Adapter.InsertCommand.Transaction = this.Transaction;
            this.Adapter.UpdateCommand.Transaction = this.Transaction;
            this.Adapter.DeleteCommand.Transaction = this.Transaction;
        }
        public void CommitTransaction()
        {
            // Commit the transaction
            this.Transaction.Commit();
            // Close the connection
            this.Connection.Close();
        }
        public void RollbackTransaction()
        {
            // Rollback the transaction
            this.Transaction.Rollback();
            // Close the connection
            this.Connection.Close();
        }
   }
}

partial此处的类声明中的关键字向编译器指示要添加到命名空间中的NorthwindTableAdapters类的成员ProductsTableAdapter。 记下 using System.Data.SqlClient 文件顶部的语句。 由于 TableAdapter 配置为使用 SqlClient 提供程序,因此在内部,它使用对象 SqlDataAdapter 向数据库发出其命令。 因此,我们需要使用 SqlTransaction 类开始事务,然后提交或回滚它。 如果使用非 Microsoft SQL Server 的数据存储,则需要使用适当的提供程序。

这些方法提供启动、回滚和提交事务所需的构建基块。 它们被标记 public,使它们能够从 ProductsTableAdapterDAL 中的另一个类或体系结构中的另一层(如 BLL)内部使用。 BeginTransaction 打开 TableAdapter 的内部 SqlConnection (如果需要),开始事务并将其分配给 Transaction 属性,并将事务附加到内部 SqlDataAdapter 对象 SqlCommandCommitTransaction并在RollbackTransaction关闭内部Connection对象之前分别调用Transaction对象和CommitRollback方法。

步骤 3:在事务的伞下添加更新和删除数据的方法

完成这些方法后,我们便可以向或 BLL 添加方法 ProductsDataTable ,以在事务的伞下执行一系列命令。 以下方法使用 Batch 更新模式通过事务更新 ProductsDataTable 实例。 它通过调用 BeginTransaction 该方法来启动事务,然后使用 try...catch 块发出数据修改语句。 如果对 Adapter 对象方法的 Update 调用导致异常,执行将传输到 catch 将回滚事务的块,并重新引发异常。 回想一下,该方法Update通过枚举提供的ProductsDataTable行和执行必要的InsertCommandUpdateCommandDeleteCommand s 来实现 Batch 更新模式。 如果其中任一命令导致错误,则会回滚事务,撤消在事务生存期内所做的先前修改。 Update如果语句完成且未出错,则事务将全部提交。

public int UpdateWithTransaction(Northwind.ProductsDataTable dataTable)
{
    this.BeginTransaction();
    try
    {
        // Perform the update on the DataTable
        int returnValue = this.Adapter.Update(dataTable);
        // If we reach here, no errors, so commit the transaction
        this.CommitTransaction();
        return returnValue;
    }
    catch
    {
        // If we reach here, there was an error, so rollback the transaction
        this.RollbackTransaction();
        throw;
    }
}

通过 UpdateWithTransaction 分部类将该方法添加到 ProductsTableAdapter 类中 ProductsTableAdapter.TransactionSupport.cs。 或者,此方法可以添加到业务逻辑层类, ProductsBLL 并进行了一些细微的语法更改。 也就是说,此关键字位于 中this.BeginTransaction(),需要this.RollbackTransaction()替换为Adapter(回想一下,Adapter这是类型ProductsTableAdapter属性ProductsBLL的名称)。 this.CommitTransaction()

该方法 UpdateWithTransaction 使用 Batch 更新模式,但一系列 DB-Direct 调用也可以在事务范围内使用,如以下方法所示。 该方法DeleteProductsWithTransaction接受为类型(要ProductID删除的类型int)的输入List<T>。 该方法通过调用BeginTransaction启动事务,然后在块中try循环访问提供的列表,调用每个ProductID值的 DB-Direct 模式Delete方法。 如果任何调用 Delete 失败,则会将控制权传输到 catch 回滚事务的块,并重新引发异常。 如果所有调用 Delete 都成功,则提交事务。 将此方法添加到 ProductsBLL 类。

public void DeleteProductsWithTransaction
    (System.Collections.Generic.List<int> productIDs)
{
    // Start the transaction
    Adapter.BeginTransaction();
    try
    {
        // Delete each product specified in the list
        foreach (int productID in productIDs)
        {
            Adapter.Delete(productID);
        }
        // Commit the transaction
        Adapter.CommitTransaction();
    }
    catch
    {
        // There was an error - rollback the transaction
        Adapter.RollbackTransaction();
        throw;
    }
}

跨多个 TableAdapters 应用事务

本教程中检查的与事务相关的代码允许针对要被视为原子操作的多个语句 ProductsTableAdapter 。 但是,如果需要以原子方式对不同数据库表进行多次修改,该怎么办? 例如,删除某个类别时,我们可能首先希望将其当前产品重新分配给其他类别。 这两个步骤将重新分配产品并删除类别应作为原子操作执行。 但是, ProductsTableAdapter 仅包括用于修改 Products 表的方法,以及 CategoriesTableAdapter 用于修改 Categories 表的唯一方法。 那么,事务如何包含两个 TableAdapters?

一个选项是向命名DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID)方法添加方法CategoriesTableAdapter,并让该方法调用存储过程,该存储过程既重新分配产品,又删除存储过程中定义的事务范围内的类别。 我们将在未来的教程中了解如何在存储过程中开始、提交和回滚事务。

另一个选项是在 DAL 中创建包含该方法的 DeleteCategoryAndReassignProducts(categoryIDtoDelete, reassignToCategoryID) 帮助程序类。 此方法将创建一个实例 CategoriesTableAdapterProductsTableAdapter 然后将这两个 TableAdapters Connection 属性设置为同一 SqlConnection 实例。 此时,两个 TableAdapters 中的任一项都会通过调用 BeginTransaction来启动事务。 将根据需要在提交或回滚事务的块中 try...catch 调用用于重新分配产品和删除类别的 TableAdapters 方法。

步骤 4:将UpdateWithTransaction方法添加到业务逻辑层

在步骤 3 中,我们向 ProductsTableAdapter DAL 中添加了一个UpdateWithTransaction方法。 我们应该向 BLL 添加相应的方法。 虽然演示层可以直接调用 DAL 来调用 UpdateWithTransaction 该方法,但这些教程一直努力定义一个分层体系结构,使 DAL 与表示层隔离开来。 因此,让我们继续这种方法。

ProductsBLL打开类文件并添加一个名为UpdateWithTransaction的方法,该方法只需调用相应的 DAL 方法。 现在应该有两个新方法 ProductsBLLUpdateWithTransaction刚刚添加的方法,并在 DeleteProductsWithTransaction步骤 3 中添加。

public int UpdateWithTransaction(Northwind.ProductsDataTable products)
{
    return Adapter.UpdateWithTransaction(products);
}
public void DeleteProductsWithTransaction
    (System.Collections.Generic.List<int> productIDs)
{
    // Start the transaction
    Adapter.BeginTransaction();
    try
    {
        // Delete each product specified in the list
        foreach (int productID in productIDs)
            Adapter.Delete(productID);
        // Commit the transaction
        Adapter.CommitTransaction();
    }
    catch
    {
        // There was an error - rollback the transaction
        Adapter.RollbackTransaction();
        throw;
    }
}

注意

这些方法不包括 DataObjectMethodAttribute 分配给类中 ProductsBLL 大多数其他方法的属性,因为我们将直接从 ASP.NET 页代码隐藏类调用这些方法。 回想一下, DataObjectMethodAttribute 用于标记 ObjectDataSource 配置数据源向导和选项卡(SELECT、UPDATE、INSERT 或 DELETE)中显示的方法。 由于 GridView 缺少对批处理编辑或删除的任何内置支持,因此我们必须以编程方式调用这些方法,而不是使用无代码声明性方法。

步骤 5:从呈现层原子更新数据库数据

为了说明事务在更新一批记录时的效果,让我们创建一个用户界面,该用户界面列出 GridView 中的所有产品,并包括一个按钮 Web 控件,在单击时,重新分配产品 CategoryID 值。 具体而言,类别重新分配将进行,以便前几个产品分配有效 CategoryID 值,而其他产品则有意分配不存在 CategoryID 的值。 如果尝试使用与现有类别CategoryID不匹配的产品CategoryID更新数据库,将发生外键约束冲突,并引发异常。 在此示例中,我们将看到,使用事务时,从外键约束冲突引发的异常将导致回滚以前的有效 CategoryID 更改。 但是,如果不使用事务,对初始类别的修改将保持不变。

首先打开 Transactions.aspx 文件夹中的页面 BatchData ,并将 GridView 从工具箱拖到设计器上。 将其 ID 设置为 Products 其智能标记,并将其绑定到名为 ProductsDataSource 的新 ObjectDataSource。 将 ObjectDataSource 配置为从 ProductsBLL 类 s GetProducts 方法拉取其数据。 这是只读 GridView,因此将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为“无”,然后单击“完成”。

图 5:将 ObjectDataSource 配置为使用 ProductsBLL 类 s GetProducts 方法

图 5:图 5:将 ObjectDataSource 配置为使用 ProductsBLL 类 s GetProducts 方法(单击以查看全尺寸图像

将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为 (无)

图 6:将 UPDATE、INSERT 和 DELETE 选项卡中的下拉列表设置为 (无) (单击可查看全尺寸图像

完成“配置数据源”向导后,Visual Studio 将为产品数据字段创建 BoundFields 和 CheckBoxField。 删除除 Product CategoryIDProductName和 Category 以外的ProductID所有字段,以及CategoryName分别将 ProductName CategoryName And BoundFields HeaderText 属性重命名为 Product 和 Category。 在智能标记中,选中“启用分页”选项。 进行这些修改后,GridView 和 ObjectDataSource 的声明性标记应如下所示:

<asp:GridView ID="Products" runat="server" AllowPaging="True" 
    AutoGenerateColumns="False" DataKeyNames="ProductID" 
    DataSourceID="ProductsDataSource">
    <Columns>
        <asp:BoundField DataField="ProductID" HeaderText="ProductID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" HeaderText="Product" 
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID" 
            SortExpression="CategoryID" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetProducts" TypeName="ProductsBLL">
</asp:ObjectDataSource>

接下来,在 GridView 上方添加三个按钮 Web 控件。 将第一个 Button s Text 属性设置为“刷新网格”,第二个属性设置为“修改类别”(WITH TRANSACTION),将第三个按钮设置为“修改类别”(没有 TRANSACTION)。

<p>
    <asp:Button ID="RefreshGrid" runat="server" Text="Refresh Grid" />
</p>
<p>
    <asp:Button ID="ModifyCategoriesWithTransaction" runat="server"
        Text="Modify Categories (WITH TRANSACTION)" />
</p>
<p>
    <asp:Button ID="ModifyCategoriesWithoutTransaction" runat="server"
        Text="Modify Categories (WITHOUT TRANSACTION)" />
</p>

此时,Visual Studio 中的设计视图应类似于图 7 中显示的屏幕截图。

页面包含 GridView 和三个按钮 Web 控件

图 7:页面包含 GridView 和三个按钮 Web 控件(单击以查看全尺寸图像

为三个 Button s Click 事件中的每个事件创建事件处理程序,并使用以下代码:

protected void RefreshGrid_Click(object sender, EventArgs e)
{
    Products.DataBind();
}
protected void ModifyCategoriesWithTransaction_Click(object sender, EventArgs e)
{
    // Get the set of products
    ProductsBLL productsAPI = new ProductsBLL();
    Northwind.ProductsDataTable products = productsAPI.GetProducts();
    // Update each product's CategoryID
    foreach (Northwind.ProductsRow product in products)
    {
        product.CategoryID = product.ProductID;
    }
    // Update the data using a transaction
    productsAPI.UpdateWithTransaction(products);
    // Refresh the Grid
    Products.DataBind();
}
protected void ModifyCategoriesWithoutTransaction_Click(object sender, EventArgs e)
{
    // Get the set of products
    ProductsBLL productsAPI = new ProductsBLL();
    Northwind.ProductsDataTable products = productsAPI.GetProducts();
    // Update each product's CategoryID
    foreach (Northwind.ProductsRow product in products)
    {
        product.CategoryID = product.ProductID;
    }
    // Update the data WITHOUT using a transaction
    NorthwindTableAdapters.ProductsTableAdapter productsAdapter = 
        new NorthwindTableAdapters.ProductsTableAdapter();
    productsAdapter.Update(products);
    // Refresh the Grid
    Products.DataBind();
}

刷新 Button 事件处理程序 Click 只需通过调用 Products GridView 方法 DataBind 将数据重新绑定到 GridView。

第二个事件处理程序重新分配产品, CategoryID 并使用 BLL 中的新事务方法在事务的伞下执行数据库更新。 请注意,每个产品 CategoryID 都任意设置为与其 ProductID相同的值。 这适用于前几个产品,因为这些产品具有 ProductID 恰好映射到有效 CategoryID 值。 但是,一旦 ProductID 开始变得太大,这种巧合重叠的 ProductID s 和 CategoryID s 不再适用。

第三 Click 个事件处理程序以相同的方式更新产品 CategoryID ,但使用 ProductsTableAdapter 默认 Update 方法将更新发送到数据库。 此方法 Update 不会在事务中包装一系列命令,因此在首次遇到外键约束冲突错误之前,这些更改将持续进行。

若要演示此行为,请通过浏览器访问此页面。 最初,应会看到数据的第一页,如图 8 所示。 接下来,单击“修改类别”(WITH TRANSACTION)按钮。 这将导致回发并尝试更新所有产品 CategoryID 值,但将导致外键约束冲突(请参阅图 9)。

产品显示在可分页的 GridView 中

图 8:产品显示在可分页网格视图中(单击以查看全尺寸图像

重新分配类别会导致外键约束冲突

图 9:重新分配类别会导致外键约束冲突(单击可查看全尺寸图像

现在,点击浏览器的“后退”按钮,然后单击“刷新网格”按钮。 刷新数据后,应会看到与图 8 中显示的输出完全相同。 也就是说,尽管某些产品 CategoryID 已更改为法律值并在数据库中更新,但在发生外键约束冲突时,它们也会回滚。

现在,请尝试单击“修改类别”(没有 TRANSACTION)按钮。 这将导致相同的外键约束冲突错误(见图 9),但这次将值更改为法定值的产品 CategoryID 将不会回滚。 点击浏览器的“后退”按钮,然后单击“刷新网格”按钮。 如图 10 所示, CategoryID 前八个产品的 s 已重新分配。 例如,在图 8 中,张有一个 CategoryID 1,但在图 10 中,它已重新分配到 2。

某些产品 CategoryID 值已更新,而其他产品未更新

图 10:某些产品值已更新,而其他值 CategoryID 未更新(单击以查看全尺寸图像

总结

默认情况下,TableAdapter 的方法不会在事务范围内包装已执行的数据库语句,但稍作工作,我们可以添加将创建、提交和回滚事务的方法。 在本教程中,我们在类中创建 ProductsTableAdapter 三种此类方法: BeginTransactionCommitTransactionRollbackTransaction。 我们了解了如何将这些方法与块一起使用 try...catch ,以原子方式创建一系列数据修改语句。 具体而言,我们在该方法中创建UpdateWithTransactionProductsTableAdapter的方法,该方法使用 Batch 更新模式对提供的ProductsDataTable行执行必要的修改。 我们还将该方法添加到 ProductsBLL BLL 中的类,该类接受值ListProductID作为其输入,并为每个ProductID方法调用 DB-Direct 模式方法DeleteDeleteProductsWithTransaction 这两种方法首先创建事务,然后在块中 try...catch 执行数据修改语句。 如果发生异常,则会回滚事务,否则将提交该事务。

步骤 5 说明了事务批处理更新与忽略使用事务的批处理更新的影响。 在接下来的三个教程中,我们将基于本教程中的基础,创建用于执行批处理更新、删除和插入的用户界面。

快乐编程!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0。 他可以通过他的博客联系到mitchell@4GuysFromRolla.com他,可以在该博客中找到http://ScottOnWriting.NET

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是戴夫·加德纳、希尔顿·吉森诺和特蕾莎·墨菲。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请把我扔一条线。mitchell@4GuysFromRolla.com