1

I have an WPF application that adheres command/query pattern and uses EF as an ORM.

In my mind, when new ViewModel is created, new instance of DbContext should be created and that same instance should be reused across (injected into) all command/query handlers, which are created within the scope of that particular ViewModel. At the end of ViewModel's lifetime, DbContext should be disposed.

How to achieve such a setup with Simple Injector?

2 Answers2

6

If you are applying the command/handler and query/handler patterns as described here and here, the most logical thing to do is to scope the lifetime of a DbContext around the execution of a command and query.

This can be achieved by defining a single generic decorator that allows applying scoping:

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

This decorator can be registered as last decorator as follows:

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

After doing this, you can register your DbContext using the LifetimeScopeLifestyle.

You can do the same trick with query handlers.

The great advantage of this is that you allow your force strict isolation of your command handlers and query handlers, minimizing the risk of influencing each other through a shared DbContext and it makes it easier later on to move your handlers to a different tier, by sending your command and query messages over the wire, as explained here.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Thank you Steven, yes, those articles inspired me to reorganize my architecture. I would like to call GetInstance() in some other place too and still get the same instance, for example in the WPF ValueConverter to get the current DbContext from the actual ViewModel with intention to change DataGrid's rows color based on the state of current entries (Added,Modified,Deleted, etc.). – Edward Jazzman Guoth Dec 09 '15 at 18:06
  • I think I need some kind of specific scoped lifestyle: - at the time of creating ViewModel, container should treat DbContext as Transient, but after it's created, it should behave as singleton (every call to GetInstance would get back the same instance) until the ViewModel is alive. In your example, the scope is limited to the creating some specific handlers I think, so all your decorators will get the same instance. But I need that kind of behavior also outside the scope of handlers. Is this possible? – Edward Jazzman Guoth Dec 09 '15 at 18:07
  • My advice is to approach your problem differently. If you are using commands and queries, only command and query handlers should use a DbContext. So move the operation that gets the colot into a query handler and inject the query handler into the original class. Problem solved – Steven Dec 09 '15 at 18:11
  • I managed to came with class PerScreenScopedLifestyle : ScopedLifestyle that seems to do exactly what i need: I attach a new handler to container.ExpressionBuilt event in CreateRegistrationCore method. In this event handler, I check if my base ViewModel type is assignable from e.RegisteredServiceType, and if so, I create and cache the new Scope (as a field in this class) and dispose previous one (if any). This scope will be then used by CreateCurrentScopeProvider as a part of return value (Func). – Edward Jazzman Guoth Dec 10 '15 at 20:49
  • So it will return the same Scope object every time it's requested to get that scoped instance(DbContext) by the time the ViewModel is alive. So far so good, hope no problems will arise. – Edward Jazzman Guoth Dec 10 '15 at 20:54
  • .. just said it, and a problem! The event container.ExpressionBuilt is called only for the first time of that object being created, so this is not a solution.. – Edward Jazzman Guoth Dec 10 '15 at 21:14
  • @EdwardJazzmanGuoth: That is correct, your approach won't work. Having a scope per screen is actually really difficult to achieve, that's why I'm advising to make command and query handlers your holistic abstractions. This makes everything much easier. – Steven Dec 13 '15 at 15:15
0

Finally I managed to get it working with help of container.RegisterInitializer method and custom 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;
}

and DbContext is registered as:

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

and container is configured as:

Container.Options.DefaultScopedLifestyle = new PerScreenScopedLifestyle();

How it works:

Everytime an IQueryBus is created, new Scope is created and previous Scope is disposed in the RegisterInitializer method of IQueryBus. When DbContext is requested, CreateCurrentScopeProvider returns the cached Scope containing cached DbContext. This means that DbContext will be shared for the lifetime of IQueryBus - and IQueryBus is injected into the ViewModel as Transient, so I will get always the same instance of DbContext until next new ViewModel gets injected with new IQueryBus.