【问题标题】:Opening and closing a database connection inside a transaction在事务中打开和关闭数据库连接
【发布时间】:2011-02-03 14:23:46
【问题描述】:

我设计了我们框架的数据访问部分,以便每次业务对象 (BO) 需要与数据库交互时,它都必须打开一个连接,调用数据访问层(以执行查询),然后然后关闭连接。然后如果它需要在事务中运行,它会打开连接,开始事务,调用数据访问层(执行查询),然后提交事务,关闭事务,最后关闭连接。

我是本着“迟开早关”的心态这样做的……但是如果我需要调用其他 BO 来在单个事务中提交数据怎么办?有没有更好的方法来处理打开和关闭连接以及处理事务?

我是设计应用程序架构的新手,所以我希望我没有做错这件事……感谢任何帮助。

【问题讨论】:

    标签: .net ado.net transactions connection


    【解决方案1】:

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

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

    如果任何对象抛出异常,它将回滚事务。

    请参阅the MSDN reference page for TransactionScope 了解更多信息。

    【讨论】:

    • 虽然TransactionScope 类在此处使用,但在 OP 的当前设计下,如果 MSDTC 被禁用或锁定,它将导致分布式事务或可能出现异常。这可能不是我们想要的结果。
    • @Aaronaught,对于 SQL Server 2008 和 .NET 3.5,事务将不会被提升为分布式事务(对于当前设计)。
    • @Tuzo:没错,使用 SQL Server 2008,您可以解决多连接问题 IF 所有连接都指向同一个数据库 并且 同一时间只有一个开放。这仍然是一个非常值得怀疑的做法,尤其是当替代设计更容易构建/维护时。
    • 我不太明白这个代码示例中发生了什么。 IDbConnection.CloseClose 方法回滚所有待处理的事务。” OTOH,SqlConnection.Close,它实现了该接口方法:“通过 System.Transactions 启动的事务通过System.Transactions 基础架构,不受SqlConnection.Close 影响。” 那么TransactionScope 仅适用于SQL Server?
    • 顺便说一句。关于代码示例:为什么要多次调用childObj.Save()?这些是否应该是对不同对象的调用(childObjAchildObjB,...)?
    【解决方案2】:

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

    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 添加到连接字符串中,否则如果事务超时,实际上可能会导致数据不一致。

    【讨论】:

    • 根据 msdn,“从 .NET Framework 版本 4 开始,对隐式取消绑定的更改会使显式取消绑定过时。” msdn.microsoft.com/en-us/library/…很好的答案和例子!
    【解决方案3】:

    正如其他人所说,TransactionScope 是要走的路。

    如果您使用的是 SQL Server 2008 和 .NET 3.5,我会修改设计以让业务对象控制事务并将连接的打开和关闭留给数据层。

    启用连接池后,您实际上不会产生打开物理数据库连接的开销,并且您的连接只会在执行实际工作时打开。因为(我假设)你有SQL Server 2008 with .NET 3.5 your transaction will not escalate to a distributed transaction(除非你同时打开多个连接)所以你可以两全其美。

    然后你可以这样写你的业务对象:

    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

    Oracle 10g 的事务将在 TransactionScope 中正常运行。它看起来像ODP.NET supports Lightweight Transactions(这很好)。不幸的是,我认为对分布式事务的提升将随着连接的关闭和打开而发生。

    如果您希望避免分布式事务,您可以将连接传递给每个方法调用/业务对象。如果您不想传递连接,您可以使用ConnectionScope class 来保持线程上的连接打开。另一种方法是使用 Enterprise Library 3.0(及更高版本)数据访问应用程序块。 Data Access Block can detect that a transaction is in progress and use the same connection 避免分布式事务。

    【讨论】:

    • 如果你有数据层处理打开和关闭连接,你如何使用事务?如果您有 3 个 BO 需要做某事,并且他们都在同一个事务中,他们不是都必须使用相同的连接吗?
    • 我在考虑使用企业库中的数据访问块。我只是不确定我是否有足够的时间来重写我的应用程序的那部分。我将不得不使用 EL 或将连接传递给我的每个 BO。我的 BO 和实体是相同的之一,因此我可能需要将它们分开以使这项工作更好。
    【解决方案4】:

    听起来你的想法是对的。如果需要涉及多个 BO,则其中一个需要成为“控制器”——它应该打开和关闭连接,并将其传递给其他人。或者一些“包装器”对象可以处理连接并将其传递给每个 BO。您的 BO 可能需要设计为既能独立运行(处理自己的连接),又能接受来自外部的现有连接。

    【讨论】:

      【解决方案5】:

      您可能正在寻找Unit of Work 模式和Registry 模式。这两种模式可以协同工作,以分离查找业务对象和跟踪它们以供以后作为事务提交到您的数据存储的关注点。

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

      由于您使用的是 C# 和 .NET,我会研究实体框架(v4,不要使用 v1)或 LINQ to SQL。两者都是从 v3.5 起随 .NET 框架一起提供的 OR 映射器。 LINQ to SQL 是一个非常简单且工具齐全的 ORM,它可以让您快速上手。实体框架是一个更丰富的 ORM,它的工具也非常好(比 LINQ to SQL 更好),并提供了更多的功能。还有第三方 ORM 可以完成这项工作,包括一个名为 NHibernate 的免费 ORM。虽然它的工具不如 Microsoft ORM,但 NHibernate 是一个非常成熟的开源 ORM,拥有大量社区。​​p>

      如果不可能使用 ORM,那么我会研究 Unit of WorkRegistry(或 Repository)、Persistence Ignorance、Separation of ConcernsSingle Responsibility 和其他相关模式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-12-31
        • 2017-02-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多