Entity framework 实体框架和DDD—在将实体传递到业务层之前加载所需的相关数据
假设您有一个域对象:Entity framework 实体框架和DDD—在将实体传递到业务层之前加载所需的相关数据,entity-framework,domain-driven-design,persistence,ddd-repositories,ddd-service,Entity Framework,Domain Driven Design,Persistence,Ddd Repositories,Ddd Service,假设您有一个域对象: class ArgumentEntity { public int Id { get; set; } public List<AnotherEntity> AnotherEntities { get; set; } } 它接收实体标识符,按id加载实体,并使用域服务在其上执行一些业务逻辑 问题: 这里的问题在于相关数据。ArgumentEntity有一个实体集合,只有当您通过Include/Load方法明确请求时,EF才会加载该集合。 Doma
class ArgumentEntity
{
public int Id { get; set; }
public List<AnotherEntity> AnotherEntities { get; set; }
}
它接收实体标识符,按id加载实体,并使用域服务在其上执行一些业务逻辑
问题:
这里的问题在于相关数据。ArgumentEntity有一个实体集合,只有当您通过Include/Load方法明确请求时,EF才会加载该集合。
DomainService是业务层的一部分,不应了解持久性、相关数据和其他EF概念
DoDomething服务方法希望接收ArgumentEntity实例,该实例加载了另一个实体集合。
您可能会说-这很简单,只需在Repository.GetById中包含所需的数据,并用相关集合加载整个对象
现在让我们从简化的示例回到大型应用程序的实际情况:
GetById
,它有两个参数:id和params表达式[]
。然后,在存储库中,我执行include。通过这种方式,您的业务逻辑中不依赖于EF(如果需要,可以手动解析表达式以获得另一种类型的数据存储框架),并且每个BLL方法都可以自行决定实际需要哪些相关数据
public async Task<ArgumentEntity> GetByIdAsync(int id, params Expression<Func<ArgumentEntity,object>>[] includes)
{
var baseQuery = ctx.ArgumentEntities; // ctx is a reference to your context
foreach (var inlcude in inlcudes)
{
baseQuery = baseQuery.Include(include);
}
return await baseQuery.SingleAsync(a=>a.Id==id);
}
public异步任务GetByIdAsync(int-id,params表达式[]包含)
{
var baseQuery=ctx.ArgumentEntities;//ctx是对上下文的引用
foreach(变量inlcude in inlcudes)
{
baseQuery=baseQuery.Include(Include);
}
返回wait wait baseQuery.SingleAsync(a=>a.Id==Id);
}
在DDD的上下文中,您似乎遗漏了项目中导致出现此问题的一些建模方面。你所写的实体看起来没有很强的凝聚力。如果不同的流程(服务方法)需要不同的相关数据,那么您似乎还没有找到合适的聚合。考虑将实体分割成具有高内聚性的多个集合。然后,与特定聚合相关的所有进程都将需要此聚合包含的全部或大部分数据
所以我不知道你的问题的答案,但是如果你能后退几步并重构你的模型,我相信你不会遇到这样的问题。在你的例子中,我可以看到方法
DoSomethingWithArgumentEntity
,它显然属于应用层。此方法调用了属于数据访问层的存储库。我认为这种情况不符合经典的分层体系结构——您不应该直接从应用层调用DAL
因此,您的代码可以用另一种方式重写:
[HttpPost("{id}")]
public IActionResult DoSomethingWithArgumentEntity(int id)
{
this.DomainService.DoDomething(id);
...
}
在DomainService
实现中,您可以从repo读取此特定操作所需的任何内容。这避免了您在应用层遇到的麻烦。在业务层,您将有更多的自由来实现读取:使用服务器存储库方法读取半满实体,或者使用EnsureXXX方法,或者其他方法。关于操作所需阅读内容的知识将被放入操作代码中,在应用层中您不再需要这些知识
每次出现这样的情况,都是一个强烈的信号,表明你们的实体并不是预先设计好的。正如krzys所说,实体没有内聚部分。换句话说,如果您经常需要一个实体的各个部分,您应该拆分该实体。感谢您分享您的经验。这是否意味着您将标识符传递给BLL方法,并且每个BLL方法调用存储库来加载具有所需相关数据的实体?基本上是的。“传递id”可能意味着很多事情:有时它意味着BLL方法有一个实际的参数,有时它意味着BLL方法通过某种注入的服务访问id。但在一天结束时,每个BLL方法都会以某种方式知道id,然后他们自己决定需要加载哪些属性,并将相应的表达式传递给存储库方法。我想我应该澄清一下:这里的存储库是一个抽象,它属于IRepository类型,并通过DI注入。存储库接口是业务逻辑的一部分,而存储库实现是DAL的一部分。所以DAL不是从应用层调用的。问题1:传递标识符
[HttpPost("{id}")]
public IActionResult DoSomethingWithArgumentEntity(int id)
{
this.DomainService.DoDomething(id);
...
}