EF Code First с репозиторием, UnitOfWork и DbContextFactory

Я собираюсь> взорвать <:) из-за количества чтения на эту тему ... У меня болит голова, и мне нужны честные мнения ... Существует аналогичный вопрос /обзор , который я заметил, но я считаю, что мой подход немного другой, поэтому я хотел спросить.

Мы приступаем к действительно большому проекту, который будет использовать ASP.NET MVC 4 с Web API и хотел бы использовать Entity Framework Code First. Прежде чем отправлять код для проверки, , пожалуйста, рассмотрите

  • Мой проект будет большим (возможно, более 100 доменов)
  • Нужен SOLID подход к архитектуре
  • В настоящее время я понимаю, что Entity Framework DbContext (полностью или частично) реализует шаблоны Unit of Work и Repository, поэтому я мог бы пропустить все это;)
  • Я действительно обеспокоен использованием EF, введенным непосредственно в конструкторы Controller (тестируемость, разделение проблем и т. д. и т. д.).
  • Я также знаю, что существует несколько способов реализации шаблонов UoW и Repository.
  • Я не беспокоюсь или не хочу создавать абстракции, которые позволят мне «обменивать» ORM (например, своп Entity Framework для NHiberante или такой).

Мой подход

  • Репозитории являются универсальными и имеют базовый класс, который реализует большую часть стандартной логики.
  • Репозитории нужны DbContext через конструктор (который предоставляется UnitOfWork)
  • UnitOfWork отвечает за управление доступом ко всем репозиториям, и между ними существует общий контекст защиты.
  • UnitOfWork является одноразовым, репозитории не ...
  • Чтобы «скрыть» DbContext, UnitOfWork создается с помощью IDbContextFactory.

Вопросы

  • Кажется, это работает для меня, и я вижу, что каждый контроллер просто нуждается в инъекции UoW, что приятно. Некоторым контроллерам требуется 2-3 репозитория в дополнение к сервисам домена, поэтому это делает вещи приятными ... Я думаю ...
  • Со временем UoW будет расти вместе с репозиториями (может быть 65+ совокупных корней, каждый из которых имеет репо). Как и как лучше управлять этим? Должен ли я каким-то образом вводить репозитории вместо od new (), используя их в UnitOfWork? Мне бы хотелось создать модуль IoC (Autofac - мой яд), чтобы связать все репозитории (как-то)
  • Является ли использование IDbContextFactory излишним, или я должен просто добавить DbContext в конструктор UnitOfWork вместо этого? Прямо сейчас, у моего веб-приложения нет прямой зависимости от Entity Framework, он зависит только от n DAL (что, в свою очередь, зависит от EF). С другой стороны, DbContextFactory new () es up MyAppDbContext и не обрабатывается IoC
  • Кто-нибудь замечает какой-либо другой «запах кода»?
  • Некоторые вопросы содержатся в комментариях к коду, чтобы сделать их более релевантными ...

Хорошо вот код с двумя репозиториями и использованием примера (все пространства имен опущены для краткости)

IDbContextFactory и DbContextFactory

/// <summary>
/// Creates instance of specific DbContext
/// </summary>
public interface IDbContextFactory //: IDisposable  //NOTE: Since UnitOfWork is disposable I am not sure if context factory has to be also...
{
    DbContext GetDbContext();
}

public class DbContextFactory : IDbContextFactory
{
    private readonly DbContext _context;

    public DbContextFactory()
    {
        // the context is new()ed up instead of being injected to avoid direct dependency on EF
        // not sure if this is good approach...but it removes direct dependency on EF from web tier
        _context = new MyAppDbContext(); 
    }

    public DbContext GetDbContext()
    {
        return _context;
    }

    // see comment in IDbContextFactory inteface...
    //public void Dispose()
    //{
    //    if (_context != null)
    //    {
    //        _context.Dispose();
    //        GC.SuppressFinalize(this);
    //    }
    //}
}

IRepository, репозиторий и 2 конкретных репозитория с дополнительными интерфейсами (Vehicle and Inventory)

public interface IRepository<T> where T : class
{
    /// <summary>
    ///   Get the total objects count.
    /// </summary>
    int Count { get; }

    /// <summary>
    ///   Gets all objects from database
    /// </summary>
    IQueryable<T> All();

    /// <summary>
    ///   Gets object by primary key.
    /// </summary>
    /// <param name="id"> primary key </param>
    /// <returns> </returns>
    T GetById(object id);

