Как обойти несколько соединений с базой данных внутри TransactionScope, если MSDTC отключен? - PullRequest
6 голосов
/ 17 апреля 2009

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

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

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

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

  1. Кэширование одного активного соединения постоянно и изменение целевой базы данных
  2. Кэширование одного активного соединения для каждой целевой базы данных (это было бесполезно, потому что пул должен был сделать это для меня, но я хотел посмотреть, получаю ли я другие результаты)
  3. Добавление дополнительных TransactionScopes внутри каждого репозитория, чтобы у них были свои собственные транзакции, используя TransactionScopeOption «requireNew»

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

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

        transaction.Complete();
    }
}

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

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

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

Ответы [ 3 ]

1 голос
/ 21 апреля 2009

Хорошо, я нашел способ обойти эту проблему. Единственная причина, по которой я так поступаю, заключается в том, что я не смог найти ЛЮБОГО другого способа решения этой проблемы, и потому что это в моих интеграционных тестах, поэтому я не беспокоюсь о том, что это окажет неблагоприятное влияние на производственный код.

Мне пришлось добавить свойство в мой DataContext, чтобы оно действовало как флаг, чтобы отслеживать, нужно ли удалять объект соединения, когда мой DataContext удаляется. Таким образом, соединение сохраняется во всей области транзакции и, следовательно, больше не беспокоит DTC

Вот образец моего нового распоряжения:

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

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

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

1 голос
/ 04 августа 2010
  1. Установите Enlist = false в строке подключения, чтобы избежать автоматического включения в транзакцию.

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

0 голосов
/ 17 апреля 2009

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

См. SqlConnection.BeginTransaction().

...