EF6 Code First具有通用存储库和依赖注入和SoC

Quo*_*ter 26 c# asp.net-mvc dependency-injection ef-code-first entity-framework-6

经过大量阅读并尝试使用Entity Framework最新的稳定版本(6.1.1).

我读了大量关于是否要使用存储库与矛盾的EF6EF一般的,因为它DbContext已经提供了一个资源库和DbSetUoW,开箱即用.

让我首先解释一下我的解决方案在项目方面所包含的内容,然后我将回到这个矛盾中.

它有一个类库项目和一个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);
}
Run Code Online (Sandbox Code Playgroud)

以下是它的实现:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这里有几个实体:

public DbSet<User> User{ get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<UserOrder> UserOrders { get; set; }
public DbSet<Shipment> Shipments { get; set; }
Run Code Online (Sandbox Code Playgroud)

我不重复自己,但是,EF6你不再通过存储库了,而是DbContext相反.因此,DI我在asp-net-mvc项目中使用以下方法设置了以下内容Ninject:

private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope();
}
Run Code Online (Sandbox Code Playgroud)

这将ApplicationDbContext在适用的情况下将via构造函数注入注入上层类.

现在又回到了矛盾之中.

如果我们不再需要存储库,因为EF已经提供了开箱即用的功能,我们该怎么办Separation of Concern(标题中缩写为SoC)?

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

关于此事的任何启示都非常感激.

Chr*_*att 40

一点点解释有望解决你的困惑.存储库模式用于抽象出数据库连接和查询逻辑.ORM(对象关系映射器,如EF)已经以这样或那样的形式存在,以至于许多人已经忘记或者从未有过处理充满SQL查询和语句的意大利面条代码的巨大喜悦和乐趣.时间是,如果你想查询数据库,你实际上负责疯狂的事情,如启动连接和实际从以太构造SQL语句.存储库模式的重点是为您提供一个单独的位置来放置所有这些肮脏,远离您美丽的原始应用程序代码.

快进到2014年,Entity Framework和其他ORM 您的存储库.所有的SQL逻辑都整齐地远离你的窥探,而你的代码中有一个很好的编程API.在一个方面,这是足够的抽象.它唯一没有涉及的是对ORM本身的依赖.如果您以后决定要为NHibernate甚至Web API之类的东西切换实体框架,那么您必须对您的应用程序进行手术.因此,添加另一层抽象仍然是一个好主意,但不是一个存储库,或者至少让我们说一个典型的存储库.

您拥有的存储库是典型的存储库.它只是为Entity Framework API方法创建代理.你打电话repo.Add,存储库调用context.Add.坦率地说,这是荒谬的,这就是为什么很多人,包括我自己,都说不要在实体框架中使用存储库.

那么,应该怎么做?创建服务,或者最好称之为"类似服务的类".当开始讨论与.NET相关的服务时,你突然谈到的是与我们在这里讨论的内容完全无关的各种事情.类似服务的类就像一个服务,它具有返回特定数据集或在某些数据集上执行非常特定功能的端点.例如,对于典型的存储库,您会发现自己做的事情如下:

articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)
Run Code Online (Sandbox Code Playgroud)

您的服务类的工作方式如下:

service.GetPublishedArticles();
Run Code Online (Sandbox Code Playgroud)

请参阅,在"端点"方法中,所有符合"已发布文章"条件的逻辑都巧妙地包含在内.此外,使用存储库,您仍然会公开底层API.由于基础数据存储区是抽象的,因此更容易使用其他内容进行切换,但如果用于查询该数据存储区的API发生了变化,那么您仍然可以使用它.

UPDATE

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

一如既往,从界面开始:

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

    ...
}
Run Code Online (Sandbox Code Playgroud)

然后,您的实施:

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

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

    public IEnumerable<Article> GetPublishedArticles()
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,事情开始变得有点毛茸茸.在示例方法中,您可以直接引用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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,此方法首先受到保护.子类可以使用它,但这绝对不应该是公共API的一部分.本练习的重点是不公开可查询.其次,它是通用的.换句话说,它可以处理你抛出的任何类型,只要在它的上下文中存在某些东西.

然后,在我们的小示例方法中,您最终会执行以下操作:

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

这种方法的另一个巧妙技巧是能够使用接口的通用服务方法.假设我希望能够有一种方法来发布任何内容.我可以有一个像这样的界面:

public interface IPublishable
{
    PublishStatus Status { get; set; }
    DateTime PublishDate { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后,任何可发布的实体都将实现此接口.有了这个,你现在可以做:

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();
}
Run Code Online (Sandbox Code Playgroud)

然后在您的应用程序代码中:

service.GetPublished<Article>();
Run Code Online (Sandbox Code Playgroud)