    /// <summary>
    ///   Gets objects via optional filter, sort order, and includes
    /// </summary>
    /// <param name="filter"> </param>
    /// <param name="orderBy"> </param>
    /// <param name="includeProperties"> </param>
    /// <returns> </returns>
    IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "");

    /// <summary>
    ///   Gets objects from database by filter.
    /// </summary>
    /// <param name="predicate"> Specified a filter </param>
    IQueryable<T> Filter(Expression<Func<T, bool>> predicate);

    /// <summary>
    ///   Gets objects from database with filting and paging.
    /// </summary>
    /// <param name="filter"> Specified a filter </param>
    /// <param name="total"> Returns the total records count of the filter. </param>
    /// <param name="index"> Specified the page index. </param>
    /// <param name="size"> Specified the page size </param>
    IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50);

    /// <summary>
    ///   Gets the object(s) is exists in database by specified filter.
    /// </summary>
    /// <param name="predicate"> Specified the filter expression </param>
    bool Contains(Expression<Func<T, bool>> predicate);

    /// <summary>
    ///   Find object by keys.
    /// </summary>
    /// <param name="keys"> Specified the search keys. </param>
    T Find(params object[] keys);

    /// <summary>
    ///   Find object by specified expression.
    /// </summary>
    /// <param name="predicate"> </param>
    T Find(Expression<Func<T, bool>> predicate);

    /// <summary>
    ///   Create a new object to database.
    /// </summary>
    /// <param name="entity"> Specified a new object to create. </param>
    T Create(T entity);

    /// <summary>
    ///   Deletes the object by primary key
    /// </summary>
    /// <param name="id"> </param>
    void Delete(object id);

    /// <summary>
    ///   Delete the object from database.
    /// </summary>
    /// <param name="entity"> Specified a existing object to delete. </param>
    void Delete(T entity);

    /// <summary>
    ///   Delete objects from database by specified filter expression.
    /// </summary>
    /// <param name="predicate"> </param>
    void Delete(Expression<Func<T, bool>> predicate);

    /// <summary>
    ///   Update object changes and save to database.
    /// </summary>
    /// <param name="entity"> Specified the object to save. </param>
    void Update(T entity);
}

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

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

    public virtual int Count
    {
        get { return _dbSet.Count(); }
    }

    public virtual IQueryable<T> All()
    {
        return _dbSet.AsQueryable();
    }

    public virtual T GetById(object id)
    {
        return _dbSet.Find(id);
    }

    public virtual IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
    {
        IQueryable<T> query = _dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        if (!String.IsNullOrWhiteSpace(includeProperties))
        {
            foreach (var includeProperty in includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }
        }

        if (orderBy != null)
        {
            return orderBy(query).AsQueryable();
        }
        else
        {
            return query.AsQueryable();
        }
    }

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

    public virtual IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50)
    {
        int skipCount = index*size;
        var resetSet = filter != null ? _dbSet.Where(filter).AsQueryable() : _dbSet.AsQueryable();
        resetSet = skipCount == 0 ? resetSet.Take(size) : resetSet.Skip(skipCount).Take(size);
        total = resetSet.Count();
        return resetSet.AsQueryable();
    }

    public bool Contains(Expression<Func<T, bool>> predicate)
    {
        return _dbSet.Count(predicate) > 0;
    }

    public virtual T Find(params object[] keys)
    {
        return _dbSet.Find(keys);
    }

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

    public virtual T Create(T entity)
    {
        var newEntry = _dbSet.Add(entity);
        return newEntry;
    }

    public virtual void Delete(object id)
    {
        var entityToDelete = _dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Delete(T entity)
    {
        if (_dbContext.Entry(entity).State == EntityState.Detached)
        {
            _dbSet.Attach(entity);
        }
        _dbSet.Remove(entity);
    }

    public virtual void Delete(Expression<Func<T, bool>> predicate)
    {
        var entitiesToDelete = Filter(predicate);
        foreach (var entity in entitiesToDelete)
        {
            if (_dbContext.Entry(entity).State == EntityState.Detached)
            {
                _dbSet.Attach(entity);
            }
            _dbSet.Remove(entity);
        }
    }

    public virtual void Update(T entity)
    {
        var entry = _dbContext.Entry(entity);
        _dbSet.Attach(entity);
        entry.State = EntityState.Modified;
    }
}   


public class VehicleRepository : Repository<Vehicle>, IVehicleRepository
{
    public VehicleRepository(DbContext dbContext) : base(dbContext)
    {
    }

}

public interface IVehicleRepository : IRepository<Vehicle>
{
    //RFU
}   

public interface IInventoryRepository : IRepository<InventoryItem>
{
    IList<InventoryItem> GetByVehicleId(string vehicleId); // NOTE: InventoryItem.VehicleId != InventoryItem.Id
}

public class InventoryItemRepository : Repository<InventoryItem>, IInventoryItemRepository
{
    public InventoryItemRepository(DbContext dbContext) : base(dbContext)
    {
    }

    public IList<InventoryItem> GetByVehicleId(string vehicleId)
    {
        return Filter(vii => vii.Vehicle.Id == vehicleId).ToList();
    }
}  

IUnitOfWork, UnitOfWork

public interface IUnitOfWork : IDisposable
{
    InventoryItemRepository InventoryItemRepository { get; }
    VehicleRepository VehicleRepository { get; }
    void Save();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _dbContext;
    private bool _disposed;
    private InventoryItemRepository _inventoryItemRepository;
    private VehicleRepository _vehicleRepository;


