在内部使用async/await.选择lambda

Fra*_*ain 11 c# lambda asynchronous asp.net-identity asp.net-core

我正在使用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);
        }
Run Code Online (Sandbox Code Playgroud)

在尝试简化代码时,我想我可以做这样的事情 (破碎的代码):

        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();
Run Code Online (Sandbox Code Playgroud)

这会中断,因为我没有在用户之前使用 lambda表达式中的async关键字.但是,当我在用户之前添加异步时,我得到另一个错误,上面写着"异步lambda表达式无法转换为表达式树"

我的猜测是GetRolesAsync()方法返回一个Task并将其分配给Roles而不是该任务的实际结果.我似乎无法弄清楚我的生活是如何让它发挥作用.

我在过去的一天里研究并尝试了很多方法而没有运气.以下是我看过的一些内容:

是否可以在非异步方法中调用等待方法?

https://blogs.msdn.microsoft.com/pfxteam/2012/04/12/asyncawait-faq/

在IEnumerable.Select中调用异步方法

如何使用LINQ异步等待任务列表?

如何在lambda中使用async/await

如何在返回集合的lambda中使用async

不可否认,我并不完全理解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))
        };
    }
Run Code Online (Sandbox Code Playgroud)

我也试过像这样使用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();
Run Code Online (Sandbox Code Playgroud)

这两个都会产生错误消息:"InvalidOperationException:在上一个操作完成之前,在此上下文中启动了第二个操作.不保证所有实例成员都是线程安全的."

在这一点上,似乎我的原始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());
Run Code Online (Sandbox Code Playgroud)

虽然现在我已经考虑了每个人的评论,但我开始仔细研究SQL Profiler,看看数据库实际得到了多少命中 - 正如Matt Johnson提到的那样,它很多(N + 1).

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

Tse*_*eng 11

我想你在这里混合了两件事.表达树和代表.Lambda可用于表示它们,但它取决于方法接受的参数类型,它将转向哪一个.

一个lambda传递给作为方法Action<T>Func<T, TResult>将被转换成一个代表(基本上匿名功能/方法).

将lambda表达式传递给方法接受时Expression<T>,可以从lambda创建表达式树.表达式树只是描述代码的代码,但不是代码本身.

话虽如此,表达式树无法执行,因为它已转换为可执行代码.您可以在运行时编译表达式树,然后像委托一样执行它.

ORM框架使用表达式树来允许您编写可以翻译成不同内容的"代码"(例如数据库查询)或在运行时动态生成代码.

因此,您不能async在接受的方法中使用Expression<T>.它转换为它时可能起作用的原因AsEnumerable()是因为它返回一个IEnumerable<T>并且它上面的LINQ方法接受Func<T, TResult>.但它基本上取出整个查询并在内存中完成所有内容,因此您无法使用投影(或者您只需在使用表达式和投影之前获取数据),将过滤后的结果转换为列表然后对其进行过滤.

你可以尝试这样的事情:

// 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))
});
Run Code Online (Sandbox Code Playgroud)

第一部分将完全在数据库上完成,您将保留所有优势(即订单发生在数据库中,因此您不必在获取结果后进行内存中排序,限制调用限制从数据库获取的数据DB等).

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


归档时间:

查看次数:

8247 次

最近记录:

8 年,11 月 前