我正在使用Asp.Net Core Identity,并尝试简化一些代码,以将用户列表及其角色投影到ViewModel中。 这段代码可以正常工作,但是当我尝试简化它时,我进入了一个疯狂的错误和好奇心的旋涡。
以下是我的工作代码:
var allUsers = _userManager.Users.OrderBy(x => x.FirstName);
var usersViewModel = new List<UsersViewModel>();
foreach (var user in allUsers)
{
var tempVm = new UsersViewModel()
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
DisplayName = user.DisplayName,
Email = user.Email,
Enabled = user.Enabled,
Roles = String.Join(", ", await _userManager.GetRolesAsync(user))
};
usersViewModel.Add(tempVm);
}
为了简化代码,我想可以像这样做(破损的代码):
var usersViewModel = allUsers.Select(user => new UsersViewModel
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
DisplayName = user.DisplayName,
Email = user.Email,
Enabled = user.Enabled,
Roles = string.Join(", ", await _userManager.GetRolesAsync(user))
}).ToList();
由于在user之前的 lambda 表达式中没有使用 async 关键字,因此代码会出现错误。但是,当我在user之前添加 async 关键字时,又会出现另一个错误,显示“异步 lambda 表达式无法转换为表达式树”。
我猜测 GetRolesAsync() 方法返回的是 Task,而不是该任务的实际结果并将其分配给 Roles。至于如何使其正常工作,我好像无论如何都弄不明白。
过去一天里,我研究并尝试了许多方法,但都没有成功。以下是我查看的一些示例:
Is it possible to call an awaitable method in a non async method?
https://blogs.msdn.microsoft.com/pfxteam/2012/04/12/asyncawait-faq/
Calling async method in IEnumerable.Select
How to await a list of tasks asynchronously using LINQ?
how to user async/await inside a lambda
How to use async within a lambda which returns a collection
诚然,我并不完全理解 async / await 的工作原理,这可能是问题的一部分。我的 foreach 代码可以工作,但我想要理解如何实现我试图做到的方式。由于我已经花了这么多时间,所以觉得这是一个很好的第一个问题。
谢谢!
编辑
我猜我必须解释一下我在每个参考文章中所做的事情,这样这篇文章才不会被标记为重复问题 - 我真的很努力地避免这种情况 :-/。虽然问题听起来相似,但结果却不同。在被标记为答案的文章中,我尝试了以下代码:
public async Task<ActionResult> Users()
{
var allUsers = _userManager.Users.OrderBy(x => x.FirstName);
var tasks = allUsers.Select(GetUserViewModelAsync).ToList();
return View(await Task.WhenAll(tasks));
}
public async Task<UsersViewModel> GetUserViewModelAsync(ApplicationUser user)
{
return new UsersViewModel
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
DisplayName = user.DisplayName,
Email = user.Email,
Enabled = user.Enabled,
Roles = String.Join(", ", await _userManager.GetRolesAsync(user))
};
}
我也尝试使用 AsEnumerable,像这样:
var usersViewModel = allUsers.AsEnumerable().Select(async user => new UsersViewModel
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
DisplayName = user.DisplayName,
Email = user.Email,
Enabled = user.Enabled,
Roles = string.Join(", ", await _userManager.GetRolesAsync(user))
}).ToList();
这两者都会产生错误消息:"InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe."
此时,似乎我的原始 ForEach 方法可能是最佳选择,但如果我使用异步方法进行操作,应该怎么做才是正确的仍然让我感到疑惑。
编辑 2 - 包含答案 在 Tseng 的评论(和其他一些研究)的帮助下,我能够使用以下代码使事情正常运行:
var userViewModels = allUsers.Result.Select(async user => new UsersViewModel
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
DisplayName = user.DisplayName,
Email = user.Email,
Enabled = user.Enabled,
Roles = string.Join(", ", await _userManager.GetRolesAsync(user))
});
var vms = await Task.WhenAll(userViewModels);
return View(vms.ToList());
尽管我已经考虑了每个人的评论,但我还是开始更仔细地查看SQL Profiler,以了解实际上有多少次访问数据库-正如Matt Johnson提到的那样,这很多(N+1)。
因此,虽然这确实回答了我的问题,但现在我正在重新考虑如何运行查询,可能只需在选择每个用户时删除主视图中的角色并仅拉取它们。 通过这个问题,我肯定学到了很多东西(也学到了更多我不知道的东西),所以谢谢大家。
Select
之前加上AsEnumerable
,这样它就会在LInq to Objects中运行,而不是尝试将其转换为EF或其他提供程序的表达式树。 - juharrGetRolesAsync
显然也在数据库上下文中运行某些内容,因此您不能同时进行多个并行的GetRolesAsync
调用。您需要按顺序运行它们。根据Matt Johnson的评论,如果您确实需要它们,应考虑一种同时查询多个(所有)用户角色的方法。 - 顺便说一句。您最初的问题完全省略了与这些链接问题相关的部分,现在您已经编辑了它,我不清楚为什么您没有搜索错误消息。 - poke