Multiple DbContexts in N-Tier Application

2019-01-23 08:05发布

I'm creating my first N-Tier MVC application and I've run into a road block with how to manage multiple DbContexts with my database first approach.

I have the following layers

Presentation
Service (WCF)
Business
Data Access

I don't want an entity framework reference in my service layer but I don't see how to create an Interface or something to manage two contexts. I have it working with a single context warpped in a IDatabaseFactory but I can't seem to find an approach to manage two.

Below is my UnitOfWork that is created in my Service ctor but every way I look at it I'm still tied to the SiteModelContainer, when in fact I have another context.

public class UnitOfWork : IUnitOfWork
    {
        private SiteModelContainer _context;

        private readonly IDatabaseFactory _databaseFactory;

        protected SiteModelContainer SiteContext
        {
            get { return _context ?? (_context = _databaseFactory.Get()); }
        }

        public UnitOfWork(IDatabaseFactory factory)
        {
            _databaseFactory = factory;
            _context = _databaseFactory.Get();
        }
        //More code
    }



public class DatabaseFactory : Disposable, IDatabaseFactory
{
    private SiteModelContainer _dataContext;

    public SiteModelContainer Get()
    {
        return _dataContext ?? (_dataContext = new SiteModelContainer());
    }

    protected override void DisposeCore()
    {
        if (_dataContext != null)
            _dataContext.Dispose();
    }
}

2条回答
聊天终结者
2楼-- · 2019-01-23 08:31

You could create a wrapper that is generic repository across DbContexts (and utilizes the underlying ObjectContext to support this).

Here is an example I've used in the past (which also decouples your code from any direct dependency on Entity Framework).

// Make your DbContext inherit from this. This goes in your Unit of Work.
public interface IEntitySetProvider : IDisposable
{
    IEntitySet<T> CreateEntitySet<T>();
}

// This is your adapted DBContext
public class MyDbContext1 : DbContext, IEntitySetProvider
{
    public IEntitySet<T> CreateEntitySet<T>()
    {
        return new EntitySet<T>(((IObjectContextAdapter)this).CreateObjectSet<T>());
    }

    .
    .
    .
}


/// <summary>
///   A wrapper for an IQueryable that exposes AddNew and Attach methods.
/// </summary>
/// <typeparam name = "T"></typeparam>
public interface IEntitySet<T> : IQueryable<T>
{
    /// <summary>
    ///   Attaches the specified value and considers it new.
    /// </summary>
    /// <param name = "value">The value.</param>
    void AddNew(T value);

    /// <summary>
    ///   Attaches the specified value and considers it modified.
    /// </summary>
    /// <param name = "value">The value.</param>
    void Attach(T value);
}

/// <summary>
///   An IEntitySet for Entity Framework.
/// </summary>
/// <typeparam name = "T"></typeparam>
internal class EntitySet<T> : IEntitySet<T> where T : class
{
    private readonly ObjectSet<T> _objectSet;

    public EntitySet(ObjectSet<T> objectSet)
    {
        _objectSet = objectSet;
    }

    #region IEntitySet<T> Members

    public void AddNew(T value)
    {
        _objectSet.AddObject(value);
    }

    public void Attach(T value)
    {
        _objectSet.Attach(value);
        _objectSet.Context.ObjectStateManager.ChangeObjectState(value, EntityState.Modified);
    }

    public IEnumerator<T> GetEnumerator()
    {
        return ((IQueryable<T>) _objectSet).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IQueryable) _objectSet).GetEnumerator();
    }

    public Type ElementType
    {
        get { return ((IQueryable<T>) _objectSet).ElementType; }
    }

    public Expression Expression
    {
        get { return ((IQueryable<T>) _objectSet).Expression; }
    }

    public IQueryProvider Provider
    {
        get { return ((IQueryable<T>) _objectSet).Provider; }
    }

    #endregion
}
查看更多
Fickle 薄情
3楼-- · 2019-01-23 08:35

Giving your Factory and UnitOfWork a generic type parameter might be a solution:

