derodevil
derodevil

Reputation: 1073

Best Practice of Repository and Unit of Work Pattern with Multiple DbContext

I plan to develop a web application using ASP.NET MVC with Entity Framework 6 (Code First / POCO). I also want to use generic Repository and Unit of Work Pattern in my application. This application connects to more than two databases so, I have to use multiple DbContext in the application.

public class ContextOne : DbContext
{
    public DbSet<Model_One1>
    public DbSet<Model_One2>
}

public class ContextTwo : DbContext
{
    public DbSet<Model_Two1>
    public DbSet<Model_Two2>
}

public class ContextThree : DbContext
{
    public DbSet<Model_Three1>
    public DbSet<Model_Three2>
}

public interface IRepository<T> where T : DbContext
{
    void Add<T>(T entity) where T : class;
}

public class Repository<T> where T : DbContext
{
    void Add<T>(T entity) where T : class
    {
        //T is DbContext and Model. So confusing
    }
}

public interface IUnitOfWork<IRepository>
{
}

public class UnitOfWork<IRepository>
{
    //IRepository contains more than one DbContext how can I initiate them here?
}

//in application should look like this
public class BaseController : Controller
{
    protected IRepository repository = new .. //here I have no idea with multiple DbContext
}

public class HomeController : BaseController
{
    public ActionResult Add(Model_Two2 model)
    {
        base.repository.Add<Model_Two2>(model)
    }
}

If I call the IRepository and IUnitOfWork from Controller how can I know the matching context? What's the best practice of this problem?

Upvotes: 14

Views: 17576

Answers (2)

ramiramilu
ramiramilu

Reputation: 17182

I would suggest you to create UnitOfWork pattern with a Constructor parameter to accept DbContext -

public class UnitOfWork : IUnitOfWork
{
    private readonly IDbContext _context;

    private bool _disposed;
    private Hashtable _repositories;

    public UnitOfWork(IDbContext context)
    {
        _context = context;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Save()
    {
        _context.SaveChanges();
    }

    public virtual void Dispose(bool disposing)
    {
        if (!_disposed)
            if (disposing)
                _context.Dispose();

        _disposed = true;
    }

    public IRepository<TEntity> Repository<TEntity>() where TEntity : class
    {
        if (_repositories == null)
            _repositories = new Hashtable();

        var type = typeof(TEntity).Name;

        if (_repositories.ContainsKey(type)) return (IRepository<TEntity>) _repositories[type];

        var repositoryType = typeof (Repository<>);

        var repositoryInstance =
            Activator.CreateInstance(repositoryType
                .MakeGenericType(typeof (TEntity)), _context);

        _repositories.Add(type, repositoryInstance);

        return (IRepository<TEntity>) _repositories[type];
    }
}

where IDbContext is -

public interface IDbContext
{
    IDbSet<T> Set<T>() where T : class;
    int SaveChanges();
    void Dispose();
}

And the repository implementation would be -

 public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        internal IDbContext Context;
        internal IDbSet<TEntity> DbSet;

        public Repository(IDbContext context)
        {
            Context = context;
            DbSet = context.Set<TEntity>();
        }

        public virtual TEntity FindById(object id)
        {
            return DbSet.Find(id);
        }

        public virtual void Update(TEntity entity)
        {
            DbSet.Attach(entity);
        }
        public virtual void Delete(object id)
        {
            var entity = DbSet.Find(id);
            var objectState = entity as IObjectState;
            if (objectState != null)
                objectState.State = ObjectState.Deleted;
            Delete(entity);
        }

        public virtual void Delete(TEntity entity)
        {
            DbSet.Attach(entity);
            DbSet.Remove(entity);
        }

        public virtual void Insert(TEntity entity)
        {
            DbSet.Attach(entity);
        }

        public virtual List<TEntity> GetAll()
        {
            return DbSet.ToList();
        }
    }

With this approach you can create a UnitOfWork for individual DBContext and you have specific logic to commit or rollback in UnitOfWork.

Upvotes: 17

Andrei
Andrei

Reputation: 44680

I would implement UnitOfWork as an ActionAttribute where OnActionExecuting I open transaction, and OnActionExecuted I commit transaction if everything is ok, if there is exception in ActionContext, transaction should be rolled back.

The tricky thing is that you have 2 DbContexts. I think, you should have lazy creation of dbContexts. Introduce a kind of flag variable and set it to True in UnitOfWork.OnActionExecuting. Then, when you first time touch dbContext, you should check if you are dealing with UnitOfWork, and if yes, you should open transaction for this particular dbContext. All the open transactions could be put into a List which is accessible from UnitOfWork.ActionExecuted. Finally, check if there are any exceptions in ActionContext: yes - Rollback, no - Commit.

Upvotes: 1

Related Questions