为什么 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 属性 当且仅当它可以确保所有相关实体都已加载时才为真。在查询后投影会使此保证无效。
true if all the related entities are loaded or the IsLoaded has been explicitly set to true
一个潜在的解决方法是显式设置此 属性 以告诉 EF 在这种情况下,您知道得更多,并且不发出重新加载实体的查询。
在我的一个应用程序中,我使用启用了延迟加载代理的 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 属性 当且仅当它可以确保所有相关实体都已加载时才为真。在查询后投影会使此保证无效。
true if all the related entities are loaded or the IsLoaded has been explicitly set to true
一个潜在的解决方法是显式设置此 属性 以告诉 EF 在这种情况下,您知道得更多,并且不发出重新加载实体的查询。