【问题标题】:Entity Framework dependencies loading实体框架依赖加载
【发布时间】:2020-09-30 12:07:05
【问题描述】:

关于如何避免通过 Entity Framework(第 6 版 - 还不是 Core,遗憾的是)查询的数据出现空错误,我有一个长期亟待解决的问题。

假设您有一个表 Employees,它与另一个表 EmployeePayments 有关系(一个员工有很多员工付款)。

在您的Employee 域对象上,您创建一个属性TotalPayments,它依赖于您为该对象加载了EmployeePayments

我尽量确保每次进行查询时,我都会“包含”依赖项,例如:

var employees = context.Employees.Include(e => e.EmployeePayments);

问题是,我在这个地方有很多查询(我使用通用存储库模式,所以我从我的服务库中调用像 GetAllGetSingle 这样的存储库函数),所以很多地方要记住添加包含。如果我不包含它们,那么如果使用了 TotalPayments 属性,我将面临出现空异常的风险。

处理这个问题的最佳方法是什么?

注意 1:我们有很多表,我真的不想为每个表恢复使用特定的存储库,我们以多种方式利用通用存储库....但我会很高兴听到对替代方案的有力论据:)

注意 2:出于性能原因,我没有开启延迟加载,也不打算开启它。

【问题讨论】:

  • 您的问题是如何修复 TotalPayments 属性以避免空异常,或者如何使用通用存储库模式设置包含?
  • 在 EF Core 中,所有查询都流经github.com/dotnet/efcore/blob/…。可能有一种方法可以将预处理器挂接到管道中以应用约定...
  • 你能在导航属性的定义上调用.Metadata.PrincipalToDependent.SetIsEagerLoaded(true)吗? (想法来自stackoverflow.com/questions/54041802/…
  • 这看起来很有趣@JeremyLakeman,我会看看我能用它实现什么。

标签: c# entity-framework


【解决方案1】:

这是我认为通用存储库是 EF 的反模式的原因之一。我使用存储库模式,但它的范围就像我的控制器一样。 IE。 CreateOrderController 将有一个 CreateOrderRepository。该存储库将通过 IQueryable 提供对所有相关实体的访问。查找等常见的东西将有自己的辅助存储库。使用适用于单个实体类型的通用存储库意味着添加对多个存储库的引用以执行特定操作,并在尝试加载实体时遇到此类问题。有时您需要相关数据,有时您不需要。简单地在顶级实体中添加方便的方法有效地“打破”了一个对象应该始终被认为是完整的或可完成的,而不依赖于带来显着性能成本的延迟加载。

让存储库返回 IQueryable 通过将控制权交给调用代码如何使用实体来避免许多问题。例如,我没有在实体中放置辅助方法,而是需要填充视图模型的代码依赖于 Linq 来构建视图模型。如果我的视图模型想要为员工支付一笔款项,那么返回 IQueryable 的存储库可以执行以下操作:

public IQueryable<Employee> GetEmployeeById(int employeeId)
{
    return Context.Employees.Where(x => x.EmployeeId == employeeId);
}

然后在控制器/服务中:

using (var contextScope = ContextScopeFactory.Create())
{
    var employeeViewModel = EmployeeRepository.GetEmployeeById(employeeId)
        .Select(x => new EmployeeSummaryViewModel
        {
           EmployeeId = x.EmployeeId,
           EmployeeName = x.LastName + ", " + x.FirstName,
           TotalPayments = x.Payments.Where(p => p.IsActive).Sum(p => p.Amount)
       }).Single();
}

我使用存储库是因为它比 DbContext 更容易模拟,而且它是 DbSet。对于同步代码,我只需模拟填充并返回List&lt;Employee&gt;().AsQueryable()。对于异步代码,我需要为异步列表添加一个包装器。

这种模式可能违背了更传统的存储库视图和调用代码需要“了解”实体的关注点分离,即 EF 主义被泄露。但是,无论您尝试采用何种方法来解决试图在存储库后面“隐藏”EF 的低效率问题,您都将留下非常低效的代码,其中存储库返回预先填充的 DTO,或者包含数十个几乎相同的方法返回不同的 DTO(或者更糟的是,具有不同完整性程度的实体),或者您正在添加复杂性,例如将魔术字符串或表达式树传递到您的方法中,以告诉 EF 如何过滤、如何排序、包含什么、分页等。在表达式或字符串中要求调用代码“了解”实体并泄露 EF 限制。 (传入的表达式/字符串还是要能被EF最终理解)

因此,对于您的项目当前状态,这可能不是一个可行的答案,但可能值得研究一下是否可以更好地管理您对存储库的依赖,而无需使用通用模式拆分它们,和/或利用 EF 的优秀IQueryable / Linq 功能让您的控制器/服务将实体投影到视图模型/ DTO 中,而不是将这些 reduce 元素嵌入到实体本身中。

【讨论】:

  • 谢谢,你给了我很多思考!我同意通用存储库是一种反模式,并且多年来也阅读了很多关于此的内容。然而,它在 MVP 构建的早期(当您只想访问您的存储库而不必为每个实体创建一个时)以及随着项目的增长以及您拥有如此多的实体时都非常有用,所以我总是分裂。跨度>
  • 我通过我的代码使用 IQueryable 进行过滤和分页等操作,但除此之外,我倾向于在 repo 本身中进行查询,以避免我在这里试图避免的事情,将其留给来电者了解他们需要包含的内容。也许半途而废的解决方案是为这样的实体提供特定的存储库,这些实体确实具有特定的依赖关系,因为它们也往往是我们的“大男孩”——驱动整个系统并值得更多个性化爱的实体:)跨度>
  • “让调用者知道他们需要包含什么。”最终,调用者需要知道要包含的内容。我知道让实体公开的属性以有效地减少规范化数据很诱人,但您提供的选择是:A)调用者需要知道急切加载以使用该减少属性(在这种情况下,它可能也知道如何提取它通过IQueryable) B) 吃掉延迟加载的性能成本(通常是不可接受的)或 C) 总是吃掉急切加载的性能/资源膨胀。恕我直言 A) 是更好的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-14
  • 2017-02-25
  • 1970-01-01
相关资源
最近更新 更多