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

时间:2010-04-02 00:20:25

标签: .net ado.net transactions connection

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

我在“开放晚,早关闭”的思维模式中这样做了......但是如果我需要调用其他BO来在单个事务中提交数据呢?有没有更好的方法来处理打开和关闭连接以及处理事务?

我是设计应用程序架构的新手,所以我希望我不是错误地做错了...感谢任何帮助。

5 个答案:

答案 0 :(得分:5)

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

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

    transactionScope.Complete();
}

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

有关详情,请参阅the MSDN reference page for TransactionScope

答案 1 :(得分: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添加到连接字符串,否则如果事务超时,实际上可能会出现不一致的数据

答案 2 :(得分:2)

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

答案 3 :(得分:2)

正如其他人所说,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

使用TransactionScope,Oracle 10g的事务将正常运行。它看起来像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以避免分布式事务。

答案 4 :(得分:1)

您可能正在寻找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。虽然它不如微软ORM那么好,但NHibernate是一个非常成熟的开源ORM,有大型社区跟随。

如果ORM不可能,那么我会调查Unit of WorkRegistry(或存储库),持久性无知,Separation of ConcernsSingle Responsibility以及其他相关内容图案。