2016-09-15 12 views
5

Tôi đang sử dụng Asp.Net Core Identity và cố gắng đơn giản hóa một số mã dự án danh sách người dùng và vai trò của họ cho ViewModel. Mã này hoạt động, nhưng trong cố gắng đơn giản hóa nó tôi đã đi vào một xoắn ốc điên rồ của các lỗi và sự tò mò.Sử dụng async/await bên trong .Chọn lambda

Đây là mã làm việc của tôi:

 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); 
     } 

Trong cố gắng để đơn giản hóa mã, I figured tôi có thể làm một cái gì đó giống như (mã bị hỏng) này:

 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(); 

này ngắt vì tôi không sử dụng từ khóa không đồng bộ trong biểu thức lambda trước người dùng. Tuy nhiên, khi tôi làm thêm async trước dùng, tôi nhận được thêm một lỗi mà nói "Async biểu thức lambda không thể được chuyển đổi sang cây khái niệm"

tôi đoán là phương pháp GetRolesAsync() đang trả lại công tác và gán nó cho Vai trò thay vì kết quả thực tế của tác vụ đó. Những gì tôi không thể hình dung ra cho cuộc sống của tôi là làm thế nào để làm cho nó hoạt động.

Tôi đã nghiên cứu và thử nhiều phương pháp trong ngày qua mà không có may mắn. Dưới đây là một vài mà tôi nhìn:

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

Phải thừa nhận rằng, tôi hoàn toàn không hiểu cách hoạt động của đồng bộ/chờ đợi, vì vậy đó có thể là một phần của vấn đề. Mã foreach của tôi hoạt động, nhưng tôi muốn có thể hiểu cách làm cho nó hoạt động theo cách tôi đang cố gắng. Vì tôi đã dành rất nhiều thời gian cho nó nên tôi đã nghĩ đây sẽ là một câu hỏi hay.

Cảm ơn!

Sửa

Tôi đoán tôi phải giải thích những gì tôi đã làm trong mỗi trường hợp của các bài viết tôi nghiên cứu để cho điều này để không bị gắn cờ là một câu hỏi trùng lặp - và tôi đã cố gắng thực sự khó khăn để tránh điều đó: - /. Trong khi câu hỏi có vẻ tương tự, kết quả thì không. Trong trường hợp của bài viết đã được đánh dấu là một câu trả lời tôi đã cố gắng đoạn mã sau:

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)) 
     }; 
    } 

Tôi cũng đã cố gắng sử dụng AsEnumerable như vậy:

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(); 

Cả hai sản xuất thông báo lỗi: " InvalidOperationException: Một thao tác thứ hai bắt đầu trên ngữ cảnh này trước khi một thao tác trước đó hoàn thành.Bất kỳ thành viên cá thể nào cũng không được bảo đảm là chuỗi an toàn. "

Tại thời điểm này có vẻ như ForEach ban đầu của tôi có thể là đặt cược tốt nhất, nhưng tôi vẫn tự hỏi điều gì sẽ là đúng cách để làm điều này nếu tôi là để làm điều đó bằng cách sử dụng phương pháp async

chỉnh sửa 2 - với lời Nhờ comments Tseng (và một số nghiên cứu khác) tôi đã có thể làm cho mọi việc làm việc bằng cách sử dụng đoạn mã sau:.

 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()); 

Mặc dù bây giờ tôi đã lấy tất cả mọi người ý kiến ​​xem xét, tôi bắt đầu nhìn gần hơn tại SQL Profiler chỉ để xem có bao nhiêu lượt truy cập DB thực sự nhận được - như Matt Johnson đã đề cập, nó rất nhiều (N + 1).

Vì vậy, trong khi điều này trả lời câu hỏi của tôi, bây giờ tôi xem xét lại cách chạy truy vấn và có thể chỉ thả vai trò trong chế độ xem chính và chỉ kéo chúng khi mỗi người dùng được chọn. Tôi chắc chắn đã học được rất nhiều thông qua câu hỏi này mặc dù (và tìm hiểu thêm về những gì tôi không biết), vì vậy cảm ơn tất cả mọi người.

+0

'Tôi đoán là phương thức GetRolesAsync() đang trả về một nhiệm vụ và gán nó cho vai trò thay vì kết quả thực tế của nhiệm vụ đó.'- có lẽ không, bởi vì' await' sẽ lấy kết quả của nhiệm vụ. – mason

+2

Hãy thử đặt 'AsEnumerable' trước' Select' để nó sẽ được chạy trong LInq to Objects thay vì cố chuyển đổi nó thành cây biểu thức cho EF hoặc bất kỳ nhà cung cấp nào bạn sử dụng. – juharr

+0

var usersViewModels = (chờ Task.WhenAll (allUsers.AsEnumerable(). Chọn (user async => mới 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 ("", chờ đợi _userManager.GetRolesAsync (user)) }))). Liệt kê(); –

Trả lời

7

Tôi nghĩ bạn đang trộn hai thứ ở đây. Cây biểu thị và đại biểu. Lambda có thể được sử dụng để diễn tả cả hai, nhưng nó phụ thuộc vào kiểu tham số mà phương thức chấp nhận trong đó một phương thức sẽ được chuyển.

Một lambda được chuyển đến phương thức như Action<T> hoặc Func<T, TResult> sẽ được chuyển đổi thành đại biểu (về cơ bản là một hàm/phương thức ẩn danh).

Khi bạn chuyển biểu thức lambda vào phương thức chấp nhận Expression<T>, bạn tạo cây biểu thức từ lambda. Cây biểu hiện chỉ là mã mô tả mã, nhưng không phải là mã.

Điều đó đang được nói, một cây biểu thức không thể được thực thi vì nó được chuyển thành mã thực thi. Bạn có thể biên dịch một cây biểu thức trong thời gian chạy và sau đó thực hiện nó như một đại biểu.

ORM Frameworks sử dụng cây biểu thức để cho phép bạn viết "mã" có thể được dịch sang một thứ khác (ví dụ: truy vấn cơ sở dữ liệu) hoặc để tạo mã động khi chạy.

Vì lý do đó bạn không thể sử dụng async trong các phương thức chấp nhận Expression<T>. Lý do tại sao nó có thể hoạt động khi bạn chuyển đổi nó thành AsEnumerable() là bởi vì nó trả về một IEnumerable<T> và các phương pháp LINQ trên nó chấp nhận Func<T, TResult>. Nhưng về cơ bản nó tìm nạp toàn bộ truy vấn và thực hiện toàn bộ nội dung trong bộ nhớ, vì vậy bạn không thể sử dụng các phép chiếu (hoặc bạn phải tìm nạp dữ liệu trước khi sử dụng các biểu thức và phép chiếu), chuyển kết quả đã lọc vào danh sách rồi lọc nó.

Bạn có thể thử một cái gì đó như thế này:

// 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)) 
}); 

Phần đầu tiên sẽ được thực hiện hoàn toàn trên cơ sở dữ liệu và bạn giữ tất cả lợi thế của bạn (tức là thứ tự xảy ra trên cơ sở dữ liệu, vì vậy bạn không cần phải làm sắp xếp trong bộ nhớ sau khi tìm nạp kết quả và các cuộc gọi giới hạn dữ liệu được tìm nạp từ DB, v.v.).

Phần thứ hai lặp qua kết quả trong bộ nhớ và tìm nạp dữ liệu cho từng mô hình tạm thời và cuối cùng ánh xạ nó vào mô hình chế độ xem.

Các vấn đề liên quan