2015-12-09 2 views
1

У меня есть приложение WPF, которое придерживается шаблона команды/запроса и использует EF как ORM.DbContext Lifestyle для экрана/ViewModel (WPF + Simple Injector)

На мой взгляд, при создании нового ViewModel должен быть создан новый экземпляр DbContext, и этот же экземпляр должен быть повторно использован для всех (проиндексированных) всех обработчиков команд/запросов, которые создаются в рамках этой конкретной ViewModel. В конце жизненного цикла ViewModel должен быть удален DbContext.

Как достичь такой установки с помощью простого инжектора?

ответ

6

Если вы подаете команду/обработчик и шаблоны запросов/обработчиков, как описано here и here, наиболее логичным, что нужно сделать, чтобы объем время жизни DbContext вокруг выполнения команды и запроса.

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

using SimpleInjector; 
using SimpleInjector.Extensions.LifetimeScoping; 

public class LifetimeScopedCommandHandlerDecorator<T> : ICommandHandler<T> 
{ 
    private readonly Container container; 
    private readonly Func<ICommandHandler<T>> decorateeProvider; 
    public LifetimeScopedCommandHandlerDecorator(Container container, 
     Func<ICommandHandler<T>> decorateeProvider) { 
     this.container = container; 
     this.decorateeProvider = decorateeProvider; 
    } 

    public void Handle(T command) { 
     using (container.BeginLifetimeScope()) { 
      this.decorateeProvider().Handle(command); 
     } 
    } 
} 

Этот декоратор может быть зарегистрирован в качестве последнего декоратора следующим образом:

container.RegisterDecorator(typeof(ICommandHandler<>), 
    typeof(LifetimeScopedCommandHandlerDecorator<>), 
    Lifestyle.Singleton); 

После этого, вы можете зарегистрируйте свой DbContext, используя LifetimeScopeLifestyle.

Вы можете сделать тот же трюк с обработчиками запросов.

Огромное преимущество этого заключается в том, что вы позволяете вашей силе строго изолировать обработчики команд и обработчики запросов, сводя к минимуму риск влияния друг на друга через общий DbContext, и это облегчает перенос ваших обработчиков на другой уровня, отправив команду и запросить сообщения по проводу, как объяснено here.

+0

Спасибо Стивен, да, эти статьи вдохновили меня на реорганизацию моей архитектуры. Я хотел бы также вызвать GetInstance () в другом месте и все равно получить тот же экземпляр, например, в WPF ValueConverter, чтобы получить текущий DbContext из реального ViewModel с намерением изменить цвет строк DataGrid на основе состояния текущих записей (Добавлено, Модифицировано, Удалено и т. Д.). –

+0

Я думаю, мне нужен какой-то определенный образ жизни: во время создания ViewModel контейнер должен рассматривать DbContext как Transient, но после его создания он должен вести себя как singleton (каждый вызов GetInstance будет возвращать один и тот же экземпляр) до тех пор, пока ViewModel жив. В вашем примере область ограничена созданием определенных обработчиков, я думаю, поэтому все ваши декораторы получат один и тот же экземпляр. Но мне нужно, чтобы такое поведение было также недоступным для обработчиков. Это возможно? –

+0

Мой совет - подойти к вашей проблеме по-другому. Если вы используете команды и запросы, только обработчики команд и запросов должны использовать DbContext. Поэтому переместите операцию, которая получает цвет в обработчик запроса, и вставьте обработчик запроса в исходный класс. Проблема решена – Steven

0

Наконец мне удалось получить его работу с помощью метода и пользовательского container.RegisterInitializer ScopedLifeStyle:

public class PerScreenScopedLifestyle : ScopedLifestyle 
{ 
    public PerScreenScopedLifestyle() 
      : this(disposeInstanceWhenScopeEnds: true) 
    { 
    } 

    public PerScreenScopedLifestyle(bool disposeInstanceWhenScopeEnds) : base("Per Screen Scope", disposeInstanceWhenScopeEnds) 
    { 
    } 

    protected override Registration CreateRegistrationCore<TService, TImplementation>(Container container) 
    { 
     // any time a IQueryBus is requested, new scope should be created.. 
     container.RegisterInitializer<IQueryBus>(QueryBusInitializer); 

     return base.CreateRegistrationCore<TService, TImplementation>(container); 
    } 

    protected override Registration CreateRegistrationCore<TService>(Func<TService> instanceCreator, Container container) 
    { 
     // any time a IQueryBus is requested, new scope should be created.. 
     container.RegisterInitializer<IQueryBus>(QueryBusInitializer); 

     return base.CreateRegistrationCore<TService>(instanceCreator, container); 
    } 

    void QueryBusInitializer(IQueryBus obj) 
    { 
     // any scope used before must be disposed 
     if (scope != null) 
     { 
      scope.Dispose(); 
     } 

     // create new scope 
     scope = new Scope(); 
    } 

    protected override Scope GetCurrentScopeCore(Container container) 
    { 
     return scope; 
    } 

    protected override Func<Scope> CreateCurrentScopeProvider(Container container) 
    { 
     return() => 
     { 
      var result = scope == null ? new Scope() : scope; 
      return result; 
     }; 
    } 

    Scope scope; 
} 

и DbContext зарегистрирован как:

Container.Register<DbContext, MyDbContext>(Lifestyle.Scoped); 

и контейнер выполнен в виде:

Container.Options.DefaultScopedLifestyle = new PerScreenScopedLifestyle(); 

Как это работает:

Каждый раз, когда создается IQueryBus, создается новый Scope и предыдущий Scope располагается в методе RegisterInitializer IQueryBus. Когда запрашивается DbContext, CreateCurrentScopeProvider возвращает кэшированную область, содержащую кэшированный DbContext. Это означает, что DbContext будет совместно использоваться для жизни IQueryBus - и IQueryBus вводится в ViewModel как Transient, поэтому я получаю всегда тот же экземпляр DbContext, пока следующий новый ViewModel не будет введен в новый IQueryBus.