-->

воскресенье, 24 мая 2015 г.

Паттерны Repository и Unit of Work для Entity Framework

По моим впечатлениям, по архитектуре использующих Entity Framework приложений написано куда больше плохих примеров и гайдов, чем хороших. Кто-то не брезгует передачей entities через все слои приложения прямо в UI, кто-то борется с заложенной в EF архитектурой, реализуя свое видение паттернов из книг по дизайну. О последних и пойдет речь. Хит-парад паттернов с приставкой "анти-" возглавляют свои реализации Generic Repository.
Например, такая статья: Generic Repository Pattern - Entity Framework, ASP.NET MVC and Unit Testing Triangle
Код в статье примерно следующий:
public abstract class GenericRepository : IGenericRepository 
    where T : class where C : DbContext, new() 
{
    private C _entities = new C();

    public virtual IQueryable GetAll() 
    {
        return _entities.Set();
    }

    public virtual void Add(T entity) {
        _entities.Set().Add(entity);
    }

    public virtual void Delete(T entity) {
        _entities.Set().Remove(entity);
    }

    public virtual void Edit(T entity) {
        _entities.Entry(entity).State = System.Data.EntityState.Modified;
    }

    public virtual void Save() {
        _entities.SaveChanges();
    }
}

public class FooRepository : GenericRepository, IFooRepository 
{
    public IQueryable GetAll() 
    {
        return context.Foos;
    }
    //и т.д.
}
Процитирую Фаулера:
A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.
Приведенная в статье реализация даже возвращает IQueryable, ни о какой инкапсуляции или маппинге даже речи не идет.

Зачем вообще нужен абстрактный класс GenericRepository? Его функциональность равна функциональности DbSet за вычетом того, что не попало в IGenericRepository<T>. Мы получаем несколько уровней вложенности кода там, где достаточно одного обращения к DbSet.

Часто ли в реальных приложениях встречаются репозитории из 4 простейших CRUD методов? Любой приближенный к реальности запрос потребует прямого прямого обращения к контексту из FooRepository.

Следующий спорный момент - время жизни контекста:
private C _entities = new C();

public virtual void Save() 
{
    _entities.SaveChanges();
}
Еще одной "отличной" идеей могло быть разделять один контекст всеми между репозиториями, чтобы собрать как можно больше побочных эффектов на строку кода. А нет, погодите, про это уже написали:

По утверждению автора, нижеприведенный код реализует паттерн Unit Of Work:
public class UnitOfWork : IDisposable
{
    private readonly EFDbContext context;

    public UnitOfWork()
    {
        context = new EFDbContext();
    }

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

    public Repository Repository() where T : BaseEntity
    {
        var type = typeof(T).Name;

        if (!repositories.ContainsKey(type))
        {
            var repositoryType = typeof(Repository<>);
            var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context);
            repositories.Add(type, repositoryInstance);
        }
        return (Repository)repositories[type];
    }
}
Этот класс - ни что иное как обертка над стандартным DbContext, умеющая коммитить изменения в БД и отдавать типизированные "репозитории". Вновь мы имеем слой абстракции, затрудняющий понимание кода и увеличивающий вложенность без каких-либо обоснований.

Чем оба рассмотренных случая сходны между собой? В обоих мы имеем корявые надстройки над стандартной архитектурой EF. Какой смысл реализовывать паттерн Unit of Work на основе DbContext?

DbContext - это готовая реализация Unit of Work;
DbSet - реализация Repository;

Кстати, об этом даже в MSDN написано =)

Да, в некоторых случаях действительно необходимо абстрагироваться от технологии доступа к данным. Да, нужны юнит-тесты, а с ними mock-объекты и dependency injection. Но рассмотренный ранее код не решает ни одну из подобных задач! Люди строят нагромождения паттернов ради самих паттернов, увеличивая количество плохого кода в геометрической прогрессии. И это печально.

Комментариев нет:

Отправить комментарий