为什么实体框架核心急于加载未包含的相关实体

时间:2019-01-09 10:15:01

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

我正在创建ASP.NET Core Web API,并且在查询上下文时遇到问题。

Web API连接到具有3个表的数据库:雇员,事件和事件责任。员工可以创建许多事件。此外,在每个事件中,可以为许多员工分配许多事件的责任(因此,链接了“事件责任”表)。

员工表

+----+-----------------+
| 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; }

事件责任模型

多对多链接表模型。

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": []
  }
]

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

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

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

所以我的问题是:为什么即使我没有加载/包含Creator导航属性,也要包含它?此外,我也想知道如何 不是 包括相关的创建者数据,并且仅在JSON中显示事件责任数据。

1 个答案:

答案 0 :(得分:2)

如果您查看documentation,则有一个具体说明实际上是您陷入的陷阱:

  

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

这样做

.ThenInclude(e => e.Employee);

您请求EF提取此Employee,从而使其被急切加载,无论它在哪里被引用。并且由于在这种特定情况下,您的Event.Creator与您明确要求的EventResponsibilities.Employee相同,因此,在将其提取到EventResponsibilities中之后,还会将其提取到Event中。 如果您的Event.CreatorEmployee是不同的EeventsResponsibilities.Employee,则不会遇到这种异常情况。

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

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

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

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