有没有办法阻止EF Core在单个枚举函数调用中进行多次DB往返?
考虑这个相对简单的LINQ表达式:
var query2 = context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
Status = ct.CheckinTabletStatuses
.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault()
}).ToList();
在过去,expactation是"一个枚举调用转换为一个数据库调用" (如果禁用延迟加载)。在EF Core中已不再是这种情况了!
在EF 6.2.0中,这个LINQ被翻译为
SELECT [Extent1].[CheckinTabletID] AS [CheckinTabletID],
[Limit1].[TimestampUtc] AS [TimestampUtc]
--...
FROM [dbo].[CheckinTablet] AS [Extent1] OUTER APPLY (
SELECT TOP (1) [Project1].[CheckinTabletStatusID] AS [CheckinTabletStatusID],
[Project1].[CheckinTabletID] AS [CheckinTabletID],
[Project1].[TimestampUtc] AS [TimestampUtc]
FROM (
SELECT [Extent2].[CheckinTabletStatusID] AS [CheckinTabletStatusID],
[Extent2].[CheckinTabletID] AS [CheckinTabletID],
[Extent2].[TimestampUtc] AS [TimestampUtc]
--...
FROM [dbo].[CheckinTabletStatus] AS [Extent2]
WHERE [Extent1].[CheckinTabletID] = [Extent2].[CheckinTabletID]
) AS [Project1] ORDER BY [Project1].[TimestampUtc] DESC
) AS [Limit1];
虽然非常难看,但POLA之后却非常好。更多的是我们可以使用它来优化数据库端(索引)。
使用EF Core 2.1.0,我们得到类似的结果:
SELECT [ct].[CheckinTabletID] AS [Id], [ct].[strName] AS [DeviceName] FROM [CheckinTablet] AS [ct]
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=1
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=2
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=3
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=4
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=5
是的,这是一次调用首先获取所有实体(CheckinTablets),然后每行调用以获取每个实体的状态...
因此,在一次调用中ToList()
实体框架正在对数据库进行n+1
调用。这是非常不受欢迎的,有没有办法禁用此行为或解决方法?
修改1:
.Include()没有帮助解决这个问题......它仍然会产生n + 1个数据库请求。
修改2(赠送@jmdon):
不返回对象但是简单的值只能进行一次调用!当然,如果您不想展平您的实体,或者您想要从第二个表中获取多个值,这实际上并没有帮助。从来没有那么好知道!
var query2 = _context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
Status = new CheckinTabletStatus
{
Id = ct.CheckinTabletStatuses.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().Id,
CheckinTabletId = ct.CheckinTabletStatuses.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().CheckinTabletId,
}
}).ToList();
生成一次对DB的调用:
SELECT [ct].[intCheckinTabletID] AS [Id0],
[ct].[strName] AS [DeviceName],
(
SELECT TOP (1) [cts].[intCheckinTabletStatusID]
FROM [tCheckinTabletStatus] AS [cts]
WHERE [ct].[intCheckinTabletID] = [cts].[intCheckinTabletID]
ORDER BY [cts].[dtmTimestampUtc] DESC
) AS [Id],
(
SELECT TOP (1) [cts0].[intCheckinTabletID]
FROM [tCheckinTabletStatus] AS [cts0]
WHERE [ct].[intCheckinTabletID] = [cts0].[intCheckinTabletID]
ORDER BY [cts0].[dtmTimestampUtc] DESC
) AS [CheckinTabletId]
FROM [tCheckinTablet] AS [ct];
答案 0 :(得分:1)
我注意到当你尝试返回嵌套对象时会这样做。
您可以尝试在投影中展平Status对象,例如。类似的东西:
var query2 = context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
StatusName = ct.CheckinTabletStatuses
.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().Name
}).ToList();
答案 1 :(得分:0)
我在.Net Conf 2018期间向Diego Vega和Smit Patel提出了这个问题...这是他们的答案(解释)。
EF Core不仅适用于关系数据库...如果某些内容无法转换为SQL,客户不希望看到Exception ...“如果需要更多的内容,那么一个查询就可以了” ...默认情况下,多个启用每个枚举查询。如果发生这种情况,有一个警告系统将输出警告。他们正在考虑添加一种方法,该方法将在执行多次往返时将警告升级为异常。他们正在根据数据结构将(n + 1)个查询优化为几个(固定大小)查询。
通过将EF Core添加到OnConfiguring方法中,可以强制EF Core在评估查询客户端的一部分时引发异常。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
更多信息:https://docs.microsoft.com/en-us/ef/core/querying/client-eval