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

时间:2018-10-30 01:13:53

标签: c# entity-framework linq entity-framework-6

我正在使用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秒的时间来运行?

4 个答案:

答案 0 :(得分:1)

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

一种解决方案是在调用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);

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

答案 1 :(得分:1)

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

使用AutoMapper,您可以通过在.ProjectTo<T>()中使用IQueryablevar roles = db.Groups.Where(g => g.User.Internetid == username) .SelectMany(g => g.Roles.Where(r => r.Asset.AssetName == application)) .ProjectTo<RoleApi>() .ToList(); 来避免急于加载实体的需要。

例如:

.Select()

这应该利用延迟执行,其中AutoMapper将根据您的映射/检查有效地填充for (int i = 0; i <= sTagsContext.GetUpperBound(0); i++) { cmListTags.Items.Add(sTagsContext[i]); ToolStripMenuItem submenu = new ToolStripMenuItem(); submenu.Text = i.ToString(); submenu.Image = Properties.Resources.InfoBig; (cmListTags.Items[i] as ToolStripMenuItem).DropDownItems.Add(submenu); chkListTags.ContextMenuStrip = cmListTags; } 中的项目,以填充RoleApi实例。

答案 2 :(得分:0)

这是避免延迟加载的另一种方法。您还可以查看投影,只显示需要的字段,而不用加载整个列。

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循环迭代集合。

答案 3 :(得分:0)

您必须意识到两个区别:

  • IEnumerable和IQueryable之间的区别
  • 返回IQueryable<TResult>(懒惰)的函数和返回TResult(正在执行)的函数之间的区别

EnumerableQueryable之间的差异

AsEnumerable的LINQ语句应在本地进程中处理。它包含所有代码和执行该语句的所有调用。一旦GetEnumeratorMoveNext被调用,则使用foreach或不返回IEnumerable<...>的LINQ语句(如{{1 }},ToListFirstOrDefault

与之相反,Any并不是要在您的流程中进行处理(但是,如果需要的话,也可以这样做)。通常,它是由不同的过程(通常是数据库管理系统)处理的。

为此,IQueryable包含一个IQueryable和一个ExpressionProvider代表必须执行的查询。 Expression知道谁必须执行查询(DBMS),以及该执行者使用哪种语言(通常是SQL)。调用ProviderGetEnumerator时,MoveNext接受Provider并将其翻译为Expression的语言。该查询不发送给执行者。返回的数据显示在Executor中,其中调用了AsEnumerableGetEnumerator

由于这种转换为SQL,所以IQueryable不能完成IEnumerable可以做的所有事情。最主要的是它不能调用您的本地函数。它甚至不能执行所有LINQ函数。 MoveNext的质量越好,它可以做的越多。参见supported and unsupported LINQ methods

惰性LINQ方法和执行LINQ方法

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

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

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

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

  

建议:尝试创建LINQ语句,例如执行   语句是DBMS可以执行的最后一条语句。确保   您只需选择您实际打算使用的属性。

例如,如果您不使用外键,请勿转让。

回到您的问题

将映射器排除在您开始的问题之外:

Expression

SingleOrDefault是非惰性函数。它不返回db.Users.SingleOrDefault(...) 。它将执行查询。它将一个完整的IQueryable<...>传送到您的本地进程,包括其User

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

Roles

用词表示:从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等于Users的{​​{1}}。从所有剩余的InternetId(您希望只是一个)中,选择每个userName的{​​{1}}中的Users的顺序。但是,我们不想选择全部Roles,我们只保留Groups等于User的{​​{1}}。现在,将所有剩余的Roles放入一个集合中(Roles中的AssetName部分),然后选择您期望的零个或一个剩余的application