ArgumentException with Complex Delegation of Compiled Query
See original GitHub issueFile a bug
I am kicking the tires on Compiled Queries, and I appear to have run into a potential bug. Reporting it here for your review.
In essence, I am delegating the resolution of the IQueryable<T>
used for the compiled query expression to other components. This works, but there does appear to be a limit to how far this can go currently.
When this condition happens, the wrong type appears to be evaluated, leading to the exception.
I have actually seen this type of exception before in my usage with EfCore, but the scenarios were far too complex to report at the time. Luckily I have been able to capture it here in a much more simpler format to investigate.
Include your code
I’ve created a sample project with a passing test and a failing test here: https://github.com/Mike-E-angelo/Stash/tree/master/EfCore.CompiledQuery.ArgumentException
Include stack traces
System.ArgumentException
Expression of type 'Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryShapedQueryCompilingExpressionVisitor+QueryingEnumerable`1[EfCore.CompiledQuery.ArgumentException.Subject]' cannot be used for parameter of type 'System.Linq.IQueryable`1[EfCore.CompiledQuery.ArgumentException.Subject]' (Parameter 'arg2')
at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
at System.Linq.Expressions.Expression.Invoke(Expression expression, Expression arg0, Expression arg1, Expression arg2)
at System.Linq.Expressions.InvocationExpression3.Rewrite(Expression lambda, Expression[] arguments)
at System.Linq.Expressions.ExpressionVisitor.VisitInvocation(InvocationExpression node)
at System.Linq.Expressions.InvocationExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CreateCompiledAsyncQuery[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledAsyncEnumerableQuery`2.CreateCompiledQuery(IQueryCompiler queryCompiler, Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase`2.<>c.<EnsureExecutor>b__6_0(CompiledQueryBase`2 t, TContext c, LambdaExpression q)
at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam1,TParam2,TParam3,TValue](TValue& target, TParam1 param1, TParam2 param2, TParam3 param3, Func`4 valueFactory)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase`2.EnsureExecutor(TContext context)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase`2.ExecuteCore(TContext context, CancellationToken cancellationToken, Object[] parameters)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase`2.ExecuteCore(TContext context, Object[] parameters)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledAsyncEnumerableQuery`2.Execute[TParam1](TContext context, TParam1 param1)
at EfCore.CompiledQuery.ArgumentException.Form`2.Get(In`1 parameter) in ...\EfCore.CompiledQuery.ArgumentException\EfCore.CompiledQuery.ArgumentException\Class1.cs:line 249
at EfCore.CompiledQuery.ArgumentException.Invoke`3.Get(TIn parameter) in ...\EfCore.CompiledQuery.ArgumentException\EfCore.CompiledQuery.ArgumentException\Class1.cs:line 231
at EfCore.CompiledQuery.ArgumentException.Evaluate`3.Get(TIn parameter)
at EfCore.CompiledQuery.ArgumentException.CompiledQueryTests.DoesNotWork() in ...\EfCore.CompiledQuery.ArgumentException\EfCore.CompiledQuery.ArgumentException\CompiledQueryTests.cs:line 46
at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 264
--- End of stack trace from previous location ---
at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:line 48
at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 90
Include verbose output
NA
Include provider and version information
EF Core version: 6.0.0-rc.1.21416.1
Database provider: Microsoft.EntityFrameworkCore.InMemory
Target framework: net6.0
Operating system: Windows 10
IDE: Visual Studio 2022 Preview 3.1
Issue Analytics
- State:
- Created 2 years ago
- Comments:17 (7 by maintainers)
EF Core has certain visitor for certain patterns to remove invocation expression. See https://github.com/dotnet/efcore/blob/release/6.0/src/EFCore/Query/Internal/InvocationExpressionRemovingExpressionVisitor.cs
The invocation pattern is only useful when a custom selector needs to be applied on a reference navigation which is not composable in LINQ any longer. For other patterns when a simpler and better expression tree can be generated using invocation expression is unsupported scenario. The specific reason you are running into type mismatch for parameter “arg2” is that the method is being evaluated on client side. I hope this explains why some of the queries worked for you. The way expression trees are being created above is not same as tree created by compiler from a LINQ query, it is not the way trees are created dynamically (there are many libraries which integrate on top of EF and generate queries dynamically, eg. OData/AutoMapper). We don’t intend to spend time in supporting the pattern represented above since we consider it bad design.
As suggested above, if you really want to construct dynamic trees then more understanding in how to create expression tree will be useful. The current design of passing around func is not supported.
In ideal world, yes it should throw compile exception but in real world things are bit different. When you write a complex expression tree and pass it to EF.CompiledQuery the delegate returned from it would take correct type, so this doesn’t cause compilation error. But in the complex expression tree written if you have a queryable subquery which would terminate (for whatever reason like applying a terminating result operator or ToList or passing it as argument to a function which EF doesn’t understand), EF will convert the queryable to IAsyncEnumerable after query translation. Now the tree you had earlier which was compile fine is incorrect since you don’t have queryable anymore. If you are using such method inside the expression passed to CompiledQuery the method needs to take type which can accommodate this change. Since queryable derives from IEnumerable, you need to make sure that parts of query which will get converted from queryable to EF Core’s enumerable are used as enumerable inside expression.