DbContext + ObjectContext в TransactionScope вызывают исключение MDTC

У меня есть старый ObjectContext и несколько новых DbContext в моем проекте (т.е. BoundedContext для разных целей). Некоторое время мне нужно зафиксировать изменения из нескольких из них в одной транзакции. В некоторых случаях мне нужно сохранить данные из ObjectContext и DbContext. В EF 5.0, чтобы избежать MSDC, я пишу обертку

public class ContextUnitOfWork
{
    List<IContext> ContextList;
    public ContextUnitOfWork()
    {
        ContextList = new List<IContext>(); 
    }
    public void RegisterContext(IContext Context)
    {
        ContextList.Add(Context);
    }
    public bool IsDisposed
    {
        get
        {
            return ContextList.Any(x => x.IsDisposed);
        }
    }
    public bool HasChangedEntities
    {
        get
        {
            return ContextList.Any(x => x.HasChangedEntities);
        }
    }
    public void Commit()
    {
        bool HasDbContext = ContextList.OfType<System.Data.Entity.DbContext>().Any();
        try
        {
            if (HasDbContext)
            {
                ContextList.ForEach(x =>
                {

                    if (x is System.Data.Entity.DbContext)
                    {
                        (x as System.Data.Entity.DbContext).Database.Connection.Open();
                    }
                    else if (x is System.Data.Objects.ObjectContext)
                    {
                        ((System.Data.Objects.ObjectContext)x).Connection.Open();
                    }

                });
            }

            using (var scope = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Required,
    new System.Transactions.TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
            {
                ContextList.ForEach(x => x.Commit());
                scope.Complete();
            }
        }
        catch (System.Data.UpdateException uex)
        {
            var ErrorList = uex.StateEntries.Select(x => x.Entity).ToList(); 
        }
        finally
        {
            if (HasDbContext)
            {
                ContextList.ForEach(x =>
                {
                    if (x is System.Data.Entity.DbContext)
                    {
                        (x as System.Data.Entity.DbContext).Database.Connection.Close();
                    }
                    else if (x is System.Data.Objects.ObjectContext)
                    {
                        ((System.Data.Objects.ObjectContext)x).Connection.Close();
                    }
                });
            };

        }
    }
}

Но в EntityFramework 6.0.1 это не работает. ObjectContext успешно фиксируется, но когда DbContext вызывает SaveChanges(), возникает исключение типа EntityException с текстом "Базовый поставщик не прошел EnlistTransaction". И Inner Expection содержит {"Сетевой доступ для диспетчера распределенных транзакций (MSDTC) отключен. Включите DTC для сетевого доступа в конфигурации безопасности для MSDTC с помощью инструмента администрирования служб компонентов."}

Любая идея зафиксировать контексты в одной транзакции и избежать исключения MDTC?


person GraySerg    schedule 23.10.2013    source источник
comment
Внутреннее исключение на английском языке Доступ к сети для диспетчера распределенных транзакций (MSDTC) отключен. Включите DTC для доступа к сети в конфигурации безопасности для MSDTC с помощью инструмента администрирования служб компонентов. Можешь поправить)   -  person shibormot    schedule 23.10.2013


Ответы (1)


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

Мой опыт работы с EF заключается в том, что он повторно использует текущее соединение только тогда, когда строка соединения (обычная внутри строки entityconnectionstring) ТОЧНО идентична. Если есть хоть одно отличие, транзакция будет преобразована в распределенную транзакцию, которая должна быть разрешена системой, а в вашем случае это не так.

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

Можете ли вы проверить, идентичны ли строки подключения? Я все равно был бы удивлен, если бы текущее соединение использовалось повторно.

person Maarten    schedule 23.10.2013
comment
Строки подключения абсолютно идентичны. Это выглядит так: Источник данных = MyDataBase; Исходный каталог = MyDataBase; Интегрированная безопасность = True; Объединение в пул = True; Имя приложения = MyApp. - person GraySerg; 23.10.2013
comment
Если не открыть соединение вручную и если строки подключения АБСОЛЮТНО ИДЕНТИЧНЫ, это работает. Спасибо, Мартен. - person GraySerg; 23.10.2013
comment
Если вы открываете соединения вручную и откладываете закрытие соединений до фиксации, тогда действительно требуется несколько открытых соединений, а для этого требуется распределенная транзакция. Локальная транзакция не может справиться с этим. - person Maarten; 23.10.2013