我正在使用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秒的时间来运行?
答案 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>()
中使用IQueryable
到var 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)
您必须意识到两个区别:
IQueryable<TResult>
(懒惰)的函数和返回TResult
(正在执行)的函数之间的区别Enumerable
和Queryable
之间的差异。 AsEnumerable
的LINQ语句应在本地进程中处理。它包含所有代码和执行该语句的所有调用。一旦GetEnumerator
和MoveNext
被调用,则使用foreach
或不返回IEnumerable<...>
的LINQ语句(如{{1 }},ToList
和FirstOrDefault
。
与之相反,Any
并不是要在您的流程中进行处理(但是,如果需要的话,也可以这样做)。通常,它是由不同的过程(通常是数据库管理系统)处理的。
为此,IQueryable
包含一个IQueryable
和一个Expression
。 Provider
代表必须执行的查询。 Expression
知道谁必须执行查询(DBMS),以及该执行者使用哪种语言(通常是SQL)。调用Provider
和GetEnumerator
时,MoveNext
接受Provider
并将其翻译为Expression
的语言。该查询不发送给执行者。返回的数据显示在Executor
中,其中调用了AsEnumerable
和GetEnumerator
。
由于这种转换为SQL,所以IQueryable不能完成IEnumerable可以做的所有事情。最主要的是它不能调用您的本地函数。它甚至不能执行所有LINQ函数。 MoveNext
的质量越好,它可以做的越多。参见supported and unsupported LINQ methods
有两组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
。