entity framework6代码优先 – 存储库实施是否好?
我即将实施一个entity framework6devise与存储库和工作单元。
有太多的文章,我不知道什么是最好的build议是:例如,我真的喜欢这里实现的模式:由于在这里的文章中build议的原因
但是, Tom Dykstra (Senior Programming Writer on Microsoft's Web Platform & Tools Content Team)
build议,应该在另一篇文章中做:
我订阅Pluralsight
,而且每次在课程中使用时都会以稍微不同的方式实现,所以selectdevise是困难的。
有些人似乎认为这个工作单元已经在DbContext
中实现了,所以我们根本就不需要去实现它。
我意识到这种types的问题已经被问过,这可能是主观的,但我的问题是直接的:
我喜欢第一个(Code Fizzle)文章中的方法,想知道它是否可以更容易维护,并且可以像其他方法一样易于testing,并且可以安全地继续使用?
任何其他意见都是值得欢迎的。
@Chris Hardie是正确的,EF实现了开箱即用的UoW。 然而,很多人忽略了这样一个事实,即EF也实现了一个通用的存储库模式:
var repos1 = _dbContext.Set<Widget1>(); var repos2 = _dbContext.Set<Widget2>(); var reposN = _dbContext.Set<WidgetN>();
…这是一个相当不错的通用资源库实现,内置于工具本身。
为什么要经历创build大量的其他接口和属性的麻烦,当DbContext给你你需要的一切? 如果要在应用程序级接口背后抽象DbContext,并且想要应用命令查询隔离,则可以这样做:
public interface IReadEntities { IQueryable<TEntity> Query<TEntity>(); } public interface IWriteEntities : IReadEntities, IUnitOfWork { IQueryable<TEntity> Load<TEntity>(); void Create<TEntity>(TEntity entity); void Update<TEntity>(TEntity entity); void Delete<TEntity>(TEntity entity); } public interface IUnitOfWork { int SaveChanges(); }
您可以使用这3个接口来访问所有实体,而不必担心将3个或更多不同的存储库注入到与3个或更多实体集一起工作的业务代码中。 当然,你仍然可以使用IoC来确保每个Web请求只有一个DbContext实例,但是所有的3个接口都是由同一个类来实现的,这使得它更容易。
public class MyDbContext : DbContext, IWriteEntities { public IQueryable<TEntity> Query<TEntity>() { return Set<TEntity>().AsNoTracking(); // detach results from context } public IQueryable<TEntity> Load<TEntity>() { return Set<TEntity>(); } public void Create<TEntity>(TEntity entity) { if (Entry(entity).State == EntityState.Detached) Set<TEntity>().Add(entity); } ...etc }
您现在只需将一个接口注入到您的依赖项中,而不pipe需要处理多less个不同的实体:
// NOTE: In reality I would never inject IWriteEntities into an MVC Controller. // Instead I would inject my CQRS business layer, which consumes IWriteEntities. // See @MikeSW's answer for more info as to why you shouldn't consume a // generic repository like this directly by your web application layer. // See http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91 and // http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=92 for more info // on what a CQRS business layer that consumes IWriteEntities / IReadEntities // (and is consumed by an MVC Controller) might look like. public class RecipeController : Controller { private readonly IWriteEntities _entities; //Using Dependency Injection public RecipeController(IWriteEntities entities) { _entities = entities; } [HttpPost] public ActionResult Create(CreateEditRecipeViewModel model) { Mapper.CreateMap<CreateEditRecipeViewModel, Recipe>() .ForMember(r => r.IngredientAmounts, opt => opt.Ignore()); Recipe recipe = Mapper.Map<CreateEditRecipeViewModel, Recipe>(model); _entities.Create(recipe); foreach(Tag t in model.Tags) { _entities.Create(tag); } _entities.SaveChanges(); return RedirectToAction("CreateRecipeSuccess"); } }
这个devise我最喜欢的事情之一是它最大限度地减less了消费者的实体存储依赖 。 在这个例子中, RecipeController
是消费者,但在实际的应用程序中,消费者将是一个命令处理程序。 (对于一个查询处理程序,你通常只会消费IReadEntities
因为你只是想返回数据,而不是改变任何状态。)但是对于这个例子,我们只是使用RecipeController
作为消费者来检查依赖关系的含义:
假设你有一组为上述行为编写的unit testing。 在每个unit testing中,你都新build了Controller,并将模拟传入构造函数。 然后,说客户决定在创build新配方时允许人们创build新的食谱或添加到现有的食谱。
使用每个实体的存储库或每个聚合的存储库接口模式,您必须将新的存储库实例IRepository<Cookbook>
注入到您的控制器构造函数中(或者使用@Chris Hardie的答案,编写代码将另一个存储库附加到UoW实例)。 这将立即使所有其他unit testing中断,并且您将不得不返回修改所有其中的构造代码,再传递另一个模拟实例,并扩大您的依赖数组。 但是,以上所述,所有其他unit testing仍然至less编译。 所有你需要做的就是写更多的testing来覆盖新的烹饪书function。
我(不)很抱歉地说,codefizzle,Dyksta的文章和以前的答案是错误的 。 简单的事实是,他们使用EF实体作为域(商业)对象,这是一个很大的WTF。
更新 :对于一个技术含量较低的解释(用简单的话来说)就是阅读Repository Pattern for Dummies
简而言之,不应将任何存储库接口耦合到任何持久性(ORM)细节。 repo接口只处理对于其他应用程序(域,可能是UI中的呈现)有意义的对象。 很多人(有MS领导包装,意图我怀疑)犯了一个错误,认为他们可以重用他们的EF实体或可以成为他们之上的业务对象。
虽然可能发生,但这种情况非常罕见。 实际上,在数据库规则(即不良build模)之后,会有很多“devise”的域对象。 存储库的目的是将应用程序的其余部分(主要是业务层)与持久性表单分离。
当你的repo处理EF实体(持久性细节)或者它的方法返回IQueryable时,你如何解耦它,这是一个为此目的而具有错误语义的泄漏抽象(IQueryable允许你build立一个查询,这意味着你需要知道持久化细节否定存储库的目的和function)?
一个主要的对象永远不应该知道持久性,EF,join等。它不应该知道你正在使用的数据库引擎,或者如果你使用一个。 与应用程序的其余部分一样,如果您希望将其与持久性详细信息分离 。
存储库接口只知道上层知道什么。 这意味着,通用的域存储库接口看起来像这样
public interface IStore<TDomainObject> //where TDomainObject != Ef (ORM) entity { void Save(TDomainObject entity); TDomainObject Get(Guid id); void Delete(Guid id); }
实现将驻留在DAL中,并将使用EF来处理数据库。 但是实现看起来像这样
public class UsersRepository:IStore<User> { public UsersRepository(DbContext db) {} public void Save(User entity) { //map entity to one or more ORM entities //use EF to save it } //.. other methods implementation ... }
你真的没有一个具体的通用知识库。 具体的通用库的唯一用法是当任何域对象以序列化的forms存储在一个键值如表中。 ORM并非如此。
那么查询呢?
public interface IQueryUsers { PagedResult<UserData> GetAll(int skip, int take); //or PagedResult<UserData> Get(CriteriaObject criteria,int skip, int take); }
UserData是适合查询上下文使用的读取/查看模型。
如果您不介意DAL知道视图模型,那么您可以直接使用EF查询查询处理程序 ,在这种情况下,您将不需要任何查询回购。
结论
- 您的业务对象不应该知道EF实体。
- 版本库将使用一个ORM ,但它永远不会将ORM公开给应用程序的其余部分,所以repo接口将只使用域对象或视图模型(或任何其他不是持久性细节的应用程序上下文对象)
- 你不告诉回购如何做它的工作,即从来没有使用IQueryable回购界面
- 如果你只是想以更简单/酷的方式使用数据库,而且你正在处理一个你不需要的简单的CRUD应用程序来保持关注的分离,那么一起跳过版本库直接使用EF的一切数据。 该应用程序将紧密联系到EF,但至less你会削减中间人,这将是有意的不是错误的。
请注意,以错误的方式使用存储库将会使其无效,并且您的应用程序仍将与持久性(ORM)紧密耦合。
如果你相信ORM在那里神奇地存储你的域对象,那不是。 ORM的目的是模拟关系表上的OOP存储。 它与持久性有关,与域无关,因此不要使用持久化外的ORM。
DbContext
确实是以工作单元模式构build的。 它允许其所有实体与我们一起共享相同的上下文。 这个实现是DbContext
内部 。
但是,应该注意的是,如果您实例化两个DbContext
对象,它们都不会看到它们每个正在跟踪的其他实体。 它们彼此绝缘,这可能是有问题的。
当我构build一个MVC应用程序时,我想确保在请求过程中,我的所有数据访问代码都是从单个DbContext
。 为了达到这个目标,我把工作单元作为DbContext
外部的模式。
这是我正在build设的烧烤食谱应用程序中的工作单元对象:
public class UnitOfWork : IUnitOfWork { private BarbecurianContext _context = new BarbecurianContext(); private IRepository<Recipe> _recipeRepository; private IRepository<Category> _categoryRepository; private IRepository<Tag> _tagRepository; public IRepository<Recipe> RecipeRepository { get { if (_recipeRepository == null) { _recipeRepository = new RecipeRepository(_context); } return _recipeRepository; } } public void Save() { _context.SaveChanges(); } **SNIP**
我把我所有的存储库,都注入了相同的DbContext
,我的工作单元对象。 只要从工作单元请求任何存储库,我们就可以放心,我们所有的数据访问代码都将使用相同的DbContext
进行pipe理 – 真棒!
如果我在MVC应用程序中使用它,我将确保在整个请求中使用工作单元,并在控制器中实例化并在整个操作中使用它:
public class RecipeController : Controller { private IUnitOfWork _unitOfWork; private IRepository<Recipe> _recipeService; private IRepository<Category> _categoryService; private IRepository<Tag> _tagService; //Using Dependency Injection public RecipeController(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; _categoryRepository = _unitOfWork.CategoryRepository; _recipeRepository = _unitOfWork.RecipeRepository; _tagRepository = _unitOfWork.TagRepository; }
现在在我们的行动中,我们可以放心,我们所有的数据访问代码将使用相同的DbContext
:
[HttpPost] public ActionResult Create(CreateEditRecipeViewModel model) { Mapper.CreateMap<CreateEditRecipeViewModel, Recipe>().ForMember(r => r.IngredientAmounts, opt => opt.Ignore()); Recipe recipe = Mapper.Map<CreateEditRecipeViewModel, Recipe>(model); _recipeRepository.Create(recipe); foreach(Tag t in model.Tags){ _tagRepository.Create(tag); //I'm using the same DbContext as the recipe repo! } _unitOfWork.Save();
在互联网上search我发现这个http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework/这是一个关于由Jon Smith的存储库模式的有用性的2部分文章。 第二部分着重于解决scheme。 希望能帮助到你!
仓库与工作模式实施单位是一个不好的回答你的问题。
entity framework的DbContext是由微软根据工作模式单元来实现的。 这意味着context.SaveChanges事务性地保存您的更改。
DbSet也是Repository模式的一个实现。 不要构build您可以执行的存储库:
void Add(Customer c) { _context.Customers.Add(c); }
创build一个单线程的方法,你可以在服务里面做什么?
没有任何好处,现在没有人将EF ORM更改为另一个ORM。
你不需要那个自由
克里斯·哈迪争辩说,可能会实例化多个上下文对象,但已经这样做,你做错了…
只需使用您喜欢的IOC工具,并通过Http请求设置MyContext,就可以了。
以ninject为例:
kernel.Bind<ITeststepService>().To<TeststepService>().InRequestScope().WithConstructorArgument("context", c => new ITMSContext());
运行业务逻辑的服务获取注入的上下文。
只要保持简单愚蠢:-)
我总是先用EF代码来使用UoW。 我发现它更高效,更容易pipe理你的上下文,以防止内存泄漏等。 您可以在我的github上find我的解决方法的示例: http : //www.github.com/stefchri RADAR项目。
如果您有任何问题,随时问他们。