    /// <summary>
    /// NOTE: repository getters instantiate repositories as needed (lazily)...
    ///       i wish I knew of IoC "way" of wiring up repository getters...
    /// </summary>
    /// <param name="dbContextFactory"></param>
    public UnitOfWork(IDbContextFactory dbContextFactory)
    {
        _dbContext = dbContextFactory.GetDbContext();
    }

    public void Save()
    {
        if (_dbContext.GetValidationErrors().Any())
        {
            // TODO: move validation errors into domain level exception and then throw it instead of EF related one
        }
        _dbContext.SaveChanges();
    }

    public InventoryItemRepository InventoryItemRepository
    {
        get { return _inventoryItemRepository ?? (_inventoryItemRepository = new InventoryItemRepository(_dbContext)); }
    }

    public VehicleRepository VehicleRepository
    {
        get { return _vehicleRepository ?? (_vehicleRepository = new VehicleRepository(_dbContext)); }
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _dbContext.Dispose();
            }
        }
        _disposed = true;
    }
}

Пример использования в ASP.NET MVC4 + веб-API

Global.asax.cs (Application_Start)

// relevant registration
        builder.RegisterType<UnitOfWork>().As<IUnitOfWork>()
            .WithParameter("dbContextFactory", new DbContextFactory())
            .InstancePerHttpRequest()
            .InstancePerApiRequest();

InventoryController

public class InventoryController : ApiController
{

    private readonly InventoryItemMapper _mapper; // NOTE: maps viewModel to domain entities and vice versa using ValueInjector
    private readonly IUnitOfWork _unitOfWork;

    public InventoryController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork; // UoW (with all repos) is injected and ready for use...
        _mapper = new VehicleInventoryItemMapper(); //TODO: this will be injected also...

    }

    public IEnumerable<InventoryViewModel> Get()
    {
        var inventoryItems = _unitOfWork.InventoryItemRepository.All().ToList();
        var inventory = _mapper.MapToModel(inventoryItems);
        return inventory;

    }
}
57 голосов | спросил zam6ak 16 Mayam12 2012, 05:22:47

4 ответа


20

Если вы, вероятно, будете расти до 100+ объектов домена, я исхожу из предположения, что у вас будет много логики.

Я бы не обращался к репозиториям напрямую через контроллеры, я бы добавил уровень сервиса, который извлекается контроллерами через UnitOfWork.

причина:

  • Действия вашего контроллера, вероятно, раздуваются и становятся неуправляемыми. Поскольку у вас будет много кода котла (например, Get, Update, Save).

  • Уровень сервиса заставит вас не нарушать принцип DRY, потому что вы не будете повторять типичный код репозитория.

  • Вы можете использовать шаблон «Фасад» для группировки общих задач, требующих нескольких репозиториев http://en.wikipedia.org/wiki/Facade_pattern

  • Ваша бизнес-логика будет находиться в Уровне обслуживания, позволяя им повторно использовать в другом месте, плюс ваши контроллеры будут слабо связаны с вашей бизнес-логикой.

пример Я построил: https://gist.github.com/3025099

ответил Mike 30 J0000006Europe/Moscow 2012, 22:58:48
11

Я согласен с аргументами @Mike о том, чтобы избежать раздувания контроллера через службы.

Обертка DbContext - это нечеткая абстракция. Независимо от того, вы окажетесь в какой-то зависимости от EF в своем сервисе /уровне контроллера.

Кроме того, я бы избегал лишнего слоя UnitOfWork и Repository просто потому, что DbContext обертывает это для вас уже.

В MSDN :

  

Класс DbContext

     

Представляет комбинацию шаблонов Unit-Of-Work и Repository   и позволяет вам запрашивать базу данных и группировать изменения, которые   затем будет записана обратно в хранилище в виде единицы.

Если вы используете любые рамки DI, вы можете легко управлять временем жизни DbContext и сервисами.

Дальнейшее чтение: Архитектура в яме гибели : Зло уровня абстракции хранилища

ответил Mrchief 14 +04002012-10-14T04:30:20+04:00312012bEurope/MoscowSun, 14 Oct 2012 04:30:20 +0400 2012, 04:30:20
2

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

Метод «Пропустить» поддерживается только для отсортированного ввода в LINQ to Entities. Метод «OrderBy» должен быть вызван перед методом «Пропустить».

ответил Prad 2 J0000006Europe/Moscow 2012, 15:17:02
0

Возможно, это была опечатка, но я, вероятно, предположил бы, что IUnitOfWork выставляет интерфейсы вместо конкретных реализаций, например. IVehicleRepository вместо VehicleRepository.

Мне очень нравится использование IDBContextFactory в этом отношении, это не полностью перебор, но красиво абстрагирует DBContext. Однако при реализации этого вы не могли бы затем передать завод в конструкторы Repository<T>, а не DBContext)?

Я всегда задавался вопросом, как обеспечить, чтобы контроллеры имели доступ к количеству или репозиториям и т. д. Мне интересно видеть комментарии других людей об экспонировании всех репозиториев в UnitOfWork.

ответил dreza 3 J0000006Europe/Moscow 2012, 01:51:42

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132