Search code examples
c#entity-frameworkmvvmdependency-injectionservice-locator

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.


Solution

  • 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.