Search code examples
wpfdependency-injectionrepositoryninjectdbcontext

Properly Disposing a context with Unit of Work Pattern Dependency Injection in WPF


I have been trying to use DI within my Unit of Work / Repository pattern in WPF. The problem I am running into currently is if I make a call to a repository like _UserRepo.Add(User) and an exception is thrown. Every new call to the repository throws the exception because the context is never disposed of.

What I have tried

Unit Of Work

 public class UnitOfWork : IUnitOfWork
{
    private DbContextTransaction _trans;
    private BomConfiguratorContext _context;

    public UnitOfWork(BomConfiguratorContext context)
    {
        _context = context;
        _trans = context.Database.BeginTransaction();
    }

    public void Dispose()
    {
        try
        {
            _context.SaveChanges();
            _trans.Commit();
        }
        catch (Exception)
        {
            _trans.Rollback();
        }
        finally
        {
            _context.Dispose(); //This obviously does not work
        }
    }
}

Unit Of Work Factory

 public class UnitOfWorkFactory : IUnitOfWorkFactory
{
    private BomConfiguratorContext _context;
    public UnitOfWorkFactory(BomConfiguratorContext context)
    {
        _context = context;
    }
    public UnitOfWork Create()
    {
        return new UnitOfWork(_context);
    }
}

My Generic Repository

public interface IRepository<TEntity> where TEntity : class
{
    void Add(TEntity entity);
    void AddRange(IEnumerable<TEntity> entities);

    void Remove(TEntity entity);
    void RemoveRange(IEnumerable<TEntity> entities);

    TEntity Get(int id);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);

    void Update(TEntity entity);
}

Generic Repository Implementation

 public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    protected readonly BomConfiguratorContext Context;

    public Repository(BomConfiguratorContext context)
    {
        Context = context;
    }
    public virtual void Add(TEntity entity)
    {
        Context.Set<TEntity>().Add(entity);
    }

    public void AddRange(IEnumerable<TEntity> entities)
    {
        Context.Set<TEntity>().AddRange(entities);
    }

    public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
    {
        return Context.Set<TEntity>().Where(predicate);
    }

    public TEntity Get(int id)
    {
        return Context.Set<TEntity>().Find(id);
    }

    public IEnumerable<TEntity> GetAll()
    {
        return Context.Set<TEntity>().ToList();
    }

    public void Remove(TEntity entity)
    {
        Context.Set<TEntity>().Remove(entity);
    }

    public void RemoveRange(IEnumerable<TEntity> entities)
    {
        Context.Set<TEntity>().RemoveRange(entities);
    }
    public void Update(TEntity entity)
    {
        Context.Set<TEntity>().Attach(entity);
        Context.Entry(entity).State = System.Data.Entity.EntityState.Modified;
    }
}

User Repository

public class UserRepository : Repository<User>,IUserRepository
{
    public UserRepository(BomConfiguratorContext context)
        :base(context)
    {

    }
}

Use Case

using (var UOW = _UnitOfWorkFactory.Create())
{
     //Submit the user
     _UserRepository.Add(ExampleNewUser);

}

So currently I am using MVVM Light to do all my DI work, now I understand with mvvm light you can only inject with singleton scope. So I am pretty sure I will end up having to switch over to something like Ninject so I can utilize their .InTransientScope or .InNamedScope (from what I have been reading).

Obviously the above code will not work with MVVM Light since the context is never properly disposed of.

The Question

So my question to you is if I were to swap over to using Ninject and start injecting my Context into these repositories / unit of work. How do I properly configure it to AWLAYS inject a new context within my unit of work for the repositories.

I read that Ninject MVC has .InRequestScope which would solve the issue entirely. But what about for WPF? How do you achieve the same kind of injection?

I can't seem to find the exact solution/pattern or maybe there is a better way to do this? Any suggestions and help would be greatly appreciated.


Solution

  • My solution to the problem was to create a ContextFactory.

    Interface

    public interface IContextFactory
    {
        BomConfiguratorContext Create();
        BomConfiguratorContext Get();
    }
    

    Context Factory

    The Factory allows me to either Get an existing context or create a new context.

    public class ContextFactory : IContextFactory
    {
        private BomConfiguratorContext _context;
    
        public ContextFactory(BomConfiguratorContext context)
        {
            _context = context;
        }
    
        public BomConfiguratorContext Create()
        {
            _context = new BomConfiguratorContext();
            return _context;
        }
    
        public BomConfiguratorContext Get()
        {
            return _context;
        }
    }
    

    New Base Repository

    By calling the ContextFactory.Get() method I use the cached context instead of creating a new one.

     public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        protected readonly IContextFactory ContextFactory;
    
        public Repository(IContextFactory factory)
        {
            ContextFactory = factory;
        }
        public virtual void Add(TEntity entity)
        {
    
            ContextFactory.Get().Set<TEntity>().Add(entity);
        }
    
        public void AddRange(IEnumerable<TEntity> entities)
        {
    
            ContextFactory.Get().Set<TEntity>().AddRange(entities);
    
        }
    
        public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
        {
            return ContextFactory.Get().Set<TEntity>().Where(predicate);
        }
    
        public TEntity Get(int id)
        {
            return ContextFactory.Get().Set<TEntity>().Find(id);
        }
    
        public IEnumerable<TEntity> GetAll()
        {
            return ContextFactory.Get().Set<TEntity>().ToList();
        }
    
        public void Remove(TEntity entity)
        {
            ContextFactory.Get().Set<TEntity>().Remove(entity);
        }
    
        public void RemoveRange(IEnumerable<TEntity> entities)
        {
            ContextFactory.Get().Set<TEntity>().RemoveRange(entities);
        }
        public void Update(TEntity entity)
        {
            ContextFactory.Get().Set<TEntity>().Attach(entity);
            ContextFactory.Get().Entry(entity).State = System.Data.Entity.EntityState.Modified;
        }
    }
    

    New Unit Of Work Factory

    When the factory is Create() method is called I call the context factory's Create() method to create a new context.

    public class UnitOfWorkFactory : IUnitOfWorkFactory
    {
        private IContextFactory _contextFactory;
    
        public UnitOfWorkFactory(IContextFactory factory)
        {
            _contextFactory = factory;
        }
        public UnitOfWork Create()
        {
            return new UnitOfWork(_contextFactory.Create());
        }
    }
    

    By doing it this way I am now able to inject my context factory into all my repositories. I attempted to use the Ninject scopes mentioned above in the original question but ended up causing issues with injecting two separate contexts, one in my unit of work factory and one in my repositories.