首页 > 解决方案 > 带有 ThenIncludes 的通用存储库模式

问题描述

我的解决方案有几个层,其中之一是DataAccess我实现存储库模式的层。

我的主要重点是EntityFramework DataAccess图层中引用。

我需要在我的查询中包含关系,所以我调整了我的查询方法来接收Includes作为输入。

/// <inheritdoc/>
public IQueryable<T> AsQueryable(params Expression<Func<T, object>>[] includes)
{
    var query = _dbSet.AsQueryable();

    if (includes != null)
    {
        query = includes.Aggregate(query,
                  (current, include) => current.Include(include));
    }

    return query;
}

示例使用:

// Books :: ICollection<Book>
var query = _repository.AsQueryable(e => e.Books);

使用上面的示例,例如,我如何Book -> Author在查询中包含关系?该属性Books是一个集合,因此我无法引用该Author属性。

示例:.AsQueryable(e => e.Books, e => Books.Author).AsQueryable(e => e.Books.ChildInclude(b => b.Author))

标签: c#entity-framework.net-core

解决方案


我的主要关注点是 EntityFramework 仅在 DataAccess 层中被引用。

实现此目的的唯一安全方法是,我建议您在必须与 EF 知识隔离的代码和可以了解 EF 的代码之间定义一个边界。这意味着只有物化的 DTO 或非实体模型才能跨越此边界。这通常适用于您希望多个消费者以隔离且相同的方式访问数据的情况。(即网站+API)即使这样,这也会在灵活性和性能方面进行权衡。该边界通常不是存储库,而是可以了解 EF、管理 DbContext 范围、(通过工作单元或管理何时SaveChanges()调用等)的服务访问利用 的存储库IQueryable<TEntity>,然后将结果投射到为消费者具体化List<TDTO>或实例化。TDO

在跟踪它们的 DBContext 范围之外传递实体会导致系统内的各种复杂性和问题。这意味着设计一个分离层来返回 DTO 或IEnumerable<TDTO>而不是IQueryable<TEntity>甚至IEnumerable<TEntity>. 任何接受实体的代码都应始终具有完整的实体图或可完成的实体图。(可延迟加载)当接受实体图的函数可能会或可能不会获得引用或填充所有属性并猜测某些获取或构造的实体是否“足够完整”以传递给现有方法时,错误条件已经成熟。

如果这种抽象只是“非常需要”来满足个人偏好,或者对可能需要您用其他机制替换 EF 的未来需求的一些不确定的担忧,我的建议将是简单的“不要”。通过实现模式以从 EF 抽象代码,您将绝对的性能限制和通常显着的复杂性强加到您的系统中,而不会立即受益。利用IQueryable允许您的代码针对数据层构建高效和高性能的查询。将对象具体化以传回消费层将意味着大量非常相似的代码,或者非常复杂的代码来处理过滤、预加载、投影、排序和分页,并且通常返回的数据比更直接的方法返回的数据多得多.

使用魔术字符串或表达式可以促进急切加载。表达式也可以促进过滤和排序。但是,开发复杂的基于表达式的抽象的一个重要警告是,虽然这可能会将调用代码与 EF 引用隔离开来,但它仍然不会将该代码与 EF 特定的规则隔离开来。例如,这些表达式需要了解并遵守 EF 规则,例如不调用函数或任何最终无法转换为 SQL 的内容。

像这样的抽象通常会导致性能问题,由于性能问题,人们更希望以后用另一种 ORM 或数据访问方法替换 EF。它变成了一个自我实现的预言。


推荐阅读