The parameters declared in compileQuery are ignored if not referenced directly in the expression body.
The exact same query works fine with EF core 2.x
``` C#
using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace TestConsole
{
class Program
{
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<TestEntity> Entities { get; set; }
}
public class TestEntity
{
public Guid Id { get; set; }
}
static void Main(string[] args)
{
var sp = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.AddDbContext<TestDbContext>(builder => builder
.UseInMemoryDatabase("test")
.EnableSensitiveDataLogging())
.BuildServiceProvider();
var predicate = CreatePredicate<TestEntity, Guid>("__id");
var okQuery = EF.CompileQuery((TestDbContext c, Guid id)
=> c.Entities
.Where(e => e.Id == id) // removing this results in an exception
.SingleOrDefault(predicate)
);
var failingQuery = EF.CompileQuery((TestDbContext c, Guid id)
=> c.Entities
.SingleOrDefault(predicate)
);
var context = sp.GetRequiredService<TestDbContext>();
var entity = new TestEntity() { Id = Guid.NewGuid() };
context.Add(entity);
context.SaveChanges();
// works fine
okQuery(context, entity.Id);
// fails: System.Collections.Generic.KeyNotFoundException: The given key '__id' was not present in the dictionary.
failingQuery(context, entity.Id);
}
private static Expression<Func<TEntity, bool>> CreatePredicate<TEntity, TKey>(
string grainKeyParamName = "__id")
{
// Creates expression (e => e.Id == id)
ParameterExpression stateParam = Expression.Parameter(typeof(TEntity), "state");
ParameterExpression grainKeyParam = Expression.Parameter(typeof(TKey), grainKeyParamName);
MemberExpression stateKeyParam = Expression.Property(stateParam, "Id");
BinaryExpression equals = Expression.Equal(grainKeyParam, stateKeyParam);
return Expression.Lambda<Func<TEntity, bool>>(equals, stateParam);
}
}
}
Unhandled exception. System.InvalidOperationException: An exception was thrown while attempting to evaluate the LINQ query parameter expression 'value(TestConsole.Program+TestDbContext).Entities.SingleOrDefault(value(TestConsole.Program+<>c__DisplayClass2_0).predicate)'.
---> System.Collections.Generic.KeyNotFoundException: The given key '__id' was not present in the dictionary.
at System.Collections.Generic.Dictionary2.get_Item(TKey key)
at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryExpressionTranslatingExpressionVisitor.GetParameterValue[T](QueryContext queryContext, String parameterName)
at lambda_method(Closure , ValueBuffer )
at System.Linq.Enumerable.WhereEnumerableIterator1.MoveNext()
at System.Linq.Enumerable.SingleOrDefaultTSource
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteTResult
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteTResult
at System.Linq.Queryable.SingleOrDefaultTSource
at lambda_method(Closure )
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.GetValue(Expression expression, String& parameterName)
--- End of inner exception stack trace ---
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.GetValue(Expression expression, String& parameterName)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Evaluate(Expression expression, Boolean generateParameter)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.Visit(Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.ParameterExtractingExpressionVisitor.ExtractParameters(Expression expression) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExtractParameters(Expression query, IParameterValues parameterValues, IDiagnosticsLogger1 logger, Boolean parameterize, Boolean generateContextAccessors)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CreateCompiledQuery[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQuery2.CreateCompiledQuery(IQueryCompiler queryCompiler, Expression expression)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase2.<>c__DisplayClass6_0.<EnsureExecutor>b__0(TContext c, LambdaExpression q)
at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam1,TParam2,TValue](TValue& target, TParam1 param1, TParam2 param2, Func3 valueFactory)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase2.EnsureExecutor(TContext context)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase2.ExecuteCore(TContext context, CancellationToken cancellationToken, Object[] parameters)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryBase2.ExecuteCore(TContext context, Object[] parameters)
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQuery2.ExecuteTParam1
at TestConsole.Program.Main(String[] args) in C:UsersAlirereposOrleans.Providers.EntityFrameworktoysTestConsoleProgram.cs:line 57
```
EF Core version: 3.0.0 / 3.1.0
Database provider: InMemory / Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET Core 3.0
Operating system: Windows 10
IDE: Visual Studio 2019 16.3
@smitpatel to take look.
Understood the full exception. Since we are trying to evaluate constant of Dbcontext (a bug!), when the where is missing, we try to evaluate whole thing which goes to implicit query caching. Where the parameter __id is not present.
This may just work by co-incidence but not the right way to create expression tree. Since predicate is being passed inside the lambda the expression tree contains closure member access rather than actual lambda expression there.
Correct way to write is to create lambda to be passed to compiledQuery method
```C#
var idParameter = Expression.Parameter(typeof(Guid), "id");
var predicate = CreatePredicate
Expression
var queryable = queryRootExpression.Body;
var compiledLambdaBody = Expression.Call(
typeof(Queryable).GetMethods().SingleOrDefault(mi => mi.Name == nameof(Queryable.SingleOrDefault) && mi.GetParameters().Count() == 2)
.MakeGenericMethod(typeof(TestEntity)),
queryable,
Expression.Quote(predicate));
var lambdaExpression = Expression.Lambda<Func<MyContext, Guid, TestEntity>>(compiledLambdaBody, queryRootExpression.Parameters[0], idParameter);
var failingQuery = EF.CompileQuery(lambdaExpression);
failingQuery(context, entity.Id); //Now this works
```
@smitpatel Right, I should've taken another look into the expression. Thanks a lot.
Most helpful comment
Understood the full exception. Since we are trying to evaluate constant of Dbcontext (a bug!), when the where is missing, we try to evaluate whole thing which goes to implicit query caching. Where the parameter
__idis not present.This may just work by co-incidence but not the right way to create expression tree. Since predicate is being passed inside the lambda the expression tree contains closure member access rather than actual lambda expression there.(idParameter);>> queryRootExpression = (ctx) => ctx.Entities.AsQueryable();
Correct way to write is to create lambda to be passed to compiledQuery method
```C#
var idParameter = Expression.Parameter(typeof(Guid), "id");
var predicate = CreatePredicate
Expression
var queryable = queryRootExpression.Body;
var compiledLambdaBody = Expression.Call(
typeof(Queryable).GetMethods().SingleOrDefault(mi => mi.Name == nameof(Queryable.SingleOrDefault) && mi.GetParameters().Count() == 2)
.MakeGenericMethod(typeof(TestEntity)),
queryable,
Expression.Quote(predicate));
```