【问题标题】:Why is Entity Framework Core eager loading Related Entities that aren't Included为什么 Entity Framework Core 渴望加载未包含的相关实体
【发布时间】:2019-01-09 22:20:16
【问题描述】:

我正在创建一个 ASP.NET Core Web API,但在查询上下文时遇到了问题。

Web API 连接到具有 3 个表的数据库:Employees、Events 和 Event Responsibilities。一名员工可以创建许多事件。此外,可以为许多员工分配每个事件中的许多事件的职责(因此链接事件职责表)。

员工表

+----+-----------------+
| Id |      Name       |
+----+-----------------+
|  1 | First Employee  |
|  2 | Second Employee |
+----+-----------------+

事件表

+----+------------+---------------+
| Id | Creator_Id |  Event_Name   |
+----+------------+---------------+
|  1 |          1 | Random Event  |
|  2 |          1 | Another Event |
+----+------------+---------------+

事件责任表

+----+-------------+----------+
| Id | Employee_Id | Event_Id |
+----+-------------+----------+
|  1 |           1 |        1 |
+----+-------------+----------+

这些模型包括...

事件模型

一个事件只能有一个员工作为创建者。 一个活动也可以有许多承担不同职责的员工。

public int Id { get; set; }
public int? CreatorId { get; set; }
public string EventName { get; set; }

public Employee Creator { get; set; }
public ICollection<EventResponsibilities> EventResponsibilities { get; set; }

员工模型

员工可以是许多事件的创建者。 员工可以在许多事件中承担责任。

public int Id { get; set; }
public string Name { get; set; }

public ICollection<Event> Event { get; set; }
public ICollection<EventResponsibilities> EventResponsibilities { get; set; }

Event_Responsibilities 模型

多对多链接表模型。

public int Id { get; set; }
public int? EmployeeId { get; set; }
public int? EventId { get; set; }

public Employee Employee { get; set; }
public Event Event { get; set; }

我在控制器方法中运行了以下 LINQ 查询...

return _context.Event.Include(e => e.EventResponsibilities).ThenInclude(e => e.Employee);

希望获得一个 JSON,其中包含事件的详细信息、其相应的事件职责以及该职责的相关员工。

我实际得到的是以下 json...

[
  {
    "Id": 1,
    "CreatorId": 1,
    "EventName": "Random Event",
    "Creator": {
      "Id": 1,
      "Name": "First Employee",
      "Event": [],
      "EventResponsibilities": [
        {
          "Id": 1,
          "EmployeeId": 1,
          "EventId": 1
        }
      ]
    },
    "EventResponsibilities": [
      {
        "Id": 1,
        "EmployeeId": 1,
        "EventId": 1,
        "Employee": {
          "Id": 1,
          "Name": "First Employee",
          "Event": [],
          "EventResponsibilities": []
        }
      }
    ]
  },
  {
    "Id": 2,
    "CreatorId": 1,
    "EventName": "Another Event",
    "Creator": {
      "Id": 1,
      "Name": "First Employee",
      "Event": [
        {
          "Id": 1,
          "CreatorId": 1,
          "EventName": "Random Event",
          "EventResponsibilities": [
            {
              "Id": 1,
              "EmployeeId": 1,
              "EventId": 1
            }
          ]
        }
      ],
      "EventResponsibilities": [
        {
          "Id": 1,
          "EmployeeId": 1,
          "EventId": 1,
          "Event": {
            "Id": 1,
            "CreatorId": 1,
            "EventName": "Random Event",
            "EventResponsibilities": []
          }
        }
      ]
    },
    "EventResponsibilities": []
  }
]

这不是我想要或期望的...... 如您所见,由于某种原因,Event 模型的 Creator 导航属性正在显示,即使我在查询中没有请求它,因为我没有使用 Include()。

此外,如果您查看 JSON 中的第二个返回事件(“Id”:2),即使没有 EventResponsibilities,也会返回两个 Creator Rows。我认为这不是延迟加载问题,因为导航属性不是虚拟的。

我本来希望 Creator 对象为 null 或空,因为我没有包含它。

所以我的问题是:为什么即使我没有加载/包含 Creator 导航属性也会被包含在内?另外,我也想知道如何 不是 包含相关的 Creator 数据,并且仅在 JSON 中显示 Event Responsibilities 数据。

【问题讨论】:

  • 您已将您的属性声明为非虚拟的。实体框架无法创建延迟加载代理,因此会急切地加载所有内容。

标签: c# asp.net-core json.net ef-core-2.1


【解决方案1】:

如果你查看documentation,有一个具体的注释实际上是你掉入的陷阱:

Entity Framework Core 将自动修复先前加载到上下文实例中的任何其他实体的导航属性。因此,即使您没有明确包含导航属性的数据,如果之前加载了部分或所有相关实体,该属性仍可能会被填充。

通过做

.ThenInclude(e => e.Employee);

您请求 EF 获取此 Employee 从而导致它在任何被引用的地方都被急切加载。并且因为在这种特定情况下,您的Event.Creator 与您明确请求的EventResponsibilities.Employee 相同,因此在将其提取到EventResponsibilities 后,它也会提取到您的Event 中。 如果您的Event.CreatorEeventsResponsibilities.Employee 不同Employee,您将不会遇到此异常。

这种意外行为和冗余数据是人们使用单独的 DTO 从控制器返回数据以确保 API 返回所需数据量的原因之一。

作为一种快速解决方法,您只需在您的 Creator 属性上添加 JsonIgnoreAttribute

[JsonIgnore]
public Employee Creator { get; set; }

但我个人认为将 DTO 和 Entity 混合在同一个类中并不是一个好主意,因为每当任何实体发生变化时,都会引起各种麻烦。

【讨论】:

  • 好答案。最后一行也是关键。您几乎应该始终使用 DTO 类进行 JSON 序列化。这使您可以明确控制 JSON 对象中的最终结果,而不是根据碰巧包含或通过修复添加的对象的任何可变潜在响应中的任何一个。设置一个类,该类完全模拟您希望 JSON 的外观,然后将您的实体映射到该类。
  • @imantas jfyi,如果你在基础设施层中使用 repos,那么你不能在那里使用 DTO,因为它违反了干净的架构原则
  • @DamirBeylkhanov 它可能不一定是 DTO,它可以是任何中间转换,只是让您能够承诺调用者准确返回它所要求的内容。基于事先执行的查询的相同查询的不确定结果肯定会比肮脏的架构更令人头疼。其次,在我个人看来,带有 EF Core 的 repos 表明代码有异味。
  • 如果我们在基础设施层中讨论 repos 的实现 - 只有在它们的接口位于域层的情况下才可以。在 repos 中,我们通常也使用域模型
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-10
  • 2023-03-29
  • 2018-02-27
  • 1970-01-01
  • 2020-08-06
  • 2018-09-10
相关资源
最近更新 更多