Sql 如何在Entity Framework Core 2.1中优化缓慢(不太复杂)的查询

Sql 如何在Entity Framework Core 2.1中优化缓慢(不太复杂)的查询,sql,database-performance,asp.net-core-2.1,ef-core-2.1,Sql,Database Performance,Asp.net Core 2.1,Ef Core 2.1,我有一个LINQ查询,可以在几个表中进行字符串搜索。然而,在大型表上查询速度非常慢。在我第一次尝试时,我得到了一个超时。我能够稍微提高性能。这是代码的第一个版本: public ListResponse<UserDTO> GetUsers(FilterParameters filter) { var query = from user in _dbContext.Users .Include(w => w.UserRoles

我有一个LINQ查询,可以在几个表中进行字符串搜索。然而,在大型表上查询速度非常慢。在我第一次尝试时,我得到了一个超时。我能够稍微提高性能。这是代码的第一个版本:

 public ListResponse<UserDTO> GetUsers(FilterParameters filter)
 {
     var query = from user in _dbContext.Users
                    .Include(w => w.UserRoles).ThenInclude(u => u.Role)
                 join accountHolder in _dbContext.AccountHolders
                    .Include(c => c.OperationCountry)
                    .Include(x => x.Accounts)
                         .ThenInclude(x => x.Currency)
                  on user.Id equals accountHolder.ObjectId into aHolder
                  
                  from a in aHolder.DefaultIfEmpty()
                  select new UserDTO
                    {
                        Id = user.Id,
                        FirstName = user.FirstName,
                        LastName = user.LastName,
                        Username = user.UserName,
                        Email = user.Email,
                        Roles = Mapper.Map<IList<RoleDTO>>(user.UserRoles.Select(i => i.Role)),
                        LastActivity = user.LastActivity,
                        CreatedAt = user.CreatedAt,
                        EmailConfirmed = user.EmailConfirmed,
                        AccountBalance = a.Accounts.Where(p => p.CurrencyId == a.OperationCountry.LocalCurrencyId).Single().Balance,
                        AccountReference = a.Accounts.Where(p => p.CurrencyId == a.OperationCountry.LocalCurrencyId).Single().AccountRef
                    };

        // Apply search term
        if (!IsNullOrEmpty(filter.SearchTerm))
            query = query.Where(w =>
                w.FirstName.Contains(filter.SearchTerm) 
                w.LastName.Contains(filter.SearchTerm) ||
                w.Email.Contains(filter.SearchTerm) ||
                w.AccountReference.Contains(filter.SearchTerm));

        if (filter.ColumnFilters != null)
        {
            if (filter.ColumnFilters.ContainsKey("EmailConfirmed"))
            {
                var valueStr = filter.ColumnFilters["EmailConfirmed"];
                if (bool.TryParse(valueStr, out var value))
                    query = query.Where(x => x.EmailConfirmed == value);
            }
        }

        // Get total item count before pagination
        var totalItemCount = query.Count();
        // Apply pagination
        query = query.ApplySortAndPagination(filter);
        var userDtoList = query.ToList();

        return new ListResponse<UserDTO>()
        {
            List = userDtoList,
            TotalCount = totalItemCount
        };
 }
此查询生成的SQL查询运行速度也很慢,而如果我用SQL编写联接查询,它将在几百毫秒内完成。我怀疑我遇到了N+1查询问题,但不确定,因为当我在SQL Server Profiler中跟踪时,EF似乎生成了一个查询

这是实体框架生成的查询,当我在SSMS上运行时,大约需要8秒钟:

exec sp_executesql N'SELECTTOP@__p_4[w].[Id],[w].[AccessFailedCount],[w].[ConcurrencyStamp],[w].[CreatedAt],[w].[CreatedBy],[w].[DeletedBy],[w].[DetailId],[w].[EmailConfirmDat],[w].[FacebookId],[w].[FirstName],[w].[GoogleId],[w].[IsActive],[w].[Isted],[w].[EmailConfirmActivity],[w].[w].[LastActivity],[w]。[LastName]、[w].[LockoutEnabled]、[w].[LockoutEnd]、[w].[NormalizedEmail]、[w].[NormalizedUserName]、[w].[PasswordHash]、[w].[PhoneNumber]、[w].[RoleId]、[w].[SecurityStamp]、[w].[TwoFactorEnabled]、[w].[w].[UpdatedAt]、[w].[w].[w].[w].[w].[w].[w].[w].[UserName]、[w].[w].[w].[w].[w].[FlowID]、[t].[Account].[Id]、[HolderLevel][AccountHolderType]、[t]、[CreatedAt]、[t]、[CreatedBy]、[t]、[DeletedBy]、[t]、[IsDeleted]、[t]、[t]、[OperationCountryId]、[t]、[UpdatedAt]、[t]、[UpdatedBy]、[t0]、[Id]、[t0]、[ContinumeId]、[t0]、[CountryCode]、[t0]、[t0]、[CreatedBy]、[t0]、[CreatedBy]、[t0]、[IsatedBy]、[t0]、[t0]、[Isat0]、[IsatedBy]、[2]、[Isat0]。[IsDeleted]、[t0].[IsOperational]、[t0].[LocalCurrencyId]、[t0].[Name]、[t0].[PhoneCode]、[t0].[PostCodeProvider]、[t0].[Regex]、[t0].[SmsProvider]、[t0].[UpdatedAt]、[t0].[UpdatedBy] 来自[用户]作为[w] 左连接 选择[a].[Id],[a].[AccountHolderLevel],[a].[AccountHolderType],[a].[CreatedAt],[a].[CreatedBy],[a].[DeletedBy],[a].[IsDeleted],[a].[OperationCountryId],[a].[UpdatedAt],[a].[UpdatedBy] 来自[账户持有人]作为[a] 其中[a].[IsDeleted]=0 作为[w].[Id]=[t].[ObjectId]上的[t] 左连接 选择[c].[Id]、[c].[c].[c].[CountryCode]、[c].[CreatedAt]、[c].[CreatedBy]、[c].[DeletedAt]、[c].[IsCode2]、[c].[IsDeleted]、[c].[IsDeleted]、[c].[IsDeleted]、[c].[LocalCurrencyId]、[c].[c].[Name]、[c].[PhoneCode]、[c].[c].[c].[c].[c].[PostCodeProvider]、[c]、[c].[c].[c].[c].[Regex]、[c].[c].[c].[c].[c].[S] 来自[国家]作为[c] 其中[c].[IsDeleted]=0 与[t].[OperationCountryId]=[t0].[Id]上的[t0]相同 其中[w].[IsDeleted]=0和[w].[FirstName]类似于@过滤搜索项[U 0+N%,左[w].[FirstName],LEN@__filter_SearchTerm_0=@@@u filter\u SearchTerm\u 0或@@@u filter\u SearchTerm\u 0=N或[w].[LastName]类似于@@@u filter\u SearchTerm\u 1+N%,左[w].[LastName],LEN@__filter_SearchTerm_1=@u filter\u SearchTerm\u 1或@u filter\u SearchTerm\u 1=N或[w]。[用户名]如@uuu filter_SearchTerm_2+N%并左[w]。[用户名],LEN@__filter_SearchTerm_2=@\uuu filter\u SearchTerm\u 2或@\uu filter\u SearchTerm\u 2=N或存在 选择1 从[账户]到[x] 其中[x].[IsDeleted]=0和[x].[AccountRef]类似于过滤器搜索项3+N%,左[x].[AccountRef],LEN@__filter_SearchTerm_3=@\uuu filter\u SearchTerm\u 3或@\uu filter\u SearchTerm\u 3=N和[t].[Id]=[x].[AccountHolderId] [w][w][Last活动[Last活动]描述,[w][w][w][C[C[C[C[w][w][w][w]的订单订单[w]的订单[Last活动[C]描述,[w]描述,[C[C]描述[C[w]描述,[w][w[C[C[D],,N'N',N'U你们们对一些网站搜索关键词的一些国际,,,[U过滤器的过滤搜索关键词搜索关键词关键词搜索关键词关键词搜索关键词搜索关键词搜索关键词搜索关键词搜索关键词搜索关键词搜索关键词搜索关键词搜索关键词搜索关键词搜索关键词,[w[w[w[w[w[w[w[w[w][w[w[w[w][w][Id][w[Id][w][w][w][w][w][w][w][w[w[w[w[w[w[w[w[w[w[w[w[w[w]“,@\u过滤器\u搜索项\u 3=N'james” 最后,这是我的SQL查询,它在不到100毫秒的时间内返回所需的内容:

声明@searchTerm varchar100='%james%' 选择前10名 u、 身份证, u、 用户名, u、 名字, u、 姓, u、 最后一项活动, u、 创建数据, a、 平衡, a、 AccountRef, 啊,账户持有人级别, u、 电子邮件, r、 名字 来自用户u 在ah.ObjectId=u.Id上加入帐户持有人ah 加入ah.Id=a.AccountHolderId上的帐户a 在ur.UserId=u.Id上加入用户角色ur 在r.Id=ur.RoleId上加入角色r 其中FirstName如@searchTerm或LastName如@searchTerm或u.UserName如@searchTerm或FirstName+“”+LastName如@searchTerm或a.AccountRef如@searchTerm a.CurrencyId=ah.OperationCountryId 顺便说一下,我正在搜索的列都被索引了,所以这不是问题。我知道新的EF Core有很多性能改进。不幸的是,我无法更新,因为有太多的破坏性更改

我不确定将查询分为两个部分,一个是针对用户的,一个是针对帐户的,因为会再次出现连接。如果我不能确定的话 d使用I计划将查询转换为视图的解决方案,但我希望将其作为最后手段,因为我们的约定是尽可能多地使用EF。我拒绝相信EF没有解决方案。这实际上根本不是一个复杂的查询,我相信这是一个相当常见的用例

那么,使用EF Core优化此查询的最佳方法是什么

那么,使用EF Core优化此查询的最佳方法是什么

从2.1 3.0、3.1、5.0到现在的6.0,EF核心查询管道中的许多事情都发生了变化,但是可以使用一些通用规则,目的是摆脱从3.0开始根本不支持的客户端查询评估,因此最好开始为今年8月结束的2.1的切换支持做准备

第一种方法是删除所有这些Include/thenclude。如果查询在不涉及实体实例的情况下将结果投影到DTO中,那么所有这些都是冗余的/不需要的,删除它们将确保查询完全转换为SQL

var query = _dbContext.Users.AsQueryable();
// Apply filters...
接下来是角色集合。必须删除Mapper.Map调用,否则无法转换。通常,要么使用AutoMapper映射和ProjectTo来完全处理投影,要么根本不使用它,永远不要将映射方法调用放入查询表达式树中。根据您的SQL,应该是这样的

Roles = user.UserRoles.Select(ur => ur.Role)
    .Select(r => new RoleDTO { Name = r.Name })
    .ToList(),
from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
select new //UserDTO
{
    Id = user.Id,
    FirstName = user.FirstName,
    LastName = user.LastName,
    Username = user.UserName,
    Email = user.Email,
    Roles = user.UserRoles.Select(ur => ur.Role)
       .Select(r => new RoleDTO { Name = r.Name })
       .ToList(),
    LastActivity = user.LastActivity,
    CreatedAt = user.CreatedAt,
    EmailConfirmed = user.EmailConfirmed,
    AccountBalance = a.Balance,
    AccountReference = a.AccountRef
}
实际上,EF Core将以单独查询的方式执行此操作,3.x中的单个查询模式打破了这种行为,并可选择使用6.0拆分查询模式返回,因此在最后调用ToList非常重要,否则您将得到N+1个查询,而不是2个查询

最后是一个电话。可以通过使用相关SelectMany或其查询语法等效项展平子集合来避免这种情况

from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
let语句不是强制性的,我添加它只是为了可读性。现在,您可以在最终选择中使用范围变量user、ah和a,类似于SQL中的表别名

另外,由于SQL查询并没有真正强制执行单帐户匹配,所以在LINQ查询中也没有这样的强制。如果需要,那么可以使用SelectMany+Where+`Take1实现单曲的等效,例如

from user in query
let ah = user.AccountHolder
from a in ah.Accounts
    .Where(a => a.CurrencyId == ah.OperationCountryId)
    .Take(1)
查询和方法语法的混合,但LINQ允许这样做

最后的查询是这样的

Roles = user.UserRoles.Select(ur => ur.Role)
    .Select(r => new RoleDTO { Name = r.Name })
    .ToList(),
from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
select new //UserDTO
{
    Id = user.Id,
    FirstName = user.FirstName,
    LastName = user.LastName,
    Username = user.UserName,
    Email = user.Email,
    Roles = user.UserRoles.Select(ur => ur.Role)
       .Select(r => new RoleDTO { Name = r.Name })
       .ToList(),
    LastActivity = user.LastActivity,
    CreatedAt = user.CreatedAt,
    EmailConfirmed = user.EmailConfirmed,
    AccountBalance = a.Balance,
    AccountReference = a.AccountRef
}
并且应该转换为非常类似于手工编制的SQL。希望执行速度能像它一样快

那么,使用EF Core优化此查询的最佳方法是什么

从2.1 3.0、3.1、5.0到现在的6.0,EF核心查询管道中的许多事情都发生了变化,但是可以使用一些通用规则,目的是摆脱从3.0开始根本不支持的客户端查询评估,因此最好开始为今年8月结束的2.1的切换支持做准备

第一种方法是删除所有这些Include/thenclude。如果查询在不涉及实体实例的情况下将结果投影到DTO中,那么所有这些都是冗余的/不需要的,删除它们将确保查询完全转换为SQL

var query = _dbContext.Users.AsQueryable();
// Apply filters...
接下来是角色集合。必须删除Mapper.Map调用,否则无法转换。通常,要么使用AutoMapper映射和ProjectTo来完全处理投影,要么根本不使用它,永远不要将映射方法调用放入查询表达式树中。根据您的SQL,应该是这样的

Roles = user.UserRoles.Select(ur => ur.Role)
    .Select(r => new RoleDTO { Name = r.Name })
    .ToList(),
from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
select new //UserDTO
{
    Id = user.Id,
    FirstName = user.FirstName,
    LastName = user.LastName,
    Username = user.UserName,
    Email = user.Email,
    Roles = user.UserRoles.Select(ur => ur.Role)
       .Select(r => new RoleDTO { Name = r.Name })
       .ToList(),
    LastActivity = user.LastActivity,
    CreatedAt = user.CreatedAt,
    EmailConfirmed = user.EmailConfirmed,
    AccountBalance = a.Balance,
    AccountReference = a.AccountRef
}
实际上,EF Core将以单独查询的方式执行此操作,3.x中的单个查询模式打破了这种行为,并可选择使用6.0拆分查询模式返回,因此在最后调用ToList非常重要,否则您将得到N+1个查询,而不是2个查询

最后是一个电话。可以通过使用相关SelectMany或其查询语法等效项展平子集合来避免这种情况

from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
let语句不是强制性的,我添加它只是为了可读性。现在,您可以在最终选择中使用范围变量user、ah和a,类似于SQL中的表别名

另外,由于SQL查询并没有真正强制执行单帐户匹配,所以在LINQ查询中也没有这样的强制。如果需要,那么可以使用SelectMany+Where+`Take1实现单曲的等效,例如

from user in query
let ah = user.AccountHolder
from a in ah.Accounts
    .Where(a => a.CurrencyId == ah.OperationCountryId)
    .Take(1)
查询和方法语法的混合,但LINQ允许这样做

最后的查询是这样的

Roles = user.UserRoles.Select(ur => ur.Role)
    .Select(r => new RoleDTO { Name = r.Name })
    .ToList(),
from user in query
let ah = user.AccountHolder
from a in ah.Accounts
where a.CurrencyId == ah.OperationCountryId
select new //UserDTO
{
    Id = user.Id,
    FirstName = user.FirstName,
    LastName = user.LastName,
    Username = user.UserName,
    Email = user.Email,
    Roles = user.UserRoles.Select(ur => ur.Role)
       .Select(r => new RoleDTO { Name = r.Name })
       .ToList(),
    LastActivity = user.LastActivity,
    CreatedAt = user.CreatedAt,
    EmailConfirmed = user.EmailConfirmed,
    AccountBalance = a.Balance,
    AccountReference = a.AccountRef
}

并且应该转换为非常类似于手工编制的SQL。希望执行得更快,与之类似。

这是我的SQL查询,返回此SQL查询中所需的任何内容。您没有以任何方式限制帐户记录,所以您如何知道它返回的是什么?单个帐户在LINQ查询中使用单个调用尝试执行什么操作?除非AccountHolderId和CurrencyId的组合是唯一的,否则我看不出这是怎么发生的。哦,是的,它确实是唯一的。这是我的SQL查询,它返回在这个SQL查询中您不限制帐户记录的所需内容
不管怎样,你怎么知道它会返回呢?单个帐户在LINQ查询中使用单个调用尝试执行什么操作?我不知道怎么会这样,除非AccountHolderId,CurrencyId的组合是唯一的。哦,是的,它确实是唯一的。很好,谢谢,我会试试的。去掉Include语句可能会大大加快它的上传速度,谢谢,我会试试的。去掉Include语句可能会加快速度