LINQ包括在搜索时降低性能

时间:2018-05-30 10:31:50

标签: c# sql linq asp.net-core entity-framework-core

我们有以下方法允许我们搜索DataGrid的Projects表:

public async Task<IEnumerable<Project>> GetFilteredProjects(string searchString)
{
    var projects = _context.Projects.Where(p => p.Current);

    projects.Include(p => p.Client);
    projects.Include(p => p.Architect);
    projects.Include(p => p.ProjectManager);

    if (!string.IsNullOrEmpty(searchString))
    {
        projects = projects
            .Where(p => p.NormalizedFullProjectName.Contains(searchString)
                    || p.Client.NormalizedName.Contains(searchString)
                    || p.Architect.NormalizedFullName.Contains(searchString)
                    || p.ProjectManager.NormalizedFullName.Contains(searchString));
    }

    projects = projects.OrderBy(p => p.Name).Take(10);

    return await projects.ToListAsync();
}

如果我们不在项目上使用Include,那么搜索是即时的。但是,在搜索中添加它们可能需要3秒钟。

我们需要包含其他实体,以便用户可以根据需要进行搜索。

我们如何才能提高效果,但仍然保留Include以便对其进行搜索?

没有Incldue,方法如下:

public async Task<IEnumerable<Project>> GetFilteredProjects(string searchString)
{
    var projects = _context.Projects.Where(p => p.Current);

    if (!string.IsNullOrEmpty(searchString))
    {
        projects = projects
            .Where(p => p.Name.Contains(searchString));
    }

    projects = projects.OrderBy(p => p.Name).Take(10);

    return await projects.ToListAsync();
}

没有Include,性能如下:

enter image description here

使用Include

enter image description here

1 个答案:

答案 0 :(得分:8)

简短的回答是,包括所有额外的实体需要花费时间和精力,从而增加了加载时间。

然而,您的假设有一个缺陷:

  

我们需要包含其他实体,以便用户可以根据需要进行搜索。

这不是(必然)正确的。过滤发生在数据库级别。 Include告诉实体框架加载来自数据库的记录。这是两件事。

请看以下示例:

_context.Projects
        .Include(p => p.Architect)
        .Where(p => p.Architect.Name == "Bob")
        .ToList()

这将为您提供一个项目(及其架构师)的列表,这些项目的架构师名为Bob。

_context.Projects
        .Where(p => p.Architect.Name == "Bob")
        .ToList()

这将为您提供一个项目列表(没有架构师),他们有一个名为Bob的架构师;但它实际上并没有将Architect对象加载到内存中。

_context.Projects
        .Include(p => p.Architect)
        .ToList()

这将为您提供项目列表(及其架构师)。它将包含每个项目,列表不会被过滤。

如果要执行内存中过滤,即在已从数据库加载的集合上,则只需使用Include

您的情况是否取决于此部分:

    projects = projects
        .Where(p => p.NormalizedFullProjectName.Contains(searchString)
                || p.Client.NormalizedName.Contains(searchString)
                || p.Architect.NormalizedFullName.Contains(searchString)
                || p.ProjectManager.NormalizedFullName.Contains(searchString));

如果NormalizedFullProjectName(和其他属性)是数据库列,则EF可以在数据库级别执行过滤。您不需要Include来过滤项目。

如果NormalizedFullProjectName(和其他属性)不是数据库列,那么EF首先必须在内存中加载项才能应用过滤器。在这种情况下,您需要Include,因为架构师(和其他人)需要加载到内存中。

如果您只是为了过滤目的而加载相关实体(不显示目的),并且您正在数据库级别进行过滤;然后你可以简单地删除包含语句。

如果您需要加载这些相关实体(用于内存中过滤或用于显示目的),那么除非您编写Include,否则无法轻松删除Select语句指定您需要的字段。

例如:

_context.Projects
        .Select(p => new { Project = p, ArchitectName = p.Architect.Name })
        .ToList()

这将加载项目实体(完整),但只加载架构师的名称而不加载任何其他属性。如果您的相关实体具有您目前不需要的许多属性,则可以显着提升性能。

注意:当前示例使用匿名类型。我一般主张为此创建一个自定义类型;但这与我们在这里解决的性能问题无关。

<强>更新

根据您的更新,您似乎暗示在从数据库加载对象后发生了预期的过滤。

这是您的性能问题的根源。您正在获取大量数据但仅显示其中的一部分。仍然需要加载未显示的数据,这是浪费精力。

这里有单独的绩效论据:

  • 加载一切 - 加载所有数据一次(可能需要很长时间),但允许用户过滤加载的数据(这非常快)
  • 加载数据块 - 仅加载与应用的过滤器匹配的数据。如果用户更改过滤器,则会再次加载数据。第一次加载会快得多,但与内存中过滤相比,后续过滤操作需要更长的时间。

这里你应该做的不是我的决定。这是一个优先事项。有些客户比较喜欢一个。我想说在大多数情况下,第二个选项(加载块)是更好的选择,因为如果用户永远不会查看90%的数据集,它可以防止不必要地加载大量数据集。这是对性能和网络负载的浪费。

我给出的答案适用于“加载块”方法。

如果您决定采用“一次加载所有”方法,那么您将不得不接受该初始加载的性能损失。您可以做的最好的事情是严格限制返回的数据列(就像我在Select中显示的那样),以便最大限度地降低性能/网络成本。

我认为没有合理的论据来混合这两种方法。你最终会遇到两个缺点。