Efcore: Query: Exception when filter uses subquery and query is executed asynchronously

Created on 3 Jun 2016  路  2Comments  路  Source: dotnet/efcore

Steps to reproduce

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace TestEF
{
    public class Product
    {
        [Key]
        public Guid Id { get; set; }
        public List<Tag> Tags { get; set; }
    }

    public class Tag
    {
        [Key]
        public Guid Id { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<Tag> Tags { get; set; }

        public MyContext() { }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("Data Source=:memory:");
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            using (var db = new MyContext())
            {
                db.Database.OpenConnection();
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();

                var expectedTagsIds = new List<Guid> { Guid.NewGuid(), Guid.NewGuid() };

                var result = db.Products
                    .Where(product => expectedTagsIds
                        .All(expectedTagId => product.Tags.Select(tag => tag.Id)
                            .Any(tagId => tagId == expectedTagId)))
                    .ToListAsync()
                    .Result;
            }
        }
    }
}

The issue

The above code results in an exception. It only occurs if you do ToListAsync(), it doesn't occur if you do ToList().

Unhandled Exception: System.AggregateException: One or more errors occurred. (Expression of type 'System.Boolean' cannot be used for parameter of type 'System.Threading.Tasks.Task`1[System.Boolean]' of method 'Boolean Result[Boolean](System.Threading.Tasks.Task`1[System.Boolean])') ---> System.ArgumentException: Expression of type 'System.Boolean' cannot be used for parameter of type 'System.Threading.Tasks.Task`1[System.Boolean]' of method 'Boolean Result[Boolean](System.Threading.Tasks.Task`1[System.Boolean])'
   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0)
   at System.Linq.Expressions.MethodCallExpression1.Rewrite(Expression instance, IList`1 args)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskBlockingExpressionVisitor.Visit(Expression expression)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskBlockingExpressionVisitor.Visit(Expression expression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.TaskBlockingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalEntityQueryableExpressionVisitor.VisitSubQuery(SubQueryExpression expression)
   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.VisitWhereClause(WhereClause whereClause, QueryModel queryModel, Int32 index)
   at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection`1 bodyClauses, 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.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)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.System.Collections.Generic.IAsyncEnumerable<TResult>.GetEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.<ToListAsync>d__129`1.MoveNext()
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at TestEF.Program.Main(String[] args)

Further technical details

EF Core version: 1.0.0-rc2-final
Operating system: Windows 10
Visual Studio version: Visual Studio 2015

Other details about my project setup:
EFCore.Sqlite 1.0.0-rc2-final

Same problem occurs with AsAsyncEnemurable as well.

closed-fixed type-bug

All 2 comments

@Zoltu
The issue is with TaskBlockingExpressionVisitor, it never checks if rewrite was done before on the Task<T> resulting expression.
Here is a workaround (possibly fix?)

using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal
{
    public class TaskBlockingExpressionVisitor : ExpressionVisitorBase, ITaskBlockingExpressionVisitor
    {
        private bool _insideBlockingCall;

        public override Expression Visit(Expression expression)
        {
            if (expression != null && !_insideBlockingCall)
            {
                var typeInfo = expression.Type.GetTypeInfo();

                if (typeInfo.IsGenericType
                    && (typeInfo.GetGenericTypeDefinition() == typeof(Task<>)))
                {
                    return Expression.Call(
                        _resultMethodInfo.MakeGenericMethod(typeInfo.GenericTypeArguments[0]),
                        expression);
                }
            }

            return base.Visit(expression);
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.DeclaringType == typeof(TaskBlockingExpressionVisitor))
            {
                _insideBlockingCall = true;
                return base.VisitMethodCall(node);
            }
            _insideBlockingCall = false;
            return base.VisitMethodCall(node);
        }

        private static readonly MethodInfo _resultMethodInfo
            = typeof(TaskBlockingExpressionVisitor).GetTypeInfo()
                .GetDeclaredMethod(nameof(Result));

        [UsedImplicitly]
        private static T Result<T>(Task<T> task) => task.GetAwaiter().GetResult();
    }
}

cc @anpete

Was this page helpful?
0 / 5 - 0 ratings