gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 21:17:27 26 4
gpt4 key购买 nike

我在网上搜索了一个使用 Entity Framework 的 Repository/Unit of Work 模式的良好实现。我遇到的一切要么在抽象的某个点紧密耦合,要么假设 DbContext工作单元和存储库使用的是共享的,并且应该在整个 HTTP 期间存在。请求(通过依赖注入(inject)的每个请求的实例)。

例如,假设您正在使用服务层的存储库,服务构造函数可能如下所示:

public DirectoryService(IUnitOfWork unitOfWork, ICountryRepository countryRepo, IProvinceRepository provinceRepo)
{
/* set member variables... */
}

工作单元构造函数可能如下所示:

public UnitOfWork(IDbContext context)
{
_context = context;
}

存储库构造函数可能如下所示:

CountryRepository(IDbContext context)
{
_context = context;
}

此解决方案盲目假设依赖注入(inject)正在设置工作单元和存储库以使用每个请求的实例共享相同的 IDbContext。这真的是一个安全的假设吗?

如果您使用每个请求实例的依赖注入(inject),相同的 IDbContext 将被注入(inject)多个工作单元。工作单元不再是原子的,是吗?我可能在一个服务中有待处理的更改,然后在另一个服务中提交这些更改,因为上下文在多个工作单元之间共享。

对我来说,设置 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()); }
}
}

然后问题就变成了,我如何让我的工作单元实现对注入(inject)的存储库可用?我不想假设每个请求都有实例,因为那样我就回到了我开始的同一条船上。

我唯一能想到的就是跟随 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 吗?

最佳答案

你不应该永远不要抽象一个 ORM(它本身就是一个抽象),但是你应该抽象 Persistence。我使用的唯一 UoW 是一个数据库事务,这是一个持久性细节。您不需要同时使用 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 的哪一部分提示您该方法还更新统计信息?)。

总而言之,在急于将 UoW、DbContext、Repositories 作为属性等复杂化之前,您需要更好地设计 BL

关于c# - 使用 Entity Framework 时,存储库应该是工作单元的属性吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23987471/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com