LINQ包括在搜索时降低性能

CBr*_*eze 2 c# sql linq entity-framework-core asp.net-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();
}
Run Code Online (Sandbox Code Playgroud)

如果我们不使用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();
}
Run Code Online (Sandbox Code Playgroud)

没有Include性能看起来像这样:

在此输入图像描述

Include:

在此输入图像描述

Fla*_*ter 8

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

但是,您的假设有一个缺陷:

我们需要包含其他实体以允许用户根据需要搜索它们.

这不是(必然)正确的.过滤发生在数据库级别.Include告诉Entity Framework 数据库加载记录.这是两件不同的事情.

请看以下示例:

_context.Projects
        .Include(p => p.Architect)
        .Where(p => p.Architect.Name == "Bob")
        .ToList()
Run Code Online (Sandbox Code Playgroud)

这将为您提供一个项目(及其架构师)的列表,他们有一个名为Bob的架构师.

_context.Projects
        .Where(p => p.Architect.Name == "Bob")
        .ToList()
Run Code Online (Sandbox Code Playgroud)

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

_context.Projects
        .Include(p => p.Architect)
        .ToList()
Run Code Online (Sandbox Code Playgroud)

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


您只需要Include在要进行内存中过滤时使用,即在已从数据库加载的集合上使用.

是否属于这种情况取决于这部分:

    projects = projects
        .Where(p => p.NormalizedFullProjectName.Contains(searchString)
                || p.Client.NormalizedName.Contains(searchString)
                || p.Architect.NormalizedFullName.Contains(searchString)
                || p.ProjectManager.NormalizedFullName.Contains(searchString));
Run Code Online (Sandbox Code Playgroud)

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

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


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

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

例如:

_context.Projects
        .Select(p => new { Project = p, ArchitectName = p.Architect.Name })
        .ToList()
Run Code Online (Sandbox Code Playgroud)

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

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


更新

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

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

这里有单独的性能参数:

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

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

我给出的答案适用于"加载块"方法.

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

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