c# - EF Core 2.1 进行多个数据库调用
问题描述
有没有办法防止 EF Core 在单个枚举函数调用上进行多次数据库往返?
考虑这个相对简单的 LINQ 表达式:
var query2 = context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
Status = ct.CheckinTabletStatuses
.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault()
}).ToList();
过去的解释是“一个枚举调用转换为一个 DB 调用”(如果禁用延迟加载)。在 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()
Entity Framework 正在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];
解决方案
我在 .Net Conf 2018 期间向 Diego Vega 和 Smit Patel 提出了这个问题……这是他们的答案(意译)。
EF Core 不仅适用于关系数据库...如果某些内容无法转换为 SQL,客户不希望看到异常...“如果它需要多个查询,那很好”...默认情况下,每个枚举有多个查询已启用。如果发生这种情况,有一个警告系统会输出警告。他们正在考虑添加一种方法,如果执行多次往返,则将警告升级为异常。他们正致力于将 (n+1) 个查询优化为基于数据结构的几个(固定大小)查询。
通过将其添加到 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
推荐阅读
- mysql - 如何在 MySQL 中显示多个表中的数据?
- c++ - 作为参数传递给函数(c ++)时如何修改全局结构?
- css - 如何使元素的高度超过窗口的可见区域?
- php - 从 xlsx 生成的页面的 PHP 千位分隔符格式
- flutter - Flutter 上的 DatePicker:获取日期时为空值:
- bash - 从 teamcity 推送更改到 git 时出错 (ssh_askpass: exec(/usr/lib/ssh/ssh-askpass))
- r - 无法过滤变异的列
- c# - 使用 BackgroundWorker 处理异常
- tensorflow - 安装 Tensorflow 1.15.0 后仍然出现 Tensorflow 2.0.0 行为
- c# - 将 ASP.NET Web Api 构建到不同的输出目录