EF6 Code First具有通用存储库和dependency injection和SoC

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

我正在阅读很多关于是否一般使用DbContextEF存储库的矛盾,因为它的DbContext已经提供了一个存储库并且DbSet UoW ,开箱即用。

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

它有一个类库项目和一个asp.net-mvc项目。 类lib项目是数据访问,并且为Code First启用了Migrations

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

 public interface IRepository where TEntity : class { IEnumerable Get(); TEntity GetByID(object id); void Insert(TEntity entity); void Delete(object id); void Update(TEntity entityToUpdate); } 

以下是它的实现:

 public class Repository where TEntity : class { internal ApplicationDbContext context; internal DbSet dbSet; public Repository(ApplicationDbContext context) { this.context = context; this.dbSet = context.Set(); } public virtual IEnumerable Get() { IQueryable 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{ get; set; } public DbSet Orders { get; set; } public DbSet UserOrders { get; set; } public DbSet Shipments { get; set; } 

我不重复自己,但是,使用EF6你不再传递存储库,而是使用DbContext 。 所以对于DI我使用Ninjectasp-net-mvc项目中设置了以下内容:

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

这将通过构造函数注入将ApplicationDbContext注入适用的上层类。

现在又回到了矛盾之中。

如果我们不再需要存储库,因为EF已经开箱即用,我们如何进行Separation of Concern (标题中缩写为SoC)?

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

对这件事情的任何启示都非常感激。

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

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

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

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

 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
GetPublishedArticles(); ... }

然后,您的实施:

 public class EntityFrameworkService : IService where TContext : DbContext { protected readonly TContext context; public EntityFrameworkService(TContext context) { this.context = context; } public IEnumerable
GetPublishedArticles() { ... } }

然后,事情开始变得有点毛茸茸。 在示例方法中,您可以直接引用DbSet ,即context.Articles ,但这意味着有关上下文中DbSet名称的知识。 最好使用context.Set() ,以获得更大的灵活性。 在我开火车太多之前,我想指出为什么我命名这个EntityFrameworkService 。 在您的代码中,您只能引用您的IService接口。 然后,通过您的dependency injection容器,您可以替换EntityFrameworkService 。 这开启了创建其他服务提供商的能力,例如WebApiService等。

现在,我喜欢使用一个返回可查询的单一受保护方法,我的所有服务方法都可以使用它。 通过var dbSet = context.Set();每次都必须初始化DbSet实例,这就摆脱了很多var dbSet = context.Set(); 。 这看起来有点像:

 protected virtual IQueryable GetQueryable( Expression> filter = null, Func, IOrderedQueryable> orderBy = null, string includeProperties = null, int? skip = null, int? take = null) where TEntity : class { includeProperties = includeProperties ?? string.Empty; IQueryable query = context.Set(); 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
GetPublishedArticles() { return GetQueryable
( 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 GetPublished() where TEntity : IPublishable { return GetQueryable( m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now, m => m.OrderByDescending(o => o.PublishDate) ).ToList(); } 

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

 service.GetPublished
();