public class UnitOfWork<T> : IUnitOfWork<T>
    where T : DbContext, new()
{
    private T _context;

    private readonly IDatabaseFactory<T> _databaseFactory;

    protected T Context
    {
        get { return _context ?? (_context = _databaseFactory.Get()); }
    }

    public UnitOfWork(IDatabaseFactory<T> factory)
    {
        _databaseFactory = factory;
        _context = _databaseFactory.Get();
    }
    //More code
}

public class DatabaseFactory<T> : Disposable, IDatabaseFactory<T>
    where T : DbContext, new()
{
    private T _dataContext;

    public T Get()
    {
        return _dataContext ?? (_dataContext = new T());
    }

    protected override void DisposeCore()
    {
        if (_dataContext != null)
            _dataContext.Dispose();
    }
}

The IDatabaseFactory and IUnitWork interfaces would also have to be generic then.

You could then create Unit of Works for different contexts:

var factory1 = new DatabaseFactory<SiteModelContainer>();
var unitOfWork1 = new UnitOfWork<SiteModelContainer>(factory1);

var factory2 = new DatabaseFactory<AnotherModelContainer>();
var unitOfWork2 = new UnitOfWork<AnotherModelContainer>(factory2);

Edit:

To get rid of the dependency on EF in your service classes you could try something like this. The service only knows these three interfaces:

public interface IUnitOfWorkFactory
{
    IUnitOfWork Create(string contextType);
}

public interface IUnitOfWork : IDisposable
{
    IRepository<TEntity> CreateGenericRepository<TEntity>()
        where TEntity : class;
    void Commit();
}

public interface IRepository<T>
{
    IQueryable<T> Find(Expression<Func<T, bool>> predicate);
    void Attach(T entity);
    void Add(T entity);
    // etc.
}

Here are special EF-specific implementations:

public class UnitOfWorkFactory : IUnitOfWorkFactory
{
    public IUnitOfWork Create(string contextType)
    {
        switch (contextType)
        {
            case "SiteModelContainer":
                return new UnitOfWork<SiteModelContainer>();
            case "AnotherModelContainer":
                return new UnitOfWork<AnotherModelContainer>();
        }

        throw new ArgumentException("Unknown contextType...");
    }
}

public class UnitOfWork<TContext> : IUnitOfWork
    where TContext : DbContext, new()
{
    private TContext _dbContext;

    public UnitOfWork()
    {
        _dbContext = new TContext();
    }

    public IRepository<TEntity> CreateGenericRepository<TEntity>()
        where TEntity : class
    {
        return new Repository<TEntity>(_dbContext);
    }

    public void Commit()
    {
        _dbContext.SaveChanges();
    }

    public void Dispose()
    {
        _dbContext.Dispose();
    }
}

public class Repository<T> : IRepository<T>
    where T : class
{
    private DbContext _dbContext;
    private DbSet<T> _dbSet;

    public Repository(DbContext dbContext)
    {
        _dbContext = dbContext;
        _dbSet = dbContext.Set<T>();
    }

    public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
    {
        return _dbSet.Where(predicate);
    }

    public void Attach(T entity)
    {
        _dbSet.Attach(entity);
    }

    public void Add(T entity)
    {
        _dbSet.Add(entity);
    }

    // etc.
}

Your service would get a IUnitOfWorkFactory injected:

public class MyService
{
    private IUnitOfWorkFactory _factory;

    public MyService(IUnitOfWorkFactory factory)
    {
        _factory = factory;
    }

    public MyMethod()
    {
        using(var unitOfWork1 = _factory.Create("SiteModelContainer"))
        {
            var repo1 = unitOfWork1.
                CreateGenericRepository<SomeEntityTypeInSiteModel>();
            // Do some work
            unitOfWork1.Commit();
        }

        using(var unitOfWork2 = _factory.Create("AnotherModelContainer"))
        {
            var repo2 = unitOfWork2.
                CreateGenericRepository<SomeEntityTypeInAnotherModel>();
            // Do some work
            unitOfWork2.Commit();
        }
    }
}

When the service is created the concrete instance of the factory is injected:

var service = new MyService(new UnitOfWorkFactory());

Keep in mind that the hard work will be in the abstract repository and it's implementation. As soon as you don't have the EF context anymore in your service class you have to mimic a lot of methods in the repo interface supporting all necessary scenarios to manipulate the data.

查看更多
登录 后发表回答