3

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.

cramopy
  • 3,459
  • 6
  • 28
  • 42
Jon Egerton
  • 40,401
  • 11
  • 97
  • 129

2 Answers2

4

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.

Community
  • 1
  • 1
anaximander
  • 7,083
  • 3
  • 44
  • 62
  • I've caught it at the lowest level I can - at the point it exits the EF code. Does that imply there are 6 levels of await inside EF then? – Jon Egerton May 14 '15 at 09:25
  • It's possible. It might also be that the various chained LINQ operators are all awaited individually... actually, I'm going to add that to my answer. – anaximander May 14 '15 at 09:28
  • I've done a further test using a console app and just executing the EF code, and the same result happens, so it looks like its the internal structure of the EF code that's doing that as we've arrived at. I'll raise it on the Github project - its not a bit deal, but a bit of a usability this. – Jon Egerton May 14 '15 at 09:41
  • 2
    It's worth noting that `AggregateException` overrides `GetBaseException()` specifically for cases like this: it will return the innermost `AggregateException`, no matter how deep the nesting. This lets you get the status and context info from that exception, and then check its `InnerException` for the error thrown - in this case, your `InvalidOperationException`. It's just a matter of remembering that async code has the potential to return this sort of exception-[matryoshka](http://en.wikipedia.org/wiki/Matryoshka_doll), and always using `GetBaseException()` to skip all the tedious unwrapping. – anaximander May 14 '15 at 09:50
  • `await` does *not* wrap its exceptions in `AggregateException`. So, layers of `async`/`await` will *not* add a wrapper at each level. Also, LINQ calls do not add these wrappers either. – Stephen Cleary May 14 '15 at 11:50
  • Indeed - it's not `await` that does the wrapping, it's getting the `Result` from a `Task` that does it. My thinking was that making the operations async might be implemented as having each one return `Task` - methods that can be `await`ed return `Task` - and perhaps that's what is causing all the wrapping. I did say it *might* be the cause; I don't know enough about how EF 7 async is implemented to say either way. I'm just working from the correlations I can see - `AggregateExceptions` being thrown where stuff is async, and the number of levels matching the number of chained calls. – anaximander May 14 '15 at 11:54
0

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

Felipe Pessoto
  • 6,855
  • 10
  • 42
  • 73