问题描述
我即将实施一个具有存储库和工作单位的实体框架6设计。
有很多文章,我不知道什么最好的建议是:例如我真的喜欢这里实现的模式:由于文章中提出的原因
然而, Tom Dykstra (Microsoft的Web平台和工具内容小组的高级编程人员)
建议应该在另一篇文章中完成:
我订阅了 Pluralsight
,它在几乎每次在一个当然,所以选择一个设计很困难。
有些人似乎是su ggest该工作单元已经通过 DbContext
实现,如,所以我们应该'
我意识到这种类型的问题已经被问过,这可能是主观的,但我的问题是直接的:
我喜欢第一个(Code Fizzle)文章中的方法,想知道它是否可以更容易维护,并且像其他方法一样容易测试,并且可以安全地继续下去?
任何其他意见都比较受欢迎。
@Chris Hardie是对的, EF实现UoW开箱即用。然而,很多人忽视了这样一个事实:EF也实现了一个通用的存储库模式:
var repos1 = _dbContext.Set< ; WIDGET1>();
var repos2 = _dbContext.Set< Widget2>();
var reposN = _dbContext.Set< WidgetN>();
...这是一个非常好的通用存储库实现,内置于工具本身。 / p>
为什么在创建大量其他接口和属性时遇到麻烦,当DbContext为您提供所需的一切?如果要抽象应用程序级别接口中的DbContext,并且要应用命令查询隔离,则可以执行以下简单操作:
public interface IReadEntities
{
IQueryable< TEntity>查询< TEntity>();
}
public interface IWriteEntities:IReadEntities,IUnitOfWork
{
IQueryable< TEntity>负载< 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>查询< TEntity>()
{
return Set< TEntity>()。AsNoTracking(); //从上下文中分离结果
}
public IQueryable< TEntity>加载< TEntity>()
{
return Set< TEntity>();
}
public void创建< TEntity>(TEntity entity)
{
if(Entry(entity).State == EntityState.Detached)
设置< TEntity>()添加(实体);
}
...等
}
您现在只需要将一个界面注入到依赖关系中,而不管需要使用多少个不同的实体:
//注意:实际上我永远不会将IWriteEntities注入到MVC控制器中。
//相反,我将注入我的CQRS业务层,它消耗了IWriteEntities。
//请参阅@ MikeSW的答案,了解更多信息,为什么不应该直接在Web应用程序层上使用这样的
//通用存储库。
//参见http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91和
// http://www.cuttingedge.it/blogs/steven /pivot/entry.php?id=92了解更多信息
//什么是消费IWriteEntities / IReadEntities的$ CQ $业务层
//(由MVC控制器消耗)可能看起来像。
public class RecipeController:Controller
{
private readonly IWriteEntities _entities;
//使用依赖注入
public RecipeController(IWriteEntities entities)
{
_entities = entities;
}
[HttpPost]
public ActionResult创建(CreateEditRecipeViewModel模型)
{
Mapper.CreateMap< CreateEditRecipeViewModel,Recipe>()
.ForMember(r => r.IngredientAmounts,opt => opt.Ignore());
食谱配方= Mapper.Map< CreateEditRecipeViewModel,Recipe>(model);
_entities.Create(recipe);
foreach(model.Tags中的标签t){
_entities.Create(tag);
}
_entities.SaveChanges();
return RedirectToAction(CreateRecipeSuccess);
}
}
我最喜欢的这个设计之一是它最小化消费者 的实体存储依赖关系。在这个例子中, RecipeController
是消费者,但是在真正的应用程序中,消费者将是一个命令处理程序。 (对于一个查询处理程序,你通常只需要消耗 IReadEntities
,只因为你只想返回数据,而不是突变任何状态。)但是对于这个例子,我们来使用 RecipeController
作为消费者,以检查依赖性影响:
假设您为上述操作编写了一套单元测试。在这些单元测试中,您将新建控制器,将模拟传递给构造函数。然后,说你的客户决定他们想让人们创建一个新的食谱,或添加到一个现有的,当创建一个新的食谱。
使用每个实体或每个聚合存储库的接口模式,您将不得不注入新的存储库实例 IRepository< Cookbook>
到你的控制器构造函数(或使用@Chris Hardie的答案,编写代码将另一个存储库附加到UoW实例)。这将立即使您的所有其他单元测试中断,您将不得不返回修改所有的构造代码,传递另一个模拟实例,并扩大您的依赖数组。但是,以上所有的其他单元测试至少会编译。所有你需要做的是写额外的测试来覆盖新的食谱功能。
I am about to implement an Entity Framework 6 design with a repository and unit of work.
There are so many articles around and I'm not sure what the best advice is: For example I realy like the pattern implemented here: for the reasons suggested in the article here
However, Tom Dykstra (Senior Programming Writer on Microsoft's Web Platform & Tools Content Team)
suggests it should be done in another article: here
I subscribe to Pluralsight
, and it is implemented in a slightly different way pretty much every time it is used in a course so choosing a design is difficult.
Some people seem to suggest that unit of work is already implemented by DbContext
as in this post, so we shouldn't need to implement it at all.
I realise that this type of question has been asked before and this may be subjective but my question is direct:
I like the approach in the first (Code Fizzle) article and wanted to know if it is perhaps more maintainable and as easily testable as other approaches and safe to go ahead with?
Any other views are more than welcome.
@Chris Hardie is correct, EF implements UoW out of the box. However many people overlook the fact that EF also implements a generic repository pattern out of the box too:
var repos1 = _dbContext.Set<Widget1>();
var repos2 = _dbContext.Set<Widget2>();
var reposN = _dbContext.Set<WidgetN>();
...and this is a pretty good generic repository implementation that is built into the tool itself.
Why go through the trouble of creating a ton of other interfaces and properties, when DbContext gives you everything you need? If you want to abstract the DbContext behind application-level interfaces, and you want to apply command query segregation, you could do something as simple as this:
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();
}
You could use these 3 interfaces for all of your entity access, and not have to worry about injecting 3 or more different repositories into business code that works with 3 or more entity sets. Of course you would still use IoC to ensure that there is only 1 DbContext instance per web request, but all 3 of your interfaces are implemented by the same class, which makes it easier.
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
}
You now only need to inject a single interface into your dependency, regardless of how many different entities it needs to work with:
// 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");
}
}
One of my favorite things about this design is that it minimizes the entity storage dependencies on the consumer. In this example the RecipeController
is the consumer, but in a real application the consumer would be a command handler. (For a query handler, you would typically consume IReadEntities
only because you just want to return data, not mutate any state.) But for this example, let's just use RecipeController
as the consumer to examine the dependency implications:
Say you have a set of unit tests written for the above action. In each of these unit tests, you new up the Controller, passing a mock into the constructor. Then, say your customer decides they want to allow people to create a new Cookbook or add to an existing one when creating a new recipe.
With a repository-per-entity or repository-per-aggregate interface pattern, you would have to inject a new repository instance IRepository<Cookbook>
into your controller constructor (or using @Chris Hardie's answer, write code to attach yet another repository to the UoW instance). This would immediately make all of your other unit tests break, and you would have to go back to modify the construction code in all of them, passing yet another mock instance, and widening your dependency array. However with the above, all of your other unit tests will still at least compile. All you have to do is write additional test(s) to cover the new cookbook functionality.
这篇关于实体框架6代码第一 - 存储库实现是好的吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!