What could cause async lambda to return unwanted data about the operation in Ok IActionResult?

Viewed 74

I tried to use await in my lambda function to have an operation done asynchronously, to simplify the question I left out unnecessary operations (where clauses etc) and I named the async method Services.CustomerAmountService.QueryDbAndReturnResult

The function takes a double (sum of amount) and does numerous tasks, but most importantly an async call (firstAsync, which makes the function awaitable)

[HttpGet]
public async Task<IActionResult> GetCustomersAndAmount()
{
    try
    {
        return Ok((await _orderRepository.CustomMadeQueryThatReturnsIQueryable()
            .Select(x => new
            {
                CustomerId = x.CustomerId,
                CustomerName = x.Customer.Name
                Amount = x.Amount
            })
            .ToListAsync())
            .GroupBy(x => x.CustomerId )
            // this "async x" causes the unwanted result in my Ok() IActionResult
            .Select( async x => new
            {
                CustomerName = x.First().CustomerName,
                SumAmount = await Services.CustomerAmountService.QueryDbAndReturnResult(x.Sum(x => x.Amount)) 
            })
        ));
    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }
}

The result looks like this:

{"1":[{"result":{"customerName":"VeryNiceCustomer","amount":1.0},"id":1269,"exception":null,"status":5,"isCanceled":false,"isCompleted":true,"isCompletedSuccessfully":true,"creationOptions":0,"asyncState":null,"isFaulted":false}]}

But the desired result and the result without "async x" and an async function (so just returning '1' and doing no further operations at all) looks like this:

{"1":[{"customerName":"VeryNiceCustomer","amount":1}]}

Im wondering what I can change to always get the second result, and not worry about returning unnecessary data about the operation ? It seems to be returing a Result, but I'm only interested in the Result of the Result (what the function returns, not the async Result)

2 Answers

async lambda is transformed into Func<T, Task<T>> so your return is actually of type IEnumerable<Task<GeneratedAnonymousType>>. You can use Task.WhenAll to transform the results:

var resultTasks = (await _orderRepository.CustomMadeQueryThatReturnsIQueryable()
    .Select(x => new
    {
        CustomerId = x.CustomerId,
        CustomerName = x.Customer.Name
        Amount = x.Amount
    })
    .ToListAsync())
    .GroupBy(x => x.CustomerId )
    .Select( async x => new
    {
        CustomerName = x.First().CustomerName,
        SumAmount = await Services.CustomerAmountService.QueryDbAndReturnResult(x.Sum(x => x.Amount)) 
    }));

return Ok(await Task.WhenAll(resultTasks));

But in general I would recommend to revise tha whole approach - usually having parallel processing in action is a smell. Writing a method wich will fetch all needed data in one query should be better.

You cannot use async in LINQ methods. You have to enumerate results by foreach and make await calls. Schematically:

foreach (var x in items)
   SumAmount = await Services.CustomerAmountService.QueryDbAndReturnResult(x.Sum(x => x.Amount))

But, I think, your query just not optimal:

var query = _orderRepository.CustomMadeQueryThatReturnsIQueryable()
   .GroupBy(x => new
    {
        CustomerId = x.CustomerId,
        CustomerName = x.Customer.Name
    })
   .Select(g => new 
    {
         CustomerName = g.Key.CustomerName,
         SumAmount = g.Sum(x => x.Amount)
    });

return return Ok(await query.ToListAsync());
Related