加快linq查询性能

时间:2020-02-15 16:42:46

标签: entity-framework linq

我正在运行这个linq查询,它有点大。

var events = _context.Event.OrderByDescending(e => e.StartDate).Where(e => e.IsPresentation == true).Where(e => e.IsCanceled == false).Where(e => e.StartDate > new DateTime());

此查询输出数据的页面需要太多时间来加载。可能是因为我在太多地方使用了

在另一个查询中使用includes,然后包含includes,我遇到了相同的问题,但是我对查询进行了划分,以提高性能。但我想弄清楚在这种情况下如何做同样的事情,因为我没有使用任何包含。

3 个答案:

答案 0 :(得分:1)

总体而言,查询的性能将在很大程度上取决于表的大小以及适当索引的可用性。

我可以从该查询中注意到几件事:

该语句没有多大意义:.Where(e => e.StartDate > new DateTime())new DateTime()将从01/01/0001初始化DateTime。例如,存储在SQL Server DateTime列中的任何日期都将是从01/01/1753开始,因此这似乎没有什么根据。如果数据库/实体的DateTime值可为空,则.Where(e => e.StartDate.HasValue)更适用。如果DateTime值不可为空,则可以完全取消此条件。

因此,如果该字段是可为null的字段,则Linq表达式将更像:

var events = _context.Event
    .Where(e => e.IsPresentation && !e.IsCanceled && e.StartDate.HasValue)
    .OrderByDescending(e => e.StartDate)
    .ToList();

如果它不能为空:

var events = _context.Event
    .Where(e => e.IsPresentation && !e.IsCanceled)
    .OrderByDescending(e => e.StartDate)
    .ToList();

要消除的下一个罪魁祸首:延迟加载代理命中。事件属性是否具有其他任何实体的导航属性?如果这类似于Web应用程序,并且您要序列化实体以发送回客户端,则导航属性EF代理绝对会降低性能。在此调用之后,任何涉及导航属性的代码都会导致额外的单个DB调用来延迟加载这些导航属性。对于返回实体列表的方法,这可能很关键。如果某个事件引用了某个用户之类的东西,并且您加载了大约1000个用户的1000个事件,则当序列化程序去序列化这1000个事件时,它将“触摸”每个用户引用。这将导致为每个事件中的每个用户ID有效执行SELECT * FROM tblUser WHERE UserId = 1SELECT * FROM tblUser WHERE UserId = 2 ...等操作的〜1000条额外的SQL语句。如果您需要这些相关的实体,可以急切地用Include(e => e.User)加载它们,这比单独加载它们要快,但这确实意味着将很多额外的数据加载到您的客户端/代码可能不需要的内存中。您可以通过关闭延迟加载和代理来避免延迟加载,但这将使这些实体具有#null引用,这意味着任何期望Event实体具有任何相关详细信息的代码都可以获取这些部分加载的实体之一。 (不好,该实体应始终处于完成或可完成状态)最后一种选择是使用Select来填充结果的视图模型。这可以大大加快查询的速度,因为您可以让EF组成查询,以从任何实体中仅提取所需的数据,而不是从所有实体或触发延迟加载中提取数据。

例如,如果您只需要显示EventId,EventNumber,Name,StartDate和UserName来显示事件:

var events = _context.Event
    .Where(e => e.IsPresentation && !e.IsCanceled && e.StartDate.HasValue)
    .Select(e => new EventViewModel
    {
        EventId = e.EventId,
        EventNumber = e.EventNumber,
        Name = e.Name,
        StartDate = e.StartDate,
        UserName = e.User.Name
    })
    .OrderByDescending(e => e.StartDate)
    .ToList();

这避免了任何延迟加载命中,并将查询运行减少到仅所需的列,从而可以显着加快查询速度。

下一步将查看EF正在运行的查询及其相关的执行计划。这可以突出显示索引缺失/不良,以及任何意外的延迟加载命中。执行此操作的方法取决于您的数据库,但涉及对DB运行Profiler以捕获调试时正在运行的SQL语句。在这里,您可以捕获EF生成的SQL语句,然后使用任何DB端分析工具针对您的数据库手动运行这些语句。 (例如,带有SQL Server的SSMS,以获取可以识别丢失索引的执行计划)由于在您的方法似乎已经完成但在数据返回之前执行了许多额外的SQL语句,因此可以检测到Web应用程序中的序列化程序延迟加载命中给客户。这是序列化程序的“接触”代理,导致服务器在将数据返回给客户端之前必须等待完成许多其他查询。

最后是数据量。任何预期随时间增长的系统都应考虑最终可以返回的数据量。随时间返回数据列表的所有内容都应包含服务器端分页,客户端在其中发送页面大小和页面#,服务器可以将页面大小和页面#转换为.Skip(pageNumber * pageSize).Take(pageSize)操作。 (/ w页面编号从0开始)大多数数据网格和类似组件应支持服务器端分页,以将这些参数发送到其数据加载方法。这些控件将需要知道总行数以设置分页,因此您需要一种方法来返回该计数:

var rowCount = _context.Event
    .Where(e => e.IsPresentation && !e.IsCanceled && e.StartDate.HasValue)
    .Count();

相同条件,无排序依据,无.Count()的{​​{1}}等将构成有效的Count查询。

这应该为您提供一些检查和调整的功能,以消除您的性能陷阱。

答案 1 :(得分:0)

一件事可以在末尾进行排序,因为这样可以减少项目的数量,然后减少排序的时间,

但这实际上取决于您的数据分布 。看看您的数据中的大多数是否具有e.IsPresentation == true,然后第一个“ Where”不会为您减小数据大小,因此,您再次检查e.IsCanceled == false,例如95%的数据。但是假设您整个数据中只有10%是e​​.IsCanceled == false。因此,现在如果您对第二个10%的位置应用e.IsPresentation == true,则所需时间要比以前少得多。因此,在大型数据库中,数据库管理员通常使用不同的查询计划!但是最终结果是相同的。处理时间不一样。希望对您有帮助。

答案 2 :(得分:0)

  1. 您应该将值存储到var now = new DateTime()这样的变量中
  2. 将所有条件合并为一个Where子句
  3. OrderByDescending子句之后仅Where,这意味着我们只是根据实际数据而不是全部进行订购。
var now = new DateTime();
var events = _context.Event
                         .Where(e => e.IsPresentation && !e.IsCanceled && e.StartDate > now)
                         .OrderByDescending(e => e.StartDate);

提示

您应根据实际数据重新安排条件。例如:

.Where(e => 1 == 2 && 2 == 2 && 3 == 3)

如您所见,由于&& 2 == 2 && 3 == 3条件,我们不需要操纵and的其余条件。