【发布时间】:2014-06-02 04:44:14
【问题描述】:
我在网上搜索,寻找使用实体框架的存储库/工作单元模式的良好实现。我遇到的所有事情要么在抽象的某个点紧密耦合,要么假设工作单元和存储库使用的DbContext 是共享的,并且应该为整个HTTP 请求而存在(每个请求的实例通过依赖注入)。
例如,假设您正在使用服务层的存储库,服务构造函数可能如下所示:
public DirectoryService(IUnitOfWork unitOfWork, ICountryRepository countryRepo, IProvinceRepository provinceRepo)
{
/* set member variables... */
}
工作单元构造函数可能如下所示:
public UnitOfWork(IDbContext context)
{
_context = context;
}
存储库构造函数可能如下所示:
CountryRepository(IDbContext context)
{
_context = context;
}
此解决方案盲目假设依赖注入正在设置工作单元和存储库以使用每个请求的实例共享相同的 IDbContext。这真的是一个安全的假设吗?
如果您对每个请求的实例使用依赖注入,则相同的 IDbContext 将被注入到多个工作单元中。工作单元不再是原子的,是吗?我可能在一个服务中有待处理的更改,然后在另一个服务中提交,因为上下文在多个工作单元之间共享。
对我来说,设置IDbContextFactory 并为每个工作单元获取新的数据库上下文似乎更有意义。
public interface IDbContextFactory
{
IDbContext OpenContext();
}
public class UnitOfWork
{
private IDbContextFactory _factory;
private IDbContext _context;
UnitOfWork(IDbContextFactory factory)
{
_factory = factory;
}
internal IDbContext Context
{
get { return _context ?? (_context = _factory.OpenContext()); }
}
}
然后问题就变成了,我如何使我的工作单元实现可用于注入的存储库?我不想为每个请求假设实例,因为那样我就回到了我开始的同一条船上。
我唯一能想到的就是效仿 Entity Framework 并将存储库 (IDbSet<T>) 成为工作单元 (DbContext) 的一部分。
那么我的工作单元可能如下所示:
public class DirectoryUnitOfWork : IDirectoryUnitOfWork
{
private IDbContextFactory _factory;
private IDbContext _context;
public DirectoryUnitOfWork(IDbContextFactory factory)
{
_factory = factory;
}
protected IDbContext Context
{
get { return _context ?? (_context = _factory.OpenContext()); }
}
public ICountryRepository CountryRepository
{
get { return _countryRepo ?? (_countryRepo = new CountryRepository(Context)); }
}
public IProvinceRepository ProvinceRepository
{
get { return _provinceRepo ?? (_provinceRepo = new ProvinceRepository(Context)); }
}
void Commit()
{
Context.SaveChanges();
}
}
然后我的目录服务开始看起来像这样
public class DirectoryService : IDirectoryService
{
private IDirectoryUnitOfWork _unitOfWork;
public DirectoryService(IDirectoryUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public GetCountry(int id)
{
return _unitOfWork.CountryRepository.GetById(id);
}
public GetProvince(int id)
{
return _unitOfWork.ProvinceRepository.GetById(id);
}
public void AddProvince(Province province)
{
_unitOfWork.ProvinceRepository.Create(province);
Country country = GetCountry(province.CountryId);
country.NumberOfProvinces++; // Update aggregate count
_unitOfWork.Commit();
}
/* ... and so on ... */
}
这似乎需要做很多工作,但使用这种方法会使所有内容都松散耦合且可进行单元测试。我是否错过了更简单的方法,或者这是一个很好的方法if我要抽象出实体框架?
【问题讨论】:
-
您是否真的预见到您将插入 Entity Framework 的直接替代品的任何场景?如果不是,那么抽象出 EF 并跳过箍以使您的代码库 EF 不可知的目的是什么? Entity Framework 已经带有一个强大的工作单元(上下文)和易于使用的存储库(DbSets)。我已经经历了在我自己的自定义 UoW 和存储库实现中重新发明 DbContext 中每个微小功能片段的痛苦,结果发现这种松散耦合没有显着优势。
-
就您的 DI 问题而言,存储库应接受 UoW 作为依赖项,而不是上下文,以便正确地将通过存储库所做的任何更改与特定工作单元相关联。您应该有一个存储库工厂,它可以使用作为参数传递的现有工作单元或新工作单元创建存储库(因为两者都是有效的用例)。
-
阿萨德,感谢您的 cmets。我预计不会有实体框架的替代品;这让我更了解顽固分子在现实世界中是如何做到这一点的。我已经阅读了许多书籍、博客和教程,但似乎没有一个能够完全实现其抽象目标。我喜欢你关于存储库工厂的想法,我以前没有想到过。我可以将 IUnitOfWork 和 IRepositoryFactory 注入到服务中,然后在服务构造函数中创建我需要的存储库。似乎是一个非常可行的选择。
-
使用 EF 的示例工作单元和存储库模式:ludwigstuyck.wordpress.com/2013/03/05/… 和下一个
标签: c# entity-framework repository-pattern unit-of-work