2

假设您有一个域对象:

class ArgumentEntity
{
    public int Id { get; set; }
    public List<AnotherEntity> AnotherEntities { get; set; }
}

你有 ASP.NET Web API 控制器来处理它:

[HttpPost("{id}")]
public IActionResult DoSomethingWithArgumentEntity(int id)
{
    ArgumentEntity entity = this.Repository.GetById(id);
    this.DomainService.DoDomething(entity);
    ...
}

它接收实体标识符,通过 id 加载实体并使用域服务在其上执行一些业务逻辑。

问题: 这里的问题是相关数据。ArgumentEntity具有AnotherEntities集合,仅当您通过 Include/Load 方法明确要求这样做时,EF 才会加载该集合。 DomainService是业务层的一部分,应该对持久性、相关数据和其他 EF 概念一无所知。

DoDomething服务方法期望接收带有加载的 AnotherEntities集合的ArgumentEntity实例。您会说 - 这很简单,只需在Repository.GetById中包含所需的数据并使用相关集合加载整个对象。

现在让我们从简化示例回到大型应用程序的现实:

  1. ArgumentEntity要复杂得多。它包含多个相关集合,并且相关实体也有其相关数据。

  2. 您有多种DomainService方法。每种方法都需要加载相关数据的不同组合。

我可以想象可能的解决方案,但它们都远非理想:

  1. 总是加载整个实体 -> 但它效率低下而且通常是不可能的。

  2. 添加几个存储库方法:GetByIdOnlyHeader、GetByIdWithAnotherEntities、GetByIdFullData以在控制器中加载特定的数据子集 -> 但控制器会知道要加载哪些数据并将其传递给每个服务方法。

  3. 添加几个存储库方法:GetByIdOnlyHeader、GetByIdWithAnotherEntities、GetByIdFullData以在每个服务方法中加载特定的数据子集 -> 效率低下,每个服务方法调用的 sql 查询。如果您为一个控制器操作调用 10 个服务方法怎么办?

  4. 每个域方法调用存储库方法来加载额外的所需数据(例如:EnsureAnotherEntitiesLoaded)-> 这很难看,因为我的业务逻辑意识到相关数据的 EF 概念。

问题: 在将实体传递到业务层之前,您将如何解决加载实体所需的相关数据的问题?

4

3 回答 3

0

好问题:)

我认为“相关数据”本身并不是一个严格的 EF 概念。相关数据对于 NHibernate、Dapper 或者即使您使用文件进行存储都是一个有效的概念。

不过,我主要同意其他观点。所以这就是我通常做的事情:在你的情况下,我有一个存储库方法,GetById它有两个参数: id 和 a params Expression<Func<T,object>>[]。然后,存储库中,我执行包含。这样,您的业务逻辑就不会对 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); 
}
于 2017-12-20T08:25:07.293 回答
0

在 DDD 的上下文中,您似乎错过了项目中导致您遇到此问题的一些建模方面。你写的实体看起来不是高度凝聚力的。如果不同的流程(服务方法)需要不同的相关数据,那么您似乎还没有找到合适的聚合。考虑将您的实体分成几个具有高内聚力的聚合。然后,与特定聚合相关的所有进程将需要此聚合包含的所有或大部分数据。

所以我不知道你的问题的答案,但如果你有能力退后几步并重构你的模型,我相信你不会遇到这样的问题。

于 2017-12-20T09:16:47.247 回答
0

在您的示例中,我可以看到DoSomethingWithArgumentEntity显然属于应用层的方法。该方法具有Repository属于数据访问层的调用。我认为这种情况不符合经典的分层架构——你不应该直接从应用层调用 DAL。

所以你的代码可以用另一种方式重写:

[HttpPost("{id}")]
public IActionResult DoSomethingWithArgumentEntity(int id)
{
    this.DomainService.DoDomething(id);
    ...
}

DomainService实现中,您可以从 repo 中读取此特定操作所需的任何内容。这避免了您在应用层的麻烦。在业务层中,您将有更多的自由来实现读取:使用服务器存储库方法读取半满实体,或者使用 EnsureXXX 方法或其他方式。关于操作需要阅读的知识将被放入操作的代码中,您在应用层不再需要这些知识。

每次出现这种情况时,这都是一个强烈的信号,表明您的实体没有经过预先设计。正如 krzys 所说,实体没有凝聚力的部分。换句话说,如果您经常单独需要实体的一部分,则应该拆分该实体。

于 2017-12-20T13:24:48.837 回答