locked
Async lambda expressions cannot be converted to expression trees RRS feed

  • Question

  • User-1637592233 posted

    I am getting the error as Async lambda expressions cannot be converted to expression trees

    The LINQ query I have is:

    public async Task<PaginatedList<BookRequestViewModel>> GetRequestedBookList(int pageNumber, int pageSize)
    {
    	//Getting Error here!!
    	var results = _dbContext.BookReservations
    		.Select(async book => new BookRequestViewModel
    		{
    			Requester = await _identityService.GetUserEmailAsync(book.RequesterId)
    
    		}).PaginatedListAsync(pageNumber, pageSize);
    	return results;
    }

    The implementation of GetUserEmailAsync is given as:

    public interface IIdentityService
    {
        Task<string> GetUserEmailAsync(string userId);
    }

    I am trying to convert the result obtained from the LINQ query to PaginatedListAsync, which have the definition as:

    public static Task<PaginatedList<TDestination>> PaginatedListAsync<TDestination>(this IQueryable<TDestination> queryable, int pageNumber, int pageSize)
    {
    	return PaginatedList<TDestination>.CreateAsync(queryable, pageNumber, pageSize);
    }

    The current implementation of PaginatedList<TDestination>.CreateAsync

    public class PaginatedList<T>
    {
    	public List<T> Items { get; }
    	public int PageIndex { get; }
    	public int TotalPages { get; }
    	public int TotalCount { get; }
    
    	public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
    	{
    		PageIndex = pageIndex;
    		TotalPages = (int)Math.Ceiling(count / (double)pageSize);
    		TotalCount = count;
    		Items = items;
    	}
    
    	public bool HasPreviousPage => PageIndex > 1;
    
    	public bool HasNextPage => PageIndex < TotalPages;
    
    	public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
    	{
    		var count = await source.CountAsync();
    		var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
    
    		return new PaginatedList<T>(items, count, pageIndex, pageSize);
    	}
    }

    After finding different resources on the internet I have realized that I have been trying to call the GetUserEmailAsync unnecessary from the database which is not the correct approach in this situation. I should first fetch the record with the pagination and then GetUserEmailAsync, but I am stuck. 

    What is the proper way of using the await inside the lambda expressions? How do I approach my LINQ query?

    Sunday, February 7, 2021 5:03 PM

All replies

  • User1535942433 posted

    Hi aakashbashyal,

    According to your codes,I couldn't reproduce your problems.

    As far as I think,you are mixing Expression trees and delegates.

    When you pass lambda expression to a method accepting Expression<T>, you create an expression tree from the lambda. Expression trees are just code which describe code, but are not code themselves.

    That being said, an expression tree can't be executed because it's converted to executable code. You can compile a expression tree at runtime and then execute it like a delegate.

    More details,you could refer to below article:

    https://stackoverflow.com/questions/39518163/using-async-await-inside-select-lambda

    Best regards,

    Yijing Sun

    Monday, February 8, 2021 8:00 AM
  • User-1637592233 posted

    Hi!

    Thanks for pointing to the solution, but that did not help me to achieve what I want. And still, I couldn't solve the problem. Can you look help me to look into this issue? 

    I have also changed my code according to the suggestions. 

            public async Task<PaginatedList<BookRequestViewModel>> GetRequestedBookList(int pageNumber, int pageSize)
            {
    
                var bookReservationList = (await _dbContext.BookReservations.Include(x => x.Book).ToListAsync())
                   .Select(
                    book => new BookRequestViewModel
                    {
                        RequesterId = book.RequesterId,
                        Author = book.Book.Author
    
                    });
    
                var userViewModels = bookReservationList.Select(async y => new BookRequestViewModel
                {
                    Requester = await _identityService.GetUserEmailAsync(y.RequesterId)
    
                });
                var vms = await Task.WhenAll(userViewModels);
    
                var result = await vms.AsQueryable().PaginatedListAsync(pageNumber, pageSize);
    
                return result;
            }

    But got the error as:

    InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

    Monday, February 8, 2021 8:05 AM
  • User-474980206 posted

    this code:

      var userViewModels = bookReservationList.Select(async y => new BookRequestViewModel
                {
                    Requester = await _identityService.GetUserEmailAsync(y.RequesterId)
    
                });
                var vms = await Task.WhenAll(userViewModels);

    the .Select is returning an array of tasks (thus the WhenAll), each of which Is running at the same time. the await in the lambda performs no function as you have no code after the await. as you are using the same dbcontext in each task you get the error.

    you appear to think the await would await between tasks.

    you could write a .SelectAsync extension that awaited the lambda before looping or use an async line library like reactive.

    the simple fix is:

    var userViewModels = new List<BookRequestViewModel>();
    foreach (var y in bookReservationList)
    {
       Requester = await _identityService.GetUserEmailAsync(y.RequesterId) });
       userViewModels.Add(Requester);
    }
    

    Monday, February 8, 2021 4:31 PM