我们有以下方法允许我们搜索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
,性能如下:
使用Include
:
答案 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
中显示的那样),以便最大限度地降低性能/网络成本。
我认为没有合理的论据来混合这两种方法。你最终会遇到两个缺点。