Несколько DbContexts в N-уровневом приложении

Я создаю свое первое приложение N-Tier MVC, и я столкнулся с проблемой управления несколькими DbContexts с помощью моего первого подхода к базе данных.

У меня есть следующие слои

Presentation
Service (WCF)
Business
Data Access

Мне не нужна ссылка на структуру сущности на моем сервисном уровне, но я не вижу, как создать интерфейс или что-то еще для управления двумя контекстами. У меня он работает с одним контекстом, искаженным в IDatabaseFactory, но я не могу найти подход для управления двумя.

Ниже мой UnitOfWork, созданный в моем Service ctor, но, как бы я ни смотрел на него, я все еще привязан к SiteModelContainer, хотя на самом деле у меня другой контекст.

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();
    }
}

person MisterIsaak    schedule 11.07.2011    source источник
comment
У меня это работает с одним контекстом, искаженным в IDatabaseFactory. Я чувствую вашу боль; большая часть моего кода, кажется, тоже находится во временной и/или пространственной деформации.   -  person B. Clay Shannon    schedule 27.01.2014


Ответы (2)


Предоставление вашему Factory и UnitOfWork параметра универсального типа может быть решением:

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();
    }
}

Тогда интерфейсы IDatabaseFactory и IUnitWork также должны быть общими.

Затем вы можете создать единицу работы для разных контекстов:

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

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

Изменить:

Чтобы избавиться от зависимости от EF в ваших классах обслуживания, вы можете попробовать что-то вроде этого. Служба знает только эти три интерфейса:

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

Вот специальные реализации для EF:

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

В вашу службу будет введено IUnitOfWorkFactory:

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();
        }
    }
}

При создании службы вводится конкретный экземпляр фабрики:

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

Имейте в виду, что тяжелая работа будет связана с абстрактным репозиторием и его реализацией. Как только у вас больше не будет контекста EF в вашем сервисном классе, вам придется имитировать множество методов в интерфейсе репо, поддерживающих все необходимые сценарии для манипулирования данными.

person Slauma    schedule 11.07.2011
comment
Мне очень нравится, как это выглядит, но для создания нового экземпляра UnitOfWork или DatabaseFactory в моем сервисе таким образом по-прежнему требуется ссылка на структуру сущностей, которую я пытаюсь избежать для разделения задач. Я не хочу, чтобы мой сервис зависел от EF, но я могу в конечном итоге нарушить это правило. - person MisterIsaak; 11.07.2011
comment
@Джисаак. Я пробовал предложение удалить зависимость вашего сервиса от EF. Я думаю, что это возможно, но я немного изменил ваш дизайн, особенно заменил DatabaseFactory на UnitOfWorkFactory (как интерфейс, так и реализацию), см. мой Редактировать... - person Slauma; 12.07.2011

Вы можете создать оболочку, которая является общим репозиторием для DbContexts (и использует базовый ObjectContext для поддержки этого).

Вот пример, который я использовал в прошлом (который также отделяет ваш код от любой прямой зависимости от 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
}
person Jeff    schedule 11.07.2011