Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 263
derodevil Avatar asked Sep 28 '14 07:09

derodevil


People also ask

Can we have multiple DbContext in Entity Framework?

Multiple DbContext was first introduced in Entity Framework 6.0. Multiple context classes may belong to a single database or two different databases.

Do we need repository pattern with Entity Framework?

No, the repository/unit-of-work pattern (shortened to Rep/UoW) isn't useful with EF Core. EF Core already implements a Rep/UoW pattern, so layering another Rep/UoW pattern on top of EF Core isn't helpful.

Is DbContext a unit of work?

A DbContext instance represents a combination of the Unit Of Work and Repository patterns such that it can be used to query from a database and group together changes that will then be written back to the store as a unit.

When should we use Repository pattern?

The Repository pattern makes it easier to test your application logic. The Repository pattern allows you to easily test your application with unit tests. Remember that unit tests only test your code, not infrastructure, so the repository abstractions make it easier to achieve that goal.


2 Answers

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.

like image 163
Andrei Avatar answered Sep 18 '22 18:09

Andrei


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.

like image 24
ramiramilu Avatar answered Sep 18 '22 18:09

ramiramilu