我正在使用Asp.Net核心標識,並試圖簡化一些代碼,將用戶列表和他們的角色投影到ViewModel。這段代碼很有用,但爲了簡化它,我陷入了一個瘋狂的錯誤和好奇之中。內部使用異步/等待。選擇lambda
這是我工作的代碼:
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();
這因爲我在用戶之前沒有在lambda表達式中使用async關鍵字。然而,當我這樣做之前用戶增加異步,我得到另一個錯誤,即「異步lambda表達式不能轉換爲表達式目錄樹」
我的猜測是,GetRolesAsync()方法返回一個任務和將其分配給角色而不是該任務的實際結果。我似乎無法弄清楚我的生活是如何使它工作。
我在過去的一天研究和嘗試了許多方法,但都沒有運氣。這裏有幾個,我看了看:
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:在完成上一個操作之前,在此上下文中啓動的第二個操作。「
在這一點上,它似乎像我的原始ForEach可能是最好的選擇,但我仍然想知道如果我能做到這一點,正確的方法是什麼?使用異步方法來做到這一點
編輯2 - 使用答案 由於曾國藩的意見(和其他一些研究),我是能夠使事情的工作使用下面的代碼:
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)。
所以,儘管這並回答我的問題,我現在重新考慮如何運行查詢,可能只是下降的角色,在主視圖中,只爲每個用戶選擇拉他們。儘管我通過這個問題學到了很多東西(並且學到了更多我不知道的東西),所以謝謝大家。
'我的猜測是,GetRolesAsync()方法返回一個任務,並將其分配給角色而不是任務的實際結果。' - 可能不會,因爲'await'會抓住任務的結果。 – mason
嘗試在「選擇」之前放置一個'AsEnumerable',以便它將在LInq中運行到對象,而不是嘗試將它轉換爲EF或您使用的任何提供程序的表達式樹。 – juharr
VAR usersViewModels =(AWAIT Task.WhenAll(allUsers.AsEnumerable(),選擇(異步用戶=>新UsersViewModel { 編號= user.Id, 用戶名= user.UserName, 姓= user.FirstName, 名字= user.LastName, DisplayName的= user.DisplayName, 電子郵件= user.Email, 啓用= user.Enabled, 角色=的string.join( 「」,等待_userManager.GetRolesAsync(用戶)) })))。 ToList(); –