Jon Egerton
Jon Egerton

Reputation: 41569

Many nested AggregateExceptions

Working with Entity Framework 7, I made a simple mistake with some linq (used Skip and forgot to include my OrderBy clause).

The exception that was thrown by this included a number of nested aggregate exceptions.

The code that generates (and catches) the exception is:

int[] newIds;
try
{
    newIds = await db.Products
        .Where(p => p.PortalId == portalId)
        .Skip(ids.ProductIds.Count) //Skip the rows already read
        .Take(takeTotal) //get the next block
        .Select(p => p.ProductId)
        .ToArrayAsync();
}
catch (AggregateException ex)
{
    Console.WriteLine(ex.Message);
    newIds = new int[] { };
}

The code above is in a repo class called from a Asp.Net 5 WebApi controller. All levels of the call are using async-await.

However the aggregate exception that I got from this was (this is dumped to the immediate window from the catch block shown above):

System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.InvalidOperationException: A query containing the Skip operator must include at least one OrderBy operation. at Microsoft.Data.Entity.Relational.Query.Sql.DefaultSqlQueryGenerator.GenerateLimitOffset(SelectExpression selectExpression) at Microsoft.Data.Entity.Relational.Query.Sql.DefaultSqlQueryGenerator.VisitSelectExpression(SelectExpression selectExpression) at Microsoft.Data.Entity.Relational.Query.Expressions.SelectExpression.Accept(ExpressionTreeVisitor visitor) at Microsoft.Data.Entity.Relational.Query.Sql.DefaultSqlQueryGenerator.GenerateSql(SelectExpression selectExpression, IDictionary`2 parameterValues) etc etc

Here the actual exception has ended up wrapped by a whole bunch of layers of aggregate exception (6 nested layers). I understand why I'm getting an aggregate exception, but wondered why so many of them? More so since I'm looking at the exception before it has bubbled back up to the controller entry-point.

Would this be a result of a number of layers of async-await, (don't think I have as many as 6) or could it be an issue in the EF7 implementation?

This is currently using EF 7 release 7.0.0-beta4.

Upvotes: 3

Views: 1824

Answers (2)

Felipe Pessoto
Felipe Pessoto

Reputation: 6969

It's not related to the quantity of method called in chain. You just need to call ToArrayAsync.

I think the problem is in Rx.NET. I sent a Pull Request to fix it: https://github.com/aspnet/EntityFramework/issues/2192

https://github.com/Reactive-Extensions/Rx.NET/pull/131/files

Upvotes: 0

anaximander
anaximander

Reputation: 7140

As the MSDN page on Task<T> explains, all exceptions thrown by a Task are wrapped in AggregateException before being thrown to the awaiting code. If you're using multiple levels of async/await and not catching this exception at the lowest possible level, then each time it bubbles up another level, it'll get wrapped again, leading to AggregateException inside AggregateException, one for every time you're awaiting without catching.

It might also be that each operation is counted as its own task; ie. each time you add another operation, the result comes up out of the previous one and back down into the next, with each one awaiting the previous. Take a look:

newIds = await db.Products               // 1
    .Where(p => p.PortalId == portalId)  // 2
    .Skip(ids.ProductIds.Count)          // 3
    .Take(takeTotal)                     // 4
    .Select(p => p.ProductId)            // 5
    .ToArrayAsync();                     // 6

Six layers of things, each awaiting the result from the previous. Six AggregateException layers. Now, your exception is caused by the third of the six, but from the nature of the error, it's likely that it comes from a part where EF reads your whole query before doing any of it, and while doing so it's spotted that you've got a .Skip() without a matching .OrderBy().

As Stephen Cleary reminded me in the comments, while things you await return Task<T>, they also do a degree of unwrapping for you, so await doesn't behave quite like Task<T>.Result, meaning that await should throw the actual exception without wrapping it in AggregateException. What all this means is that at best we only have half the answer here (which is a little awkward, seeing that it's been accepted already). Honestly, I'd suggest that you un-accept this answer so that others don't skip over your question, and see if anyone else knows something that might fill the gaps.

Upvotes: 4

Related Questions