简体   繁体   中英

MVVM + Services + Entity Framework and Dependency Injection vs Service Locator

I have many systems that use WPF with MVVM. For unit testing we inject dependencies into the View Models, however I have found that when injecting the dependent class at construction time we cannot control the lifetime of the dependent object such as an Entity Framework DbContext.

A simple scenario is a follows:

public class FooVM
{
    private readonly IBarService _barService;

    // Set in the UI via Databinding
    public string Name { get; set; }
    public string OtherName { get; set; }

    public FooVM(IBarService barService)
    {
        _barService = barService;
    }

    public void SaveFoo()
    {
        _barService.SaveFoo(Name);
    }

    public void SaveBar()
    {
        _barService.SaveBar(OtherName);
    }
}

public class BarService : IBarService
{
    private readonly IEntityContext _entityContext;

    public BarService(IEntityContext entityContext)
    {
        _entityContext = entityContext;
    }

    public void SaveFoo(string name)
    {
        // some EF stuff here
        _entityContext.SaveChanges();
    }

    public void SaveBar(string otherName)
    {
        // some EF stuff here
        _entityContext.SaveChanges();
    }
}

The VM needs to use the service so has it injected, the service needs an IEntityContext and thus has that injected. The problem comes when in the VM we call SaveFoo and SaveBar , as the _entityContext object is dirty after a single call. Ideally we want to dispose of the _entityContext object after each call.

The only way I've found round this is to use Dependency Injection to inject the container which then calls the code as follows:

public class FooVM
{
    private readonly IInjector _injector;

    // Set in the UI via Databinding
    public string Name { get; set; }
    public string OtherName { get; set; }

    public FooVM(IInjector injector)
    {
        _injector = injector;
    }

    public void SaveFoo()
    {
        var barService = _injector.GetUniqueInstance<IBarService>();
        barService.SaveFoo(Name);
    }

    public void SaveBar()
    {
        var barService = _injector.GetUniqueInstance<IBarService>();
        barService.SaveBar(OtherName);
    }
}

In this way the container ( IInjector ) is acting like a service locator which works great, except is clunky for unit testing. Is there a better way to manage this? I understand that doing this pretty much voids all the benefits of Dependency Injection, but I can't figure another way.

EDIT: Further Example

Say you have a window with two buttons. One service sits behind it which has been injected via dependency injection. You click button A and it loads an object, modifies it, and saves, however this fails (for some reason, lets say some validation fails in the DbContext), you show a nice message.

Now you click button 2. It loads a different object and modifies it and tries to save, now because the first button was pressed, and the service is the same service, with the same context, this operation will fail for the same reason as when clicking button A.

My company does the same thing as you are asking, and we solve it by using the Repository and UnitOfWorkFactory patterns.

A simpler version of this would look something like so:

public class BarService : IBarService
{
    private readonly IEntityContextFactory _entityContextFactory;

    public BarService(IEntityContextFactory entityContextFactory)
    {
        _entityContextFactory = entityContextFactory;
    }

    public void SaveFoo(string name)
    {
        using (IEntityContext entityContext = _entityContextFactory.CreateEntityContext())
        {
            // some EF stuff here
            entityContext.SaveChanges();
        }
    }

    public void SaveBar(string otherName)
    {
        using (IEntityContext entityContext = _entityContextFactory.CreateEntityContext())
        {
            // some EF stuff here
            _entityContext.SaveChanges();
        }
    }
}

And the factory:

public class EntityContextFactory : IEntityContextFactory
{
    private readonly Uri _someEndpoint = new Uri("http://somwhere.com");

    public IEntityContext CreateEntityContext()
    {
        // Code that creates the context.
        // If it's complex, pull it from your bootstrap or wherever else you've got it right now.
        return new EntityContext(_someEndpoint);
    }
}

Your IEntityContext needs to implement IDisposable for the "using" keyword to work here, but this should be the gist of what you need.

As also pointed out by @ValentinP, I also believe you are going down the wrong path but for a different reason.

If you do not want to pollute the state tracking in the DbContext instance for your persistence methods with the objects that have already been retrieved during database queries then you need a redesign your application and split your business logic into 2 logical tiers. One tier for retrieval and one for persistence, each tier would use its own instance of a DbContext , this way you never have to worry about objects that were retrieved and manipulated accidentally being persisted back with another operation ( I assume this is why you asked the question ).

This is widely accepted pattern called Command Query Responsibility Segregation or CQRS for short. See this CQRS article by Martin Fowler on the pattern or this Microsoft article with code samples.

