繁体   English   中英

SelectMany查询与Where一起产生许多SQL查询

[英]SelectMany query with Where produces many SQL queries

我正在使用GetAppRolesForUser函数(并尝试根据此处的答案进行变体):

private AuthContext db = new AuthContext();
...
var userRoles = Mapper.Map<List<RoleApi>>(
    db.Users.SingleOrDefault(u => u.InternetId == username)
      .Groups.SelectMany(g => g.Roles.Where(r => r.Asset.AssetName == application)));

我最终在SQL Profiler中为每个单个RolesId进行了以下处理:

exec sp_executesql N'SELECT 
    [Extent2].[GroupId] AS [GroupId], 
    [Extent2].[GroupName] AS [GroupName]
    FROM  [Auth].[Permissions] AS [Extent1]
    INNER JOIN [Auth].[Groups] AS [Extent2] ON [Extent1].[GroupId] = [Extent2].[GroupId]
    WHERE [Extent1].[RolesId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=6786

如何重构,以便EF对userRoles生成单个查询,而无需花费18秒即可运行?

我认为问题在于您懒于加载组和角色。

一种解决方案是在调用SingleOrDefault之前急于加载它们

var user = db.Users.Include(x => x.Groups.Select(y => y.Roles))
                   .SingleOrDefault(u => u.InternetId == username);

var groups = user.Groups.SelectMany(
                   g => g.Roles.Where(r => r.Asset.AssetName == application));

var userRoles = Mapper.Map<List<RoleApi>>(groups);

另请注意 :此处没有健全性检查是否为null。

TheGeneral的答案涵盖了为什么您会因懒惰加载而陷入困境。 您可能还需要包括Asset才能获得AssetName。

使用AutoMapper,可以通过在IQueryable使用.ProjectTo<T>()来避免急于加载实体的需要,前提是Group中有一个可访问的用户。

例如:

var roles = db.Groups.Where(g => g.User.Internetid == username)
   .SelectMany(g => g.Roles.Where(r => r.Asset.AssetName == application))
   .ProjectTo<RoleApi>()
   .ToList();

这应该利用延迟执行,其中AutoMapper将根据您的映射/检查有效地投射所需的.Select()来填充RoleApi实例。

这是避免延迟加载的另一种方法。 您还可以查看投影,仅包含所需的字段,而不是加载整个列。

var userRoles = Mapper.Map<List<RoleApi>>(
db.Users.Where(u => u.InternetId == username).Select(../* projection */ )
  .Groups.SelectMany(g => g.Roles.Where(r => r.Asset.AssetName == application)));

EF还附带:

var userRoles = Mapper.Map<List<RoleApi>>(
db.Users.Where(u => u.InternetId == username).Select(../* projection */ )
  .Include(g => g.Roles.Where(r => r.Asset.AssetName == application)));

然后可以使用多个for循环迭代集合。

您必须注意两个区别:

  • IEnumerable和IQueryable之间的区别
  • 返回IQueryable<TResult>函数(延迟)与返回TResult函数(执行)之间的区别

区别EnumerableQueryable

可以在本地进程中处理AsEnumerable LINQ语句。 它包含所有代码和执行该语句的所有调用。 MoveNext GetEnumeratorMoveNext ,将显式地或隐式地使用不返回IEnumerable<...> foreach或LINQ语句(例如ToListFirstOrDefaultAny来执行该语句。

相比之下, IQueryable并不是要在您的流程中进行处理(但是,可以根据需要进行处理)。 它通常意味着要由不同的过程(通常是数据库管理系统)来处理。

为此, IQueryable包含一个Expression和一个Provider Expression表示必须执行的查询。 Provider知道谁必须执行查询(DBMS),以及该执行者使用哪种语言(通常是SQL)。 MoveNext GetEnumeratorMoveNextProviderExpression转换为Executor的语言。 该查询不发送给执行者。 返回的数据显示为AsEnumerable ,其中MoveNext GetEnumeratorMoveNext

由于这种转换为SQL,所以IQueryable不能完成IEnumerable可以做的所有事情。 最主要的是它不能调用您的本地函数。 它甚至不能执行所有LINQ函数。 Provider的质量越好,它可以做的越多。 查看受支持和不受支持的LINQ方法

惰性LINQ方法和执行LINQ方法

有两组LINQ方法。 那些返回`IQueryable <...> / IEnumerable <...>的不返回。

第一组使用延迟加载。 这意味着在LINQ语句的末尾已创建了查询,但尚未执行。 只有“ GetEnumerator and MoveNext” will make that the提供程序will translate the Expression并命令DBMS执行查询。

串联IQueryables只会更改Expression 这是一个相当快的过程。 因此,如果执行一个大型LINQ表达式而不是将它们串联起来,则不会提高性能。

通常,与您的流程相比,DBMS更加智能,并且为进行选择做好了更好的准备。 将所选数据传输到本地过程是查询中较慢的部分之一。

建议:尝试创建LINQ语句,以使执行语句是DBMS可以执行的最后一条语句。 确保仅选择您实际计划使用的属性。

因此,例如,如果您不使用外键,请不要转移它们。

回到您的问题

让映射器脱离您开始的问题:

db.Users.SingleOrDefault(...)

SingleOrDefault是非延迟函数。 它不返回IQueryable<...> 它将执行查询。 它将一个完整的User (包括其Roles传输到您的本地流程。

建议将SingleOrDefault推迟到最后一个语句:

var result = myDbcontext.Users
    .Where(user => user.InternetId == username)
    .SelectMany(user => user.Groups.Roles.Where(role => role.Asset.AssetName == application))

     // until here, the query is not executed yet, execute it now:
     .SingleOrDefault();

在的话:从序列Users ,只保留那些UsersInternetId ,等于userName 从所有剩余的Users (您希望只有一个)中,选择每个User GroupsRoles序列。 但是,我们不希望选择所有Roles ,我们只保留AssetName等于applicationRoles 现在,将所有剩余的Roles放入一个集合中( SelectManymany部分),然后选择期望的零或一个剩余Role

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM