为什么 EF Core 3 在包含 .Query() 时不将集合标记为已加载?

Why does EF Core 3 not mark a collection as loaded when including .Query()?

在我的一个应用程序中,我使用启用了延迟加载代理的 EF Core 3.1。根据 documentation here and ,似乎我应该能够显式加载一个集合 并将该集合标记为已加载 如果我执行以下操作:

await context.Entry(parent)
    .Collection(p => p.Children)
    .Query()
    .Include(c => c.Grandchildren)
    .Where(p => p.Id > 100)
    .LoadAsync();

这应该为 parent 实体获取经过过滤的子实体子集,并拉回子实体本身的相关实体集合。

到那时,我希望 parent.Children 被 EF Core 视为 已加载 ,这样我就可以稍后在我的代码中访问该集合并且它不会'不要尝试返回我的数据库,即不会尝试延迟加载它。

在我的申请中,我发现情况完全不是这样。在下面的屏幕截图中,您可以看到 productConsultant 实体上的 Order 集合在我尝试执行类似操作后未标记为已加载:

我可以看到它进入数据库并执行接近我指定的查询(它包括“where”条件但不加入相关实体):

SELECT [o].[Id],
       [o].[Assignee],
       [o].[CommissionAuthorisedInPeriod],
       [o].[CommissionPaidInPeriod],
       [o].[CsiMaxScore],
       [o].[CsiScore],
       [o].[CustomerName],
       [o].[DateDelivered],
       [o].[DateSigned],
       [o].[DeliveredInPeriod],
       [o].[EmailStatus],
       [o].[HasZeroCsiScore],
       [o].[IsFleetOrder],
       [o].[IsSubPrime],
       [o].[OrderStreamCheckpoint],
       [o].[PaymentMethod],
       [o].[Status],
       [t].[Id],
       [t].[Vehicle],
       [t].[VehicleSaleType]
FROM [Order] AS [o]
LEFT JOIN
(
    SELECT [o0].[Id],
           [o0].[Vehicle],
           [o0].[VehicleSaleType]
    FROM [Order] AS [o0]
    WHERE [o0].[VehicleSaleType] IS NOT NULL
          AND [o0].[Vehicle] IS NOT NULL
) AS [t] ON [o].[Id] = [t].[Id]
WHERE ([o].[Assignee] = @__p_0)
      AND ([o].[CommissionAuthorisedInPeriod] = @__activePeriod_1);

但是,因为它没有将集合标记为已加载,所以当我尝试在 ProductConsultant class 中引用 Order 集合时,它会关闭并执行另一个数据库查询 产品顾问曾经交付过的所有订单,尽管我之前曾尝试仅使用一部分订单显式加载订单集合:

public void PayCommissions(Period period)
{
    var payableOrders = from o in Orders
                        where o.CommissionAuthorisedInPeriod == period
                        where !o.HasBeenPaid
                        select o;
    // ...
}
SELECT [o].[Id],
       [o].[Assignee],
       [o].[CommissionAuthorisedInPeriod],
       [o].[CommissionPaidInPeriod],
       [o].[CsiMaxScore],
       [o].[CsiScore],
       [o].[CustomerName],
       [o].[DateDelivered],
       [o].[DateSigned],
       [o].[DeliveredInPeriod],
       [o].[EmailStatus],
       [o].[HasZeroCsiScore],
       [o].[IsFleetOrder],
       [o].[IsSubPrime],
       [o].[OrderStreamCheckpoint],
       [o].[PaymentMethod],
       [o].[Status],
       [t].[Id],
       [t].[Vehicle],
       [t].[VehicleSaleType]
FROM [Order] AS [o]
LEFT JOIN
(
    SELECT [o0].[Id],
           [o0].[Vehicle],
           [o0].[VehicleSaleType]
    FROM [Order] AS [o0]
    WHERE [o0].[VehicleSaleType] IS NOT NULL
          AND [o0].[Vehicle] IS NOT NULL
) AS [t] ON [o].[Id] = [t].[Id]
WHERE [o].[Assignee] = @__p_0;

如果我要更改原始查询,使其除了加载集合什么都不做,那么它 将集合标记为已加载,但这是毫无意义的,因为它拉回了太多数据并且不允许我包含嵌套实体集合:

与我在 EF Core 3 中遇到的许多其他问题一样,这可能可以通过 EF Core 5 轻松解决(使用过滤包含)。在我能够将此项目升级到目标 .NET 5 之前,有没有办法让它与 EF Core 3 一起使用?有什么我想念的吗?

更新

根据斯蒂芬的回答,我做了如下所示的更改。这允许我仅从数据库中取回相关数据,并防止 EF Core 稍后执行第二个查询:

原因是在执行 .query() 之后执行 .包括哪个 EF 认为您要求新查询并丢弃旧的查询过滤器,以及它为什么第二次访问数据库。

简而言之,EF Core 的 IsLoaded 属性 当且仅当它可以确保所有相关实体都已加载时才为真。在查询后投影会使此保证无效。

IsLoaded

true if all the related entities are loaded or the IsLoaded has been explicitly set to true

一个潜在的解决方法是显式设置此 属性 以告诉 EF 在这种情况下,您知道得更多,并且不发出重新加载实体的查询。