Using this pattern you can dispose of the DbContext instances (directly or indirectly via the Dispose of the root owning object).

Edit based on latest edit

That scenario clears up a lot of questions on what you are trying to accomplish.

  1. I stand by the option of implementing CQRS as I still believe it is applicable.
  2. It is a common approach to not use long lived DbContext instances in applications. Create one when you need it and then dispose of it when you are done with it. There is minimal overhead in the creation/disposal of a DbContext object itself. You should then re-attach any modified models / collections to the new DbContext where you are looking to persist the changes, there is no reason to re-retrieve them from the underlying store. If a failure then occurs the entry point to that part of the code (either in the service layer or the presentation layer) should handle the error (display message, revert changes, etc). Concurrency exceptions (using TimeStamp/Rowversion) are also handled correctly using this approach. Also because you used a new DbContext you do not have to worry about other commands that could also be carried out on the same view failing if they try to execute something independent.

You should be able to specify the life time scope of each object you are injecting. For your IEntityContext you could specify Transient (which is the default) and inject it into the appropriate service layer constructor. Each instance of IEntityContext should have exactly one owner / root. If you use a CQRS pattern this becomes a little easier to manage. If you are using something like a DDD pattern it becomes a little more convoluted but still doable. Alternatively you could also specify life time scope at Thread level although I would not recommend this as it could introduce a lot of unexpected side effects if you ever forget this and try to add some parallel programming or using the async/await pattern without recapturing the original thread context.

My recomendation from the bottom of my heart, leverage your design on a lifetime aware IoC container like Autofac.

Take a look of this to know how to control the lifetime even with IoC: http://autofac.readthedocs.org/en/latest/lifetime/instance-scope.html

If you need more details about how to implement this for your purpose, complain with me here.

Which DI framework are you using? With Autofac you have something called LifeTimeScope. Probably other frameworks have similar functionality.

http://docs.autofac.org/en/latest/lifetime/index.html

Basically, you'd need to identify what's the Unit of Work on your application (Each ViewModel instance? Each ViewModel action? ), and have a new LifeTimeScope for each UoW, and resolve your dependencies with the lifetime scope. Depending on your implementation it might end up looking more like a service locator, but it makes managing the lifetime of the dependencies relatively easy. (If you register the DBContext as PerLifeTimeScope, you can be sure that all the dependencies resolved in the same lifetime scope will share the same dbcontext,and that it will not be shared for dependencies resolved with another lifetimescope).

Plus, since the lifetimescopes implements an interface, it can be easily mocked to resolved mock services for unit test purposes.

You should use factory to create db context for every time. If you want to use Autofac , It has already auto generated factory for this. You can use Dynamic Instantiation to create dbcontext every time. You can use Controlled Lifetime to manage lifetime of dbcontext for yourself. If you combine both, you will have dbcontext everytime and you will manage life time in method (Dispose it yourself).

While you are testing you will just registered mocked instance of IEntityContext .

public class BarService : IBarService
    {
        private readonly Func<Owned<IEntityContext>> _entityContext;

        public BarService(Func<Owned<IEntityContext>> entityContext)
        {
            _entityContext = entityContext;
        }

        public void SaveFoo(string name)
        {
            using (var context = _entityContext())
            {
                context.SaveChanges();
            }
        }

        public void SaveBar(string otherName)
        {
            using (var context = _entityContext())
            {
                context.SaveChanges();
            }
        }
    }

If you want to manage all your dbcontexts life time, we can remove Owned and we can register your context ExternallyOwned . That means autofac will not handle lifetime of this object.

builder.RegisterType<EntityContext>().As<IEntityContext>().ExternallyOwned();

Then your field and constructor should be like this:

private readonly Func<IEntityContext> _entityContext;

            public BarService(Func<IEntityContext> entityContext)
            {
                _entityContext = entityContext;
            }
  1. I think it's a bad practice to create and dispose DbContext every time. It seems to be very performance-costly.
  2. Thus, don't you want to extract SaveChanges method? It will just call SaveChanges on DbContext.
  3. If you can not do that, I consider to create a ContextFactory is a better way instead of Service Locator. I know that for example Windsor can auto-generate factory implementations for given interface ( http://docs.castleproject.org/Default.aspx?Page=Typed-Factory-Facility-interface-based-factories&NS=Windsor ). It's better semantically and for testing purposes. The focus here is in transparent factory interface, which implementation is based on IoC configuration and lifitime policies.
  4. Finally, if you are not interested in immediate changes pushing, you may create IDisposable DbContext wrapper, which will SaveChanges on disposing. Assuming you are using some request/response paradigm and per request lifetime management.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM