我即将使用存储库和工作单元实现 Entity Framework 6 设计。
周围有很多文章,我不确定最好的建议是什么:例如,我真的很喜欢这里实现的模式:出于文章 here 中建议的原因
但是,Tom Dykstra(Microsoft Web 平台和工具内容团队的高级编程作家)
建议应该在另一篇文章中完成此操作:here
我订阅了 Pluralsight
,而且每次在类(class)中使用它时,它的实现方式都略有不同,因此选择设计很困难。
有些人似乎认为工作单元已经由 DbContext
实现,如 post 所示。 ,所以我们根本不需要实现它。
我意识到以前曾有人问过此类问题,这可能是主观的,但我的问题是直接的:
我喜欢第一篇(代码失败)文章中的方法,并想知道它是否比其他方法更易于维护、更容易测试,并且可以安全地继续使用?
欢迎任何其他意见。
最佳答案
@Chris Hardie 是正确的,EF 开箱即用地实现了 UoW。然而,许多人忽视了这样一个事实:EF 也实现了开箱即用的通用存储库模式:
var repos1 = _dbContext.Set<Widget1>();
var repos2 = _dbContext.Set<Widget2>();
var reposN = _dbContext.Set<WidgetN>();
...这是一个非常好的通用存储库实现,内置于工具本身中。
当 DbContext 为您提供所需的一切时,为什么还要费力创建大量其他接口(interface)和属性呢?如果您想要抽象应用程序级接口(interface)后面的 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 个接口(interface)进行所有实体访问,而不必担心将 3 个或更多不同的存储库注入(inject)到与 3 个或更多实体集一起使用的业务代码中。当然,您仍然可以使用 IoC 来确保每个 Web 请求只有 1 个 DbContext 实例,但是所有 3 个接口(interface)都由同一个类实现,这使得它更容易。
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
}
您现在只需将单个接口(interface)注入(inject)到您的依赖项中,无论它需要使用多少个不同的实体:
// 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");
}
}
关于此设计,我最喜欢的事情之一是它最大限度地减少了对消费者的实体存储依赖。在此示例中 RecipeController
是消费者,但在实际应用程序中,消费者将是命令处理程序。 (对于查询处理程序,您通常会使用 IReadEntities
只是因为您只想返回数据,而不是改变任何状态。)但对于此示例,我们只使用 RecipeController
作为消费者检查依赖性影响:
假设您有一组为上述操作编写的单元测试。在每个单元测试中,您都会新建 Controller ,并将模拟传递到构造函数中。然后,假设您的客户决定允许人们创建一本新的食谱或在创建新食谱时添加到现有的食谱中。
使用每个实体存储库或每个聚合存储库接口(interface)模式,您必须注入(inject)一个新的存储库实例 IRepository<Cookbook>
到你的 Controller 构造函数中(或者使用 @Chris Hardie 的答案,编写代码将另一个存储库附加到 UoW 实例)。这将立即使所有其他单元测试中断,并且您必须返回修改所有单元测试中的构造代码,传递另一个模拟实例,并扩大您的依赖项数组。然而,有了上述内容,所有其他单元测试至少仍然可以编译。您所要做的就是编写额外的测试来涵盖新的说明书功能。
关于entity-framework - Entity Framework 6 代码优先 - 存储库实现是一个好的实现吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21349950/