Подтвердить что ты не робот

Код EF6 Сначала с общим хранилищем и инъекцией зависимостей и SoC

После большого количества чтения и тестирования с Entity Framework последней стабильной версией (6.1.1).

Я читаю много противоречий о том, следует ли использовать репозитории с EF6 или EF вообще, потому что он DbContext уже предоставляет репозиторий и DbSet UoW, из коробки.

Позвольте мне сначала объяснить, что мое решение содержит в терминах проекта, и затем я вернусь к противоречию.

Он имеет проект библиотеки классов и проект asp.net-mvc. Проект класса lib, являющийся доступом к данным, и где Migrations включены для Code First.

В моем проекте класса lib у меня есть общий репозиторий:

public interface IRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> Get();

    TEntity GetByID(object id);

    void Insert(TEntity entity);

    void Delete(object id);

    void Update(TEntity entityToUpdate);
}

И вот его реализация:

public class Repository<TEntity> where TEntity : class
{
    internal ApplicationDbContext context;
    internal DbSet<TEntity> dbSet;

    public Repository(ApplicationDbContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get()
    {
        IQueryable<TEntity> query = dbSet;
        return query.ToList();
    }

    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }
}

И вот несколько объектов:

public DbSet<User> User{ get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<UserOrder> UserOrders { get; set; }
public DbSet<Shipment> Shipments { get; set; }

Я не повторяю, но с EF6 вы больше не пропускаете репозитории, а DbContext. Итак, для DI я создал в проекте asp-net-mvc следующее: Ninject:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
}

И это будет вставлять ApplicationDbContext через инъекцию конструктора в классы верхнего уровня, где это применимо.

Теперь вернемся к противоречию.

Если нам больше не нужен репозиторий, потому что EF уже предоставляет это из коробки, как мы это делаем Separation of Concern (сокращенно называемый SoC в заголовке)?

Теперь исправьте меня, если я ошибаюсь, но это звучит для меня так, как будто мне просто нужно сделать все логику/вычисления доступа к данным (например, добавление, выборку, обновление, удаление и некоторые пользовательские логики/вычисления здесь и там (объект specific)) в проекте asp.net-mvc, если я не добавляю репозиторий.

Любой свет по этому вопросу действительно оценен.

4b9b3361

Ответ 1

Небольшое объяснение, надеюсь, очистит ваше замешательство. Шаблон репозитория существует для абстрактного соединения с базой данных и логики запросов. ORM (объектно-реляционные картографы, такие как EF) были в той или иной форме настолько длинными, что многие люди забыли или никогда не испытывали огромной радости и удовольствия от работы с кодом спагетти, замусоренным SQL-запросами и утверждениями. Было время, что если вы хотите запросить базу данных, вы фактически несете ответственность за сумасшедшие вещи, такие как инициирование соединения и фактическое построение операторов SQL из эфира. Цель шаблона репозитория заключалась в том, чтобы дать вам одно место, чтобы положить всю эту гадость, подальше от вашего красивого нетронутого кода приложения.

Ускоренная пересылка на 2014 год, платформа Entity Framework и другие ORM - это ваш репозиторий. Вся логика SQL аккуратно упакована из ваших любопытных глаз, и вместо этого у вас есть хороший программный API для использования в вашем коде. В одном отношении это достаточно абстракция. Единственное, на что он не распространяется, - это зависимость от самого ORM. Если позже вы решите, что хотите отключить Entity Framework для чего-то вроде NHibernate или даже веб-API, вам нужно сделать операцию в своем приложении, чтобы сделать это. В результате добавление еще одного уровня абстракции по-прежнему является хорошей идеей, но просто не репозиторием или, по крайней мере, пусть типичным репозиторием.

У репозитория у вас есть типичный репозиторий. Он просто создает прокси для методов API Entity Framework. Вы вызываете repo.Add, а репозиторий вызывает context.Add. Это, честно говоря, смешно, и поэтому многие, включая меня, говорят, что не используют репозитории с Entity Framework.

Так что вы должны делать? Создавайте службы, или, возможно, лучше всего называйте "сервисные классы". Когда услуги начинают обсуждаться в отношении .NET, внезапно вы говорите о любых вещах, которые совершенно не имеют отношения к тому, что мы обсуждаем здесь. Класс, подобный сервису, подобен сервису, поскольку он имеет конечные точки, которые возвращают определенный набор данных или выполняют очень специфическую функцию для некоторого набора данных. Например, в то время как с типичным репозиторием вы обнаружите, что вы сами делаете такие вещи, как:

articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)

Ваш класс обслуживания будет работать следующим образом:

service.GetPublishedArticles();

Смотрите, вся логика для того, что квалифицируется как "опубликованная статья", аккуратно содержится в методе конечных точек. Кроме того, в репозитории вы все еще подвергаете воздействию базового API. Легче переключаться с чем-то другим, потому что базовое хранилище данных абстрагируется, но если API для запроса в этот хранилище данных изменится, вы все еще будите.

UPDATE

Настройка будет очень похожей; разница в основном заключается в том, как вы используете службу против репозитория. А именно, я бы даже не сделал его зависимым от сущности. Другими словами, у вас по существу есть служба для каждого контекста, а не для объекта.

Как всегда, начните с интерфейса:

public interface IService
{
    IEnumerable<Article> GetPublishedArticles();

    ...
}

Затем ваша реализация:

public class EntityFrameworkService<TContext> : IService
    where TContext : DbContext
{
    protected readonly TContext context;

    public EntityFrameworkService(TContext context)
    {
        this.context = context;
    }

    public IEnumerable<Article> GetPublishedArticles()
    {
        ...
    }
}

Тогда все начинает становиться немного волосатым. В методе примера вы можете просто ссылаться на DbSet напрямую, т.е. context.Articles, но это подразумевает знание имен DbSet в контексте. Лучше использовать context.Set<TEntity>() для большей гибкости. Прежде чем переходить на поезда слишком много, я хочу указать, почему я назвал это EntityFrameworkService. В вашем коде вы всегда ссылаетесь только на свой IService интерфейс. Затем, используя контейнер для инъекций зависимости, вы можете заменить EntityFrameworkService<YourContext> на это. Это открывает возможность создания других поставщиков услуг, например, WebApiService и т.д.

Теперь мне нравится использовать один защищенный метод, который возвращает запрос, который могут использовать все мои сервисные методы. Это избавляет от большого количества трещин, таких как необходимость каждый раз инициализировать экземпляр DbSet через var dbSet = context.Set<YourEntity>();. Это будет выглядеть примерно так:

protected virtual IQueryable<TEntity> GetQueryable<TEntity>(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = null,
    int? skip = null,
    int? take = null)
    where TEntity : class
{
    includeProperties = includeProperties ?? string.Empty;
    IQueryable<TEntity> query = context.Set<TEntity>();

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

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

    if (orderBy != null)
    {
        query = orderBy(query);
    }

    if (skip.HasValue)
    {
        query = query.Skip(skip.Value);
    }

    if (take.HasValue)
    {
        query = query.Take(take.Value);
    }

    return query;
}

Обратите внимание, что этот метод, во-первых, защищен. Подклассы могут использовать его, но это определенно не должно быть частью публичного API. Весь смысл этого упражнения заключается в том, чтобы не подвергать запросов. Во-вторых, он общий. В других словах он может обрабатывать любой тип, который вы бросаете на него, если в нем есть что-то в этом контексте.

Затем, в нашем маленьком примере, вы в конечном итоге сделаете что-то вроде:

public IEnumerable<Article> GetPublishedArticles()
{
    return GetQueryable<Article>(
        m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
        m => m.OrderByDescending(o => o.PublishDate)
    ).ToList();
}

Другим опрятным подходом к этому подходу является способность иметь общие методы обслуживания с использованием интерфейсов. Позвольте сказать, что я хотел иметь один способ получить опубликованное что-либо. Я мог бы иметь такой интерфейс, как:

public interface IPublishable
{
    PublishStatus Status { get; set; }
    DateTime PublishDate { get; set; }
}

Затем любые объекты, которые могут быть доступны для публикации, просто реализуют этот интерфейс. Теперь вы можете сделать следующее:

public IEnumerable<TEntity> GetPublished<TEntity>()
    where TEntity : IPublishable
{
    return GetQueryable<TEntity>(
        m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now,
        m => m.OrderByDescending(o => o.PublishDate)
    ).ToList();
}

И затем в вашем коде приложения:

service.GetPublished<Article>();