我有一个WPF应用程序,它遵循命令/查询模式并使用EF作为ORM。
在我看来,当创建新的ViewModel时,应该创建新的DbContext实例,并且应该在所有命令/查询处理程序中重复使用相同的实例(注入),这些处理程序是在特定ViewModel的范围内创建的。在ViewModel的生命周期结束时,应该处理DbContext。
如何使用Simple Injector实现这样的设置?
答案 0 :(得分: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所述。
答案 1 :(得分: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将在IQueryBus的RegisterInitializer方法中处理。当请求DbContext时,CreateCurrentScopeProvider返回包含缓存的DbContext的缓存的Scope。这意味着DbContext将在IQueryBus的生命周期内共享 - 并且IQueryBus作为Transient注入到ViewModel中,因此我将始终获得相同的DbContext实例,直到下一个新的ViewModel被注入新的IQueryBus。