C# EF6代码首先使用通用存储库、依赖项注入和SoC

C# EF6代码首先使用通用存储库、依赖项注入和SoC,c#,asp.net-mvc,dependency-injection,ef-code-first,entity-framework-6,C#,Asp.net Mvc,Dependency Injection,Ef Code First,Entity Framework 6,在大量阅读并试用了实体框架最新稳定版本(6.1.1)之后 我读到了很多关于是否将存储库与EF6或EF一起使用的矛盾,因为DbContext已经提供了一个存储库和DbSet的UoW,开箱即用 首先让我解释一下我的解决方案在项目方面包含了什么,然后我将回到矛盾的地方 它有一个类库项目和一个asp.netmvc项目。类库项目作为数据访问,其中迁移为代码优先启用 在我的类库项目中,我有一个通用存储库: public interface IRepository<TEntity> where T

在大量阅读并试用了
实体框架
最新稳定版本(6.1.1)之后

我读到了很多关于是否将存储库与
EF6
EF
一起使用的矛盾,因为
DbContext
已经提供了一个存储库和
DbSet
UoW
,开箱即用

首先让我解释一下我的解决方案在项目方面包含了什么,然后我将回到矛盾的地方

它有一个类库项目和一个
asp.netmvc
项目。类库项目作为数据访问,其中
迁移
代码优先
启用

在我的类库项目中,我有一个通用存储库:

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);
}
这将通过构造函数注入将
ApplicationDbContext
注入到上层类(如果适用)

现在回到矛盾上来

如果我们不再需要存储库,因为
EF
已经提供了开箱即用的存储库,那么我们如何进行
关注点分离(标题中简称为SoC)

现在,如果我错了,请纠正我的错误,但在我看来,如果我不添加存储库,我只需要在
asp.net mvc
项目中执行所有数据访问逻辑/计算(例如添加、获取、更新、删除和一些自定义逻辑/计算(特定于实体))


非常感谢您对此事的任何解释。

一点解释将有望消除您的困惑。存储库模式用于抽象数据库连接和查询逻辑。ORM(对象关系映射器,如EF)以这样或那样的形式存在了很长时间,以至于许多人已经忘记或从未有过处理充斥着SQL查询和语句的意大利面代码的巨大乐趣。当时,如果你想查询一个数据库,你实际上要为一些疯狂的事情负责,比如启动一个连接和从以太构造SQL语句。存储库模式的要点是为您提供一个单独的位置,让您远离美丽的原始应用程序代码,从而消除所有这些不好的地方

快进到2014年,Entity Framework和其他ORM是您的存储库。所有的SQL逻辑都被整齐地包装起来,远离窥探者的眼睛,相反,您可以在代码中使用一个很好的编程API。在一个方面,这已经足够抽象了。它唯一没有涉及的是对ORM本身的依赖性。如果您以后决定将实体框架转换为NHibernate或甚至Web API,那么您必须对应用程序进行手术才能做到这一点。因此,添加另一层抽象仍然是一个好主意,但不是一个存储库,或者至少是一个典型的存储库

您拥有的存储库是典型的存储库。它只为实体框架API方法创建代理。调用
repo.Add
,存储库调用
context.Add
。坦率地说,这是荒谬的,这就是为什么包括我在内的许多人说不要将存储库与实体框架结合使用的原因

那你该怎么办?创建服务,或者最好将其称为“类似服务的类”。当人们开始讨论与.NET相关的服务时,突然之间,你谈论的各种事情与我们在这里讨论的内容完全无关。类服务类似于服务,因为它的端点返回特定的数据集或对某个数据集执行非常特定的函数。例如,如果使用典型的存储库,您会发现自己在做以下事情:

articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)
请看,端点方法中整齐地包含了“已发布文章”的所有逻辑。此外,使用存储库,您仍然可以公开底层API。因为基本数据存储是抽象的,所以更容易切换到其他数据存储,但是如果用于查询该数据存储的API发生更改,您仍然会遇到麻烦

更新

设置将非常相似;区别主要在于如何使用服务和存储库。也就是说,我甚至不会让它依赖实体。换句话说,基本上每个上下文都有一个服务,而不是每个实体

一如既往,从一个界面开始:

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

    ...
}
请注意,此方法首先是受保护的。子类可以利用它,但这绝对不应该是公共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 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();
}
public IEnumerable GetPublished()
地点:我可发表
{
返回GetQueryable(
m=>m.Status==PublishStatus.Published&&m.PublishDate m.OrderByDescending(o=>o.PublishDate)
).ToList();
}
然后在应用程序代码中:

service.GetPublished<Article>();
service.GetPublished();

这只是你能得到多抽象的问题。EF-context确实是一个存储库,但它是一个非常固执己见的存储库,具有自己的一组特定于关系数据库的方法。如果你想把它抽象出来,你必须用你自己的通用存储库来包装EF。我在互联网上读到的矛盾是最让我困扰的。但现在我们把事情弄清楚了,我想知道该怎么办。克里斯·普拉特(Chris Pratt)说,这是一项服务。你说呢?服务一直存在(并且由于领域驱动设计的影响而普及了很多),但我不认为它们是为了取代存储库。据我所知,它们只是普通操作和查询的一个幌子,在大多数实现中,您会看到一个
IReposit
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()
    {
        ...
    }
}
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;
}
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>();