实体框架和DDD - 在将实体传递给业务层

时间:2017-12-20 07:46:34

标签: entity-framework domain-driven-design persistence ddd-repositories ddd-service

假设您有一个域对象:

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 方法。每种方法都需要加载相关数据的不同组合。

  3. 我可以想象可能的解决方案,但所有这些解决方案都远非理想:

    1. 始终加载整个实体 - &gt;但效率低下且往往不可能。

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

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

    4. 每个域方法调用存储库方法来加载其他所需数据(例如: EnsureAnotherEntitiesLoaded ) - &gt;它很难看,因为我的业务逻辑已经意识到相关数据的EF概念。

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

3 个答案:

答案 0 :(得分:0)

好问题:)

我认为“相关数据”本身并不是严格的EF概念。相关数据是NHibernate,Dapper的有效概念,或者即使您使用文件进行存储也是如此。

但是,我主要赞同其他观点。所以这就是我通常所做的:我有一个存储库方法,在你的情况GetById中,它有两个参数:id和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); 
}

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

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

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

DomainService实现中,您可以从repo中读取此特定操作所需的任何内容。这可以避免您在应用层中遇到麻烦。在Business Layer中,您可以更自由地实现读取:使用多个存储库方法读取半满实体,或使用EnsureXXX方法或其他方法。有关操作需要阅读的内容的知识将被放入操作代码中,您不再需要app-layer中的这些知识。

每次出现这样的情况时,都会发出一个关于你的实体的强烈信号。正如krzys所说,该实体没有凝聚力。换句话说,如果您经常需要单独部分实体,则应该拆分该实体。