C# 在内部使用异步/等待。选择lambda

C# 在内部使用异步/等待。选择lambda,c#,asynchronous,lambda,asp.net-core,asp.net-identity,C#,Asynchronous,Lambda,Asp.net Core,Asp.net Identity,我正在使用Asp.NETCoreIdentity并试图简化一些代码,这些代码将用户列表及其角色投影到ViewModel。这段代码可以工作,但在试图简化它的过程中,我陷入了错误和好奇的疯狂漩涡 这是我的工作代码: var allUsers = _userManager.Users.OrderBy(x => x.FirstName); var usersViewModel = new List<UsersViewModel>();

我正在使用Asp.NETCoreIdentity并试图简化一些代码,这些代码将用户列表及其角色投影到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);
        }
这会中断,因为我没有在用户之前的lambda表达式中使用async关键字。然而,当我在用户之前添加async时,我得到了另一个错误,即“async lambda表达式无法转换为表达式树”

我猜想GetRolesAsync()方法返回任务并将其分配给角色,而不是该任务的实际结果。在我的一生中,我似乎不知道如何让它发挥作用

在过去的一天里,我研究并尝试了很多方法,但都没有成功。以下是我看到的一些:

诚然,我不完全理解async/Wait是如何工作的,所以这可能是问题的一部分。我的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))
        };
    }
        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());
这两个操作都会产生错误消息:“InvalidOperationException:在上一个操作完成之前在此上下文上启动了第二个操作。不保证任何实例成员都是线程安全的。”

在这一点上,我原来的ForEach似乎是最好的选择,但我仍然在想,如果我使用异步方法,那么什么才是正确的方法呢

编辑2-带答案 多亏了Tseng的评论(以及其他一些研究),我能够使用以下代码使事情顺利进行:

    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))
        };
    }
        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,只是想看看DB实际获得了多少点击率——正如Matt Johnson所提到的,点击率非常高(N+1)


因此,虽然这确实回答了我的问题,但我现在正在重新考虑如何运行查询,可能只是在主视图中删除角色,并且仅在选择每个用户时才将其拉入。不过,通过这个问题,我确实学到了很多(也学到了更多我不知道的东西),所以谢谢大家

我认为你把两件事混为一谈了。表达式树和委托。Lambda可以用来表示这两个参数,但它取决于方法接受的参数类型,该参数将在其中一个参数中转换

传递给方法的lambda,作为
Action
Func
将转换为委托(基本上是匿名函数/方法)

将lambda表达式传递给接受
表达式的方法时,将从lambda创建表达式树。表达式树只是描述代码的代码,而不是代码本身

也就是说,表达式树无法执行,因为它已转换为可执行代码。您可以在运行时编译表达式树,然后像委托一样执行它

ORM框架使用表达式树来允许您编写“代码”,这些代码可以翻译成不同的东西(例如数据库查询),或者在运行时动态生成代码

因此,您不能在接受
表达式的方法中使用
async
。当您将其转换为
AsEnumerable()
时,它可能会工作的原因是,它返回一个
IEnumerable
,并且其上的LINQ方法接受
Func
。但它本质上是获取整个查询,并在内存中执行所有操作,因此您不能使用投影(或者您必须在仅使用表达式和投影之前获取数据),将过滤结果转换为列表,然后对其进行过滤

您可以尝试以下方法:

// Filter, sort, project it into the view model type and get the data as a list
var users = await allUsers.OrderBy(user => user.FirstName)
                             .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
    }).ToListAsync();

// now that we have the data, we iterate though it and 
// fetch the roles
var userViewModels = users.Select(async user => 
{
    user.Roles = string.Join(", ", await _userManager.GetRolesAsync(user))
});
第一部分将完全在数据库上完成,您将保留所有优势(即,顺序发生在数据库上,因此在获取结果后不必进行内存排序,而limit调用将限制从数据库中获取的数据等)


第二部分迭代内存中的结果,获取每个临时模型的数据,最后将其映射到视图模型中

这里有一个解决方案,您可以使用
列表作为回报

var userViewModels = (await allUsers).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))
        }).Select(q => q.Result);
更新1: 多亏了@TheodorZoulias,我意识到
.Select(q=>q.Result)
会导致线程阻塞。 因此,我认为在sb找到更好的方法之前,最好使用此解决方案它还可以洗牌这些项目。

List<UsersViewModel> userViewModels = new();
(await allUsers)
.Select(async user => new UsersViewModel()
{
    //(...)
    Roles = string.Join(", ", await _userManager.GetRolesAsync(user))
})
.ToList()
.ForEach(async q => userViewModels.Add(await q));
List userViewModels=new();
(等待诱惑者)
.Select(异步用户=>newusersviewmodel()
{
//(...)
Roles=string.Join(“,”等待_userManager.GetRolesAsync(用户))
})
托利斯先生()
.ForEach(异步q=>userViewModels.Add(等待q));

我的猜测是GetRolesAsync()方法返回一个任务并将其分配给角色,而不是该任务的实际结果。
-可能不是,因为
await
将获取任务的结果。请尝试将
AsEnumerable
放在
Select
之前,这样它将在LInq to Objects中运行,而不是尝试将其转换为EF或您使用的任何提供程序的表达式树。var usersViewModels=(await task.WhenAll(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=stri)