如果禁用MSDTC,如何绕过TransactionScope内的多个数据库连接?

Jos*_*eph 6 c# sql-server transactions asp.net-2.0

我有一个Web应用程序,它向DAL中的3个数据库发出请求.我正在编写一些集成测试,以确保整个功能往返实际上完成了我期望它做的事情.这与我的单元测试完全分开,只是fyi.

我打算写这些测试的方式是这样的

[Test]
public void WorkflowExampleTest()
{
    (using var transaction = new TransactionScope())
    {
        Presenter.ProcessWorkflow();
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,Presenter已经建立.问题在ProcessWorkflow方法中发挥作用,因为它调用各种存储库,而这些存储库又访问不同的数据库,而我的sql server框没有启用MSDTC,所以每当我尝试创建新的sql连接时,我都会收到错误,或者尝试更改缓存连接的数据库以定位另一个数据库.

为简洁起见,Presenter类似于:

public void ProcessWorkflow()
{
    LogRepository.LogSomethingInLogDatabase();
    var l_results = ProcessRepository.DoSomeWorkOnProcessDatabase();
    ResultsRepository.IssueResultstoResultsDatabase(l_results);
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试了很多东西来解决这个问题.

  1. 始终缓存一个活动连接并更改目标数据库
  2. 为每个目标数据库缓存一个活动连接(这有点无用,因为池应该为我做这个,但我想看看我是否得到不同的结果)
  3. 在每个存储库中添加其他TransactionScope,以便使用TransactionScopeOption"RequiresNew"拥有自己的事务

我在列表上的第三次尝试看起来像这样:

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

        transaction.Complete();
    }
}
Run Code Online (Sandbox Code Playgroud)

实际上我尝试的第三件事实际上是让单元测试工作,但所有完成的事务实际上都是我的数据库!所以这是一个彻头彻尾的失败,因为整个观点是不影响我的数据库.

因此,我的问题是,鉴于我已经制定的限制,还有哪些其他选择可以实现我想要做的事情?

编辑:

这就是"//做一些数据库工作"的样子

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

并且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();
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

Jos*_*eph 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();
   }
}
Run Code Online (Sandbox Code Playgroud)

这允许我的集成测试采用以下形式:

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

        Presenter.ProcessWorkflow();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我希望这可以帮助其他遇到与我相同问题的人。