在事务内部打开和关闭数据库连接

9
我设计了我们框架的数据访问部分,以便每次业务对象(BO)需要与数据库交互时,它都必须打开连接,调用数据访问层(执行查询)然后关闭连接。然后,如果需要在事务中运行,它将打开连接,开始事务,调用数据访问层(执行查询),然后提交事务,关闭事务,最后关闭连接。
我这样做是出于“晚开早关”的心态...但如果我需要调用其他 BOs 在单个事务中提交数据,有没有更好的处理打开和关闭连接以及处理事务的方法?
我是一个新手,在设计应用程序架构方面,所以希望我没有做错...感谢任何帮助。
5个回答

5

如果一个给定的业务对象需要在事务中执行各种方法,请使用 TransactionScope,如下所示:

using ( var transactionScope = new TransactionScope() )
{
    this.Save();
    childObjA.Save();
    childObjB.Save();
    childObjC.Save();
    childObjD.Save();

    transactionScope.Complete();
}

如果任何对象抛出异常,它将回滚事务。
有关更多信息,请参见TransactionScopeMSDN参考页面

3
虽然 TransactionScope 是此处要使用的类,但根据 OP 目前的设计,它将导致分布式事务或者可能引发异常,如果 MSDTC 被禁用或者受到限制,这很可能不是期望的结果。 - Aaronaught
2
@Aaronaught,使用SQL Server 2008和.NET 3.5,该事务不会升级为分布式事务(针对当前设计)。 - Randy Levy
2
@Tuzo: 是的,使用SQL Server 2008,如果所有连接都是到同一个数据库,并且始终只有一个连接处于打开状态,则可以规避多连接问题。这仍然是一种相当可疑的做法,特别是当另一种替代设计更易于构建/维护时。 - Aaronaught
1
我不太理解这个代码示例中发生了什么。IDbConnection.Close:_"Close方法会回滚任何未决的事务。"_ 另一方面,SqlConnection.Close实现了该接口方法:_"通过System.Transactions启动的事务由System.Transactions基础结构控制,并且不受SqlConnection.Close的影响。"_ 所以,TransactionScope只适用于SQL Server? - stakx - no longer contributing
顺便提一下,关于代码示例:为什么要多次调用 childObj.Save()?这些是针对不同对象(childObjAchildObjB 等)的调用吗? - stakx - no longer contributing
@stakx - 是的。这些将代表各种子属性,您可以在其上调用保存操作。 - Thomas

4

当高级抽象依赖于低级抽象(例如业务逻辑类依赖于数据连接),通常通过构造函数提供低级抽象。这种技术称为构造函数注入

public class OrderService
{
    private SqlConnection connection;

    public OrderService(SqlConnection connection)
    {
        if (connection == null)
            throw new ArgumentNullException("connection");
        this.connection = connection;
    }

    // Other methods
}

这样你就可以编写类似下面的服务代码:

这将允许您针对类似以下的服务编写代码:

using (TransactionScope tsc = new TransactionScope())
using (SqlConnection connection = new SqlConnection(...))
{
    connection.Open();
    OrderService os = new OrderService(connection);
    os.ProcessOrder(myOrder);
    ShippingService ss = new ShippingService(connection);
    ss.ShipOrder(myOrder);
    tsc.Complete();
}

最终,您最想要的可能是共享一个连接以服务多个服务的能力。这还有助于将您的服务与数据连接的实现细节分离。这样,如果您想在某些情况下更改连接设置之类的操作,您不必挖掘50个不同服务的详细信息,只需更改创建连接的一行代码即可。
还有一件事:如果您要使用TransactionScope,请确保在连接字符串中添加“Transaction Binding=Explicit Unbind”,否则如果事务超时,则可能会出现不一致的数据。

1
根据 MSDN 的说法,“从 .NET Framework 4 开始,对隐式解绑的更改使显式解绑变得过时。”http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.connectionstring(v=vs.100).aspx非常好的答案和示例! - xkingpin

