Как вы обходите несколько соединений с базой данных внутри TransactionScope, если MSDTC отключен?

У меня есть веб-приложение, которое выдает запросы к 3 базам данных в DAL. Я пишу некоторые интеграционные тесты, чтобы убедиться, что общая функциональность в оба конца фактически делает то, что я ожидаю от нее. Это полностью отличается от моих модульных тестов, просто fyi.

То, как я собирался писать эти тесты, было чем-то вроде этого

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

Ведущий в этом случае уже настроен. Проблема возникает в процессе ProcessWorkflow, потому что она вызывает различные репозитории, которые, в свою очередь, обращаются к различным базам данных, а в моем ядре сервера sql не включен MSDTC, поэтому я получаю сообщение об ошибке, когда я пытаюсь либо создать новое соединение sql, либо попробовать для изменения базы данных кэшированного подключения для таргетинга на другую.

Для краткости презентатор напоминает что-то вроде:

public void ProcessWorkflow()
{
    LogRepository.LogSomethingInLogDatabase();
    var l_results = ProcessRepository.DoSomeWorkOnProcessDatabase();
    ResultsRepository.IssueResultstoResultsDatabase(l_results);
}

Я попытался найти множество вещей, чтобы решить эту проблему.

  • Кэширование одного активного соединения в любое время и изменение целевой базы данных
  • Кэширование одного активного соединения для каждой целевой базы данных (это было бесполезно, потому что объединение должно было сделать это для меня, но я хотел посмотреть, получились ли у меня разные результаты)
  • Добавление дополнительных TransactionScopes внутри каждого репозитория, чтобы они имели свои собственные транзакции, используя TransactionScopeOption "RequiresNew"

Моя третья попытка в списке выглядит примерно так:

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

        transaction.Complete();
    }
}

И на самом деле 3-я вещь, которую я пробовал, фактически заставила модульные тесты работать, но все транзакции, которые завершили фактически HIT мою базу данных! Так что это был полный провал, так как все дело в том, чтобы НЕ воздействовать на мою базу данных.

Таким образом, мой вопрос заключается в том, какие другие варианты существуют для выполнения того, что я пытаюсь сделать, учитывая ограничения, которые я изложил?

EDIT:

Это то, что "//делает работу с базой данных" будет выглядеть как

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();
       }
   }
}

Ответ 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();
    }
}

Я бы не рекомендовал использовать это в производственном коде, но для тестов интеграции я считаю, что это уместно. Также имейте в виду, что это работает только для соединений, где сервер всегда один и тот же, а также пользователь.

Я надеюсь, что это поможет кому-то другому, кто сталкивается с той же проблемой, что и я.

Ответ 2

  • Задайте Enlist = false в строке подключения, чтобы избежать автоматического подключения к транзакции.

  • Вручную введите соединение в качестве участников в области транзакций. (http://msdn.microsoft.com/en-us/library/ms172153%28v=VS.80%29.aspx)

Ответ 3

Если вы не хотите использовать MSDTC, вы можете напрямую использовать транзакции SQL.

См. SqlConnection.BeginTransaction().