我正在使用EF Core 3.1+PostrgeSql.执行后:

var stopWatch = Stopwatch.StartNew();

var entity1 = await _context.Entity1
    .Include(e => e.Entity2)
    .Include(e => e.Entity3)
        .ThenInclude(e => e.Entity4)
    .FirstOrDefaultAsync(e => e.Id == someId);

stopWatch.Stop();
_logger.LogInformation($"Elapsed: {stopWatch.ElapsedMilliseconds} milliseconds.");

可以看到下面的日志(log):

[16:49:36 INF] Executed DbCommand (43ms) [Parameters=[@__Id_0='?' (DbType = Guid)], CommandType='Text', CommandTimeout='30']
SELECT t."Id", ....
...
ORDER BY ....
[16:49:36 INF] Elapsed: 400 milliseconds.

我可以看到,DBCommand是被处决的43ms.如果我使用记录的SELECT查询并对数据库执行它,则花费的时间大致相同.查询返回45列和2000行数据集.

但总的时间几乎是十倍:400ms.

问题是:

  • 额外的时间是关于什么的?这是EF花在创建C#对象上的时间吗?
  • 有没有可能降低呢?

我try 了什么:

  • 我试着用AsNoTracking().似乎在这里没有任何效果.

推荐答案

是的,EF需要一些时间来构建和关联实体.为了进行公平的比较,需要判断DbContext在测试之前是否"预热"了.对DbContext执行的第一个查询总是会产生一次性配置成本.您的域模型越大,成本就越高.因此,如果有可能第一次针对DbContext执行此测试:

var temp = _context.Entity1.Any(); // Run a simple initial query which will ensure the one-off config cost is not being counted.

var stopWatch = Stopwatch.StartNew();

var entity1 = await _context.Entity1
    .Include(e => e.Entity2)
    .Include(e => e.Entity3)
        .ThenInclude(e => e.Entity4)
    .FirstOrDefaultAsync(e => e.Id == someId);

stopWatch.Stop();
_logger.LogInformation($"Elapsed: {stopWatch.ElapsedMilliseconds} milliseconds.");

影响查询性能的另一个因素是解析跟踪实例,但是如果您发现AsNoTracking()的性能没有差异,那么这不是您的情况下的一个因素.如果您有一个已经加载的DbContext,并且正在跟踪相当数量的实体,这些实体可能与特定查询中加载的实体相关,也可能与之无关;如果您使用跟踪运行该查询,则DbContext不仅将跟踪任何已加载实体的引用,而且将遍历all个相关实体,以查找与您请求的实体相关的跟踪实例并将它们关联起来,无论您是否告诉它立即加载.

例如:

var children = _context.Children.Where(x => x.ParentId < 5).ToList();

var parent1 = _context.Parents.Single(x => x.ParentId == 1);
var parent6 = _context.Parents.Single(x => x.ParentId == 6);

当我们加载两个父对象时,即使我们没有显式地Eager 地加载子对象Include,因为前面跟踪的针对DbContext的查询加载了父对象1到5的子对象,当您判断父对象#1时,它将加载其子对象集合,而父对象#6不会.这可能导致情景行为,并显示适用于两个父查询的性能成本,因为DbContext将扫描所有跟踪的引用,以查看加载时是否有任何内容与父查询相关联.加载带有AsNoTracking()的父项,即使加载和跟踪子项也会导致只返回父项,而不会产生DbContext扫描被跟踪实体以查找要填充的引用的开销.

当您确实需要紧急加载实体图时,提高性能的一个提示是使用AsSplitQuery()来减少笛卡尔乘积.它不是生成带有联接的单个查询,而是为相关实体构建单独的查询,从而拉取的记录要少得多.在1个记录有10个A和10个B、20个C的情况下,笛卡尔回调2000行.如果有AsSplitQuery()行,它将回调41行.使用AsSplitQuery()不是灵丹妙药,因为如果排序和分页是查询的一个因素,则可能会出现问题.

最大限度地减少Cartestian产品和跟踪引用的影响的最佳方法是尽可能多地使用投影.不要获取实体及其相关项,而是使用SelectProjectTo(Automapper)从实体图中获取所需的列.这自动避免了添加或搜索跟踪缓存的问题,并且在列较少的情况下,可以减小所得到的笛卡尔坐标的大小,其中填充的结果通常比实体对象图更简单/更平坦.

希望这能给你提供一些可供参考的途径.与原始SQL相比,EF总是会带来一些额外的查询,但它通常不应该慢很多倍,并且提供了大量的功能来弥补它确实增加的成本.

Asp.net相关问答推荐

调用context.Users和context.Set User有什么区别?

Razor 页面客户端站点验证不起作用

在哪里可以记录 ASP.NET Core 应用程序的启动/停止/错误事件?

如何在页面加载之前运行 JavaScript 代码?

SqlBulkCopy 超时

解析 JSON 响应的最简单方法

如何在asp.net中判断会话是否过期

如何在页面的基类中执行 Page_Load()?

如何在 ASP.NET core rc2 中禁用浏览器缓存?

无法从 App_code 文件夹中找到类型或命名空间

RestSharp 不反序列化 JSON 对象列表,始终为 Null

从 RowDataBound 事件的 gridview 从单元格中获取值

使用 Lucene.NET 索引 .PDF、.XLS、.DOC、.PPT

在 C# 中将 IHtmlContent/TagBuilder 转换为字符串

在不知道键名的情况下访问 JSON 对象的元素

如何在 ASP.NET MVC3 中配置区域

使用 LINQ 进行递归控制搜索

在 Visual Studio 2010 Professional 中找不到请求的 .Net Framework 数据提供程序

用于呈现

如何直接在 .aspx 页面中访问 web.config 设置?