2
如其他人所提到的,TransactionScope 是一个不错的选择。
如果你正在使用 SQL Server 2008 和 .NET 3.5,我建议修改设计,让业务对象控制事务,并将连接的打开和关闭留给数据层处理。
由于启用了连接池,实际上你并不需要承担打开物理数据库连接的开销,只有在执行实际工作时才会打开连接。因为(我假设)你使用的是 SQL Server 2008 with .NET 3.5,除非同时打开多个连接,否则你的事务不会升级为分布式事务,所以你可以得到最好的两个世界。
然后你可以像这样编写你的业务对象:
using (TransactionScope transactionScope = new TransactionScope())
{
    DataObject dataObject = new DataObject();
    dataObject.UpdateQuantity(...);

    ShippingManager shippingManager = new ShippingManager();
    shippingManager.ShipOrder(...);

    transactionScope.Complete()
}

这样可以避免将连接字符串传递给所有业务对象,并使协调事务变得容易。
更新:
System.Transactions的美妙之处在于,无论您使用哪个连接,所有事务都由它管理。您只需声明一个TransactionScope,在该TransactionScope中的所有数据库访问都将发生在单个事务中(除非您使用不同的TransactionScope设置请求)。
在过去(SQL Server 2005 .NET 2.0),如果您打开并关闭了一个连接,然后再打开并关闭另一个连接(即使使用相同的连接字符串),则事务会从轻量级事务升级为分布式事务。这是不可取的,因为性能会受到影响(与MSDTC的通信是进程外的,并且需要两阶段提交协议),而且在许多生产环境中配置MSDTC可能很麻烦(防火墙和安全性)。
在SQL Server 2008和.NET 3.5中,他们增加了在单个事务中打开和关闭具有相同连接字符串的多个连接时避免此升级的功能。有关他们所做的内容的真正好的解释,请参见Extending Lightweight Transactions in SqlClient

更新2

使用TransactionScope与Oracle 10g进行的事务将正常运行。而且看起来ODP.NET支持轻量级事务(这很好)。不幸的是,我认为在关闭和打开连接时会进行分布式事务的升级。

如果您希望避免分布式事务,可以将连接传递给每个方法调用/业务对象。如果您不想在各个方法之间传递连接,可以使用ConnectionScope类,该类可在线程上保持连接处于打开状态。另一种选择是使用Enterprise Library 3.0(及以上版本)数据访问应用程序块。数据访问块可以检测到事务正在进行,并使用相同的连接以避免分布式事务。


如果您的数据层处理打开和关闭连接,那么如何使用事务?如果您有三个BO需要执行某些操作,并且它们都在同一个事务中,那么它们不都必须使用相同的连接吗? - Dan H
我在考虑使用Enterprise Library中的数据访问块。只是我不确定是否有足够的时间重写我的应用程序的那一部分。我将不得不使用EL或将连接传递给我的每个BO。我的BO和实体是相同的,所以我可能需要将它们分开以使其更好地工作。 - Dan H

2

看起来你已经有了正确的想法。如果需要多个业务对象参与,那么其中一个需要成为“控制器”——它应该打开和关闭连接,并将其传递给其他对象。或者一些“包装器”对象可以处理连接并将其传递给每个业务对象。你的业务对象可能需要设计成既可以独立操作(处理自己的连接),又可以接受外部现有连接。


1

你可能正在寻找工作单元模式和注册表模式。这两种模式可以协同工作,将查找业务对象和跟踪它们以便稍后提交到数据存储作为事务的关注点分离。

我还建议你了解对象关系映射(ORM)。ORM是工作单元、注册表、持久性无知和其他模式的更高级别组合,提供了非常清晰的业务逻辑与持久性逻辑之间的分离。使用ORM,通常可以消除编写存储过程、构建自定义数据访问层等需求。ORM会为您处理持久性问题,使您能够专注于需要完成的业务逻辑。

由于您正在使用C#和.NET,我建议您研究Entity Framework(v4,不要使用v1)或LINQ to SQL。这两个都是OR映射器,从.NET框架v3.5及以上版本开始使用。 LINQ to SQL是一个非常简单且功能齐全的ORM,可以让您快速入门。 Entity Framework是一个更加强大的ORM,也是非常好的工具(比LINQ to SQL更好),并提供了更多的功能。还有第三方ORM可以完成此工作,包括名为NHibernate的免费ORM。虽然它没有Microsoft ORM那么好用,但NHibernate是一个非常成熟的开源ORM,拥有庞大的社区追随者。
如果ORM不可行,则应查看 Unit of Work,{{link2:Registry}}(或Repository),Persistence Ignorance,{{link3:Separation of Concerns}},{{link4:Single Responsibility}}和其他相关模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接