dotnet/efcore

Error when using async queries involving result operators and projecting a collection (N+1 queries scenario)

Closed this issue · 7 comments

Steps to reproduce

I have method named GetSeriesData:

private IQueryable<SeriesData> GetSeriesData(IQueryable<Series> query, long? userId = null)
{
    DateTime date = DateTime.Today.AddMonths(1);

    IQueryable<SeriesData> seriesDataQuery = query.Select(x => new SeriesData
    {
        Series = x,
        Subscribed = userId.HasValue && x.Subscriptions.Any(y => y.UserId == userId),
        CurrentSeasonNumber = x.Episodes.Where(z => z.ReleaseDate.HasValue && z.ReleaseDate < date).Max(y => y.SeasonNumber),
        Channel = x.Channel,
        Country = x.Channel.Country,
        ReleaseGroups = x.Episodes.SelectMany(z => z.Releases)
                                    .Select(y => y.ReleaseGroup)
                                    .OrderBy(y => y.Name)
                                    .Distinct()
                                    .Select(r => new ReleaseGroupData
                                    {
                                        ReleaseGroup = r,
                                        Subscribed = userId.HasValue && x.Subscriptions.Any(y => y.UserId == userId && y.ReleaseGroupId == r.Id)
                                    }).ToList()
    });

    return seriesDataQuery;
}

I call GetSeriesData method:

public async Task<SeriesData> Get(IFindSeriesParameters parameters, long? userId)
{
    DbSet<Series> seriesRepository = _dataContext.Set<Series>();
    IQueryable<Series> seriesQuery;

    if (parameters.Id.HasValue)
    {
        seriesQuery = seriesRepository.Where(x => x.Id == parameters.Id);
    }
    else if (!string.IsNullOrEmpty(parameters.UniqueUrl))
    {
        seriesQuery = seriesRepository.Where(x => x.UniqueUrl == parameters.UniqueUrl);
    }
    else
    {
        seriesQuery = seriesRepository;
    }

    SeriesData seriesData = await GetSeriesData(seriesQuery, userId).SingleOrDefaultAsync();
    return seriesData;
}

The issue

Calling SingleOrDefaultAsync throws exception

Exception message:
System.ArgumentException: Incorrect number of arguments supplied for call to method 'System.Threading.Tasks.Task`1[System.Boolean] GetResult[Boolean](System.Collections.Generic.IAsyncEnumerable`1[Microsoft.EntityFrameworkCore.Storage.ValueBuffer], System.Threading.CancellationToken)'

Stack trace:
System.ArgumentException: Incorrect number of arguments supplied for call to method 'System.Threading.Tasks.Task`1[System.Boolean] GetResult[Boolean](System.Collections.Generic.IAsyncEnumerable`1[Microsoft.EntityFrameworkCore.Storage.ValueBuffer], System.Threading.CancellationToken)'
   at System.Dynamic.Utils.ExpressionUtils.ValidateArgumentCount(MethodBase method, ExpressionType nodeKind, Int32 count, ParameterInfo[] pis)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalResultOperatorHandler.HandleResultOperator(EntityQueryModelVisitor entityQueryModelVisitor, ResultOperatorBase resultOperator, QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, Int32 index)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, Int32 index)
   at Remotion.Linq.QueryModelVisitorBase.VisitResultOperators(ObservableCollection`1 resultOperators, QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalEntityQueryableExpressionVisitor.VisitSubQuery(SubQueryExpression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.ReplaceClauseReferences(Expression expression, IQuerySource querySource, Boolean inProjection)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitSelectClause(SelectClause selectClause, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateAsyncQueryExecutor[TResult](QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddAsyncQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.SingleOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at MySeries.Core.Services.SeriesService.<Get>d__8.MoveNext() in D:\Sources\myseries-2\MySeries.Core\Services\SeriesService.cs:line 174
—- End of stack trace from previous location where exception was thrown —-
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at MySeries.Web.Controllers.SeriesController.<Details>d__5.MoveNext() in D:\Sources\myseries-2\MySeries.Web\Controllers\SeriesController.cs:line 65
—- End of stack trace from previous location where exception was thrown —-
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__27.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__25.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextResourceFilter>d__22.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeAsync>d__20.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware`1.<Invoke>d__18.MoveNext()
—- End of stack trace from previous location where exception was thrown —-
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.<Invoke>d__7.MoveNext()

But if i call SingleOrDefault instead of SingleOrDefaultAsync all works fine
Also this code works with EF6

Per information in #7284, this is a regression introduced in 1.1. Should we move this to 1.1.1? @divega

Thanks @maumar. Moved.

Fixed in fba433e

@maumar Is the fix for this issue checked in the 1.1.1 branch? Should the issue be reopened for approval?

Eilon commented

This patch bug is approved. Please use the normal code review process w/ a PR and make sure the fix is in the correct branch, then close the bug and mark it as done.

Is there any indication when the 1.1.1 release will go public? Alternatively, is there a workaround for this?

Eilon commented

@RehanSaeed it's about 2 - 3 weeks out. We're working on setting up a MyGet feed so people can try out the patch fixes.