如果禁用了MSDTC,如何在TransactionScope中处理多个数据库连接?

6
我有一个Web应用程序,它向DAL中的3个数据库发出请求。我正在编写一些集成测试,以确保整体功能往返实际上做我期望它做的事情。这与我的单元测试完全分开,只是提供信息。
我打算编写这些测试的方式是这样的:
[Test]
public void WorkflowExampleTest()
{
    (using var transaction = new TransactionScope())
    {
        Presenter.ProcessWorkflow();
    }
}

在这种情况下,Presenter已经被设置好了。问题出现在ProcessWorkflow方法中,因为它调用各种仓库,而这些仓库又访问不同的数据库。我的SQL服务器框没有启用MSDTC,所以每当我尝试创建新的SQL连接或尝试更改缓存连接的数据库以针对不同的数据库时,就会出现错误。
为了简洁起见,Presenter类似于:
public void ProcessWorkflow()
{
    LogRepository.LogSomethingInLogDatabase();
    var l_results = ProcessRepository.DoSomeWorkOnProcessDatabase();
    ResultsRepository.IssueResultstoResultsDatabase(l_results);
}

我尝试了很多方法来解决这个问题。

  1. 始终缓存一个活动连接并更改目标数据库
  2. 为每个目标数据库缓存一个活动连接(这有点无用,因为池化应该替我完成此操作,但我想看看是否会得到不同的结果)
  3. 在每个存储库内添加其他TransactionScopes,以便它们拥有自己的事务,使用TransactionScopeOption“RequiresNew”

我列表中的第三种尝试看起来像这样:

public void LogSomethingInLogDatabase()
{
    using (var transaction = 
        new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        //do some database work

        transaction.Complete();
    }
}

实际上,我尝试的第三件事确实让单元测试正常工作了,但是所有完成的交易实际上都影响了我的数据库!因此这是一个彻底的失败,因为整个重点在于不影响我的数据库。

因此,我的问题是,在我提出的限制条件下,还有什么其他选项可以实现我想要做的事情?

编辑:

"//执行一些数据库操作"看起来就像这样

using (var l_context = new DataContext(TargetDatabaseEnum.SomeDatabase))
{
    //use a SqlCommand here
    //use a SqlDataAdapter inside the SqlCommand
    //etc.
}

而 DataContext 本身大致如下所示。
public class DataContext : IDisposable
{
   static int References { get; set; }
   static SqlConnection Connection { get; set; }

   TargetDatabaseEnum OriginalDatabase { get; set; }

   public DataContext(TargetDatabaseEnum database)
   {
       if (Connection == null)
          Connection = new SqlConnection();

       if (Connection.Database != DatabaseInfo.GetDatabaseName(database))
       {
           OriginalDatabase = 
               DatabaseInfo.GetDatabaseEnum(Connection.Database);

           Connection.ChangeDatabase(
               DatabaseInfo.GetDatabaseName(database));
       }           

       if (Connection.State == ConnectionState.Closed)
       {
           Connection.Open() //<- ERROR HAPPENS HERE
       }    

       ConnectionReferences++;                 
   }

   public void Dispose()
   {
       if (Connection.State == ConnectionState.Open)
       {
           Connection.ChangeDatabase(
               DatabaseInfo.GetDatabaseName(OriginalDatabase));
       }

       if (Connection != null && --ConnectionReferences <= 0)
       {
           if (Connection.State == ConnectionState.Open)
               Connection.Close();
           Connection.Dispose();
       }
   }
}

我不确定我理解了。你说的“工作单元”是什么意思? - Joseph
抱歉,我们在谈论多个数据库服务器还是对单个服务器的多个连接? - meandmycode
在同一服务器上使用多个数据库还是在不同的服务器上使用多个数据库?如果它们在同一台服务器上,则不需要 MSDTC。 - SQLMenace
我实际上尝试了通过创建自己的“DataContext”对象来解决这个问题。它基本上只持有一个 SqlConnection 对象并在请求时进行重复利用并更改目标数据库。然而,即使采取了这些措施,我仍然会遇到 MSDTC 错误,而且我不知道原因所在。如果你想看看我的 DataContext,我可以发布一些样例代码。 - Joseph
我做了一些阅读,我认为我对你所说的“工作单元”有了更好的理解。问题在于,在给定的“工作单元”中,我进行了日志记录,有时是针对不同的数据库(也使用了不同的存储库),这基本上就是导致这个问题的根本原因。 - Joseph
显示剩余2条评论
3个回答

2

1

好的,我找到了解决这个问题的方法。我之所以这样做是因为我找不到任何其他解决这个问题的方法,并且因为它在我的集成测试中,所以我不担心这会对生产代码产生不良影响。

我不得不在我的DataContext中添加一个属性来作为标志,以跟踪在我的DataContext被处理时是否要处理连接对象。这样,连接在整个事务范围内保持活动状态,因此不再干扰DTC。

以下是我新Dispose的示例:

internal static bool SupressConnectionDispose { get; set; }

public void Dispose()
{
   if (Connection.State == ConnectionState.Open)
   {
       Connection.ChangeDatabase(
           DatabaseInfo.GetDatabaseName(OriginalDatabase));
   }

   if (Connection != null 
       && --ConnectionReferences <= 0 
       && !SuppressConnectionDispose)
   {
       if (Connection.State == ConnectionState.Open)
           Connection.Close();
       Connection.Dispose();
   }
}

这使得我的集成测试可以采用以下形式:

[Test]
public void WorkflowExampleTest()
{
    (using var transaction = new TransactionScope())
    {
        DataContext.SuppressConnectionDispose = true;

        Presenter.ProcessWorkflow();
    }
}

我不建议在生产代码中使用这个,但对于集成测试来说,我认为它是合适的。另外请记住,这仅适用于服务器和用户始终相同的连接。

希望这能帮助其他遇到同样问题的人。


0
如果您不想使用 MSDTC,可以直接使用 SQL 事务。
请参阅 SqlConnection.BeginTransaction()

在集成测试的情况下,这将如何工作?如果我在存储库级别使用BeginTransaction和Commit或Rollback,当我将整个事务范围包装在其中时会发生什么? - Joseph

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