2016-01-12 4 views
5

Справочная информация: У меня есть служба WCF с SimpleInjector в качестве IoC, которая создает экземпляр DbContext для запроса WCF.Как проверить, имеет ли транзакция DbContext транзакцию?

Сам бэкэнд - это CQRS. CommandHandlers много декораторов (проверка, авторизация, регистрация, некоторые общие правила для различных групп обработчиков и т.д.), и один из них является сделка декоратор:

public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> 
    where TCommand : ICommand 
{ 
    private readonly ICommandHandler<TCommand> _handler; 
    private readonly IMyDbContext _context; 
    private readonly IPrincipal _principal; 

    public TransactionCommandHandlerDecorator(ICommandHandler<TCommand> handler, 
     IMyDbContext context, IPrincipal principal) 
    { 
     _handler = handler; 
     _context = context; 
     _principal = principal; 
    } 

    void ICommandHandler<TCommand>.Handle(TCommand command) 
    { 
     using (var transaction = _context.Database.BeginTransaction()) 
     { 
      try 
      { 
       var user = _context.User.Single(x => x.LoginName == _principal.Identity.Name); 
       _handler.Handle(command); 
       _context.SaveChangesWithinExplicitTransaction(user); 
       transaction.Commit(); 
      } 
      catch (Exception ex) 
      { 
       transaction.Rollback(); 
       throw; 
      } 
     } 
    } 
} 

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

using (var transaction = _context.Database.BeginTransaction()) 

, потому что мой экземпляр DbContext уже имеет сделку.

Есть ли способ проверить существование текущей транзакции?

+2

Вам не хватает [целостной абстракции] (http://qujck.com/commands-and-queries-are-holistic-abstractions/) – qujck

+0

@qujck после прочтения этой статьи Я не понимаю, в чем разница между ' IQuery 'и' IDataQuery '. Оба они возвращают данные. Для чего нужен второй интерфейс? Есть ли примеры? – Szer

+0

Они оба возвращают данные, но эти различные абстракции важны только тогда, когда речь идет о декорировании вашего кода. Вам нужна одна абстракция, которая владеет всей операцией, абстракцией, которую можно обернуть чем-то вроде «TransactionDecorator». У вас есть другие абстракции нижнего уровня, которые могут быть украшены сквозными проблемами, такими как «AuthoriseDecorator» или «LoggingDecorator», которые не связаны с атомной операцией. – qujck

ответ

4

Вместо использования транзакции из DbContext из Entity Framework вы можете или, возможно, должны использовать класс TransactionScope, который создает область внешней транзакции и управляет транзакциями всех подключений, сделанных к базе данных SQL (SQL) под обложками.

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

Он даже может управлять подключением к различным базам данных одновременно. Для этого он использует сервис DTC. Остерегайтесь, что это боль при настройке при необходимости. Обычно, при одном соединении БД (или нескольких подключениях к одному и тому же БД) вам не потребуется код DTC.

TransactionScopeCommandHandlerDecorator реализация тривиальна:

public class TransactionScopeCommandHandlerDecorator<TCommand> 
     : ICommandHandler<TCommand> 
{ 
    private readonly ICommandHandler<TCommand> decoratee; 

    public TransactionScopeCommandHandlerDecorator(ICommandHandler<TCommand> decoratee) 
    { 
     this.decoratee = decoratee; 
    } 

    public void Handle(TCommand command) 
    { 
     using (var scope = new TransactionScope()) 
     { 
      this.decoratee.Handle(command); 

      scope.Complete(); 
     } 
    } 
} 

Но: Как qujck уже упоминалось в комментариях, вам не хватает концепции ICommandHandler как атомарные операции. Один командный манипулятор никогда не должен ссылаться на другой манипулятор. Это не только плохо для транзакций, но и учитывайте это:

Представьте, что приложение растет, и вы будете реорганизовывать некоторые из ваших обработчиков команд в фоновый поток, который будет работать в некоторых службах Windows. В этом сервисе Windows недоступен образ жизни PerWcfOperation. Теперь вам понадобится образ жизни LifeTimeScope. Потому что ваш дизайн позволяет это, что здорово кстати !, вы бы типично обернули ваши манипуляторы в LifetimeScopeCommandHandler decorator, чтобы начать LifetimeScope. В вашем текущем проекте, где один командный манипулятор ссылается на другие манипуляторы команд, вы столкнетесь с проблемой, потому что каждый манипулятор будет создан в своей собственной области. Таким образом, он получает другой DbContext, чем другие манипуляторы!

Итак, вам нужно сделать несколько редизайнов и сделать свои манипуляторы holistic abstractions и создать абстракцию более низкого уровня для выполнения операций DbContext.

+0

Правильный подход, +1. 'Он даже поместил бы прямой SqlCommand в ту же транзакцию, если бы вы использовали точную (чувствительную к регистру) строку соединения для SqlCommand', которая не гарантируется. Осторожно, это имеет тенденцию терпеть неудачу под нагрузкой. – usr

+0

@usr, ты уверен? Наверное, ты. Можете ли вы указать на некоторые документы об этом? В некоторых сценариях мы сильно полагаемся на это ... Большое спасибо за эту заметку! –

+0

@ Ric.Net большое спасибо! Я рассмотрю редизайн цепочечных команд. Но я должен сказать, что это непросто. В моем случае, когда клиент посылает команду AddEntityA, сервер должен добавить объект A в базу данных, а после этого он должен также добавить сущность B. Команда AddEntityB имеет множество правил и проверок и собственный декоратор, поэтому, если сущность B не может быть создана, сущность A также не будет добавлен в базу данных. Вот почему я выполняю команду внутри команды – Szer

10

Я думаю, что вы ищете CurrentTransaction свойство DbContext:

var transaction = db.Database.CurrentTransaction; 

Затем вы можете сделать чек так:

using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction()) 
{ 
    ... 
} 

Однако я не уверен, как вы можете знать, когда совершать транзакцию, если она используется параллельными методами.

+0

Я очень сильно склоняюсь к вашему ответу, потому что по какой-то причине у меня была версия 6.0.0 EF, которая не имеет этого свойства. После обновления пакета EF идет до 6.1.3 и свойство есть. Большое спасибо! – Szer

+0

@Szer да, это не было в EF для длинных :) –

+0

Я понимаю, что ваш ответ - это более простой «ответ» на мой вопрос, но я должен признать, что реальный ответ - «плохой дизайн», что и есть @ Ric.Net сказал. – Szer

Смежные вопросы