C# · 12月 25, 2021

c# – 使用Entity Framework时,存储库应该是工作单元的属性吗?

我已经在网上搜索了使用Entity Framework实现存储库/工作单元模式的良好实现.我遇到的所有内容要么在抽象中的某个点紧密耦合,要么假设工作单元和存储库使用的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()); } }}

然后问题变成了,如何使我的工作单元实现可用于注入的存储库?我不想假设每个请求的实例,因为那时我就回到了我开始的同一条船上.

我唯一能想到的是遵循实体框架的主导并使存储库(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 … */}

这似乎很多工作,但使用这种方法会使所有东西松散耦合并且可以进行单元测试.我是否错过了一种更简单的方法,或者如果我要抽象出实体框架,这是一个很好的方法吗?

解决方法 你永远不应该抽象ORM(它本身就是一个抽象),但是你应该抽象出持久性.我使用的唯一UoW是db事务,这是一个持久性细节.您不需要一起使用UoW和Repository.你应该想想你是否真的需要这些东西.

就个人而言,我默认使用存储库,因为持久性是我在应用程序中做的最后一件事.我不关心模式本身,我关心将我的BL或UI与DAL分离.您的上层(除了DAL之外的所有内容,从依赖性的角度来看是最低层)应始终了解抽象,以便您可以在DAL实现中尽可能地使用它.

许多开发人员不知道的一个技巧是设计模式(特别是建筑模式)应该首先被视为高级原则,而作为技术诀窍第二.简单地说,最重要的是模式试图实现的好处(原则,更大的图片),而不是它的实际实现(“低级”细节).

问问自己为什么BL首先要知道UoW. BL只知道处理业务对象的抽象.而且你从不和整个BL一起工作,你总是处于一个特定的BL环境中.您的DirectoryService似乎正在为自己的利益做很多事情.更新统计数据看起来不像添加新省份所属的相同环境.另外,为什么你需要UoW查询?

我看到的一个错误是开发人员急于编写任何代码(附带设计模式),当他们没有做最重要的部分时:设计本身.当您使用不正确的设计时,会出现问题并开始寻找解决方法.其中之一就是具有Repository属性的UoW,它需要像BL这样的高层来了解业务问题.现在BL需要知道你正在使用一个UoW,一个对DAL来说很棒的低级模式,对于BL来说不是那么好.

顺便说一下,当你处理“读”情况时,UoW从来没有理由进行查询,UoW只适用于’写’.我没有提到EF或任何ORM,因为它们并不重要,您的BL服务(目录服务)已经被不正确设计所需的基础结构细节破坏.请注意,有时您需要妥协以实现解决方案,但事实并非如此.

正确的设计意味着您了解有限的上下文(是的,您可以应用DDD概念,无论您想要做多少DDD),并且不要将可能使用相同数据的所有内容放在一个地方.您有特定的用例上下文和计数省(演示文稿/报告详细信息)实际上是一个与添加省不同的用例.

添加省,然后发布一个事件,向处理程序发出信号以更新统计信息是一种更优雅,可维护的解决方案.也不需要UoW.

你的代码看起来像这样

public class DirectoryService : IDirectoryService{ public DirectoryService(IProvinceRepository repo,IDispatchMessage bus) { //assign fields } /* other stuff */ //maybe province is an input model which is used by the service to create a business Province? public void AddProvince(Province province) { _repo.Save(province); _bus.Publish( new ProvinceCreated(province)); }} public class StatsUpdater:ISubscribeTo<ProvinceCreated> /* and other stat trigger events */ { public void Handle(ProvinceCreated evnt) { //update stats here } }

在某种程度上它简化了事情,换句话说你可能会让事情复杂化.实际上,这是一种可维护的方法,因为统计信息更新程序可以订阅许多事件,但逻辑仅保留在一个类中.而DirectoryService只做了它假定的事情(AddProvince名称的哪一部分暗示你该方法也更新了统计数据?).

总之,你需要更好地设计BL,然后用UoW,DbContext,Repositories作为属性等来惹恼你的生活.