在多个数据库中控制事务的 C# 编程

29

假设我有一个连接到n个数据库的Windows表单应用程序,并且同时打开了n个连接。

我想要的是在一次操作中与所有这些数据库进行交互。

例如,如果我有2个数据库连接:

using (ITransaction tx1 = session1.OpenTransaction())
{
    using (ITransaction tx2 = session2.OpenTransaction())
    {
        // Do the query thingy here
    }
}

一开始写这些东西还好,但当我需要在各个地方查询时,有点冗余,更不用说可能要添加新的连接。

我想要的是循环所有注册的会话并将其包装在一个服务中,大概像这样:

class TransactionManager
{
    private ISession[] _sessions;

    public TransactionManager(string[] connectionStrings)
    {
        // Initialize the sessions here
    }

    public function DoTransaction(string query)
    {
        foreach (ISession session in _sessions)
        {
            // What to do here? Using? Try-catch?
        }
    }
}
如果我在foreach循环中使用了"using",这意味着如果连接A成功但连接B没有成功,那么只有连接B会被回滚。
2个回答

53

看起来你可能正在重新发明TransactionScope。在工作单元下完成所有这些是直截了当的*:

  using (TransactionScope scope = new TransactionScope())
  {
     ... Do Stuff with Connection 1 using SqlDataReader
     ... Do Stuff with Connection 2 using Entity Framework
     ... Do Stuff with Connection 3 on another Oracle Database
     ... And for good measure do some stuff in MSMQ or other DTC resource
     scope.Complete(); // If you are happy
  }

Stuff不需要内联 - 它可以在不同的类或不同的程序集中。没有必要使用TransactionScope显式注册数据库或队列连接 - 只要您使用的资源能够加入环境事务,一切都会自动完成

现在是小字:

  • * 任何时候,如果您同时使用多个数据库连接、不同的连接字符串或多种技术,这将需要2阶段提交并升级为分布式事务,以确保跨资源的ACID。MSDTC本身有更多细节说明,并在企业网络中提出了许多挑战,例如防火墙集群安全配置错误

  • 然而,在MS Sql Server上使用轻量级事务,如果您可以使所有连接使用相同的数据库和相同的连接字符串设置,并在打开下一个连接之前关闭每个连接,则可以避免DTC

  • 分布式事务是复杂的-您需要在应用程序服务器上安装和配置MSDTC服务,并且所有分布式资源都需要配置MSDTC或等效的(例如XA,LUA)。

  • MSDTC尚未完全支持.Net Core

  • 在多个ACID资源之间维护事务将不可避免地在这些资源上保持锁定,直到事务提交或回滚。在高容量企业中,这通常不利于邻里关系,因此请务必考虑锁定的后果。

  • 如果Stuff跨多个线程完成,则需要牵涉到DependentTransaction

  • 最后值得一提的是,在TransactionScope中的默认隔离级别为Serializable,容易出现死锁。在大多数非关键场景中,您可能能够将其降低到Read Committed


3
简直不敢相信我现在才知道TransactionScope。我这些时间都干嘛去了?? - Samuel Adam
2
此外,TransactionScope 有一个可怕的习惯,即将多个连接升级到 MSDTC,这会导致性能非常糟糕。请小心使用。 - Haney
你的 * 着重点是否也适用于给定示例,即完全不同类型的多个数据库连接? - Timo
是的。例如,只要所有数据库都按照配置进行了设置,您就可以在SqlServer、Oracle和MQSeries队列之间进行2阶段事务。当与非Windows技术堆栈(例如托管在*nix服务器上的Oracle)通信时,您可能需要在源.Net服务器上配置XA。但是,在使用ACID事务伤害企业之前,您可能需要分析分布式锁定的负面影响。还有(确实复杂的)替代方案,如补偿和BASE。 - StuartLC
我可以确认一下吗?在单个TransactionScope中,是否可以使用2个连接到不同SQL服务器上不同数据库的DbContext,并且所有操作都是原子性的? - Simon Green
是的 - 只要SQL服务器和您的应用程序服务器都启用了DTC。原子性的代价是锁定SaveChanges所涉及的行,直到作用域被提交。 - StuartLC

7
使用TransactionScope,它会处理所有包含的事务的提交或回滚:
using (var ts = new TransactionScope())
{
   ... // your old code

   ts.Complete()
}

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