@@ -247,7 +247,7 @@ private static IQueryable<TSource> CallGenericWhereMethod<TSource>(IQueryable<TS
247
247
PropertyInfo relationProperty = null ;
248
248
PropertyInfo property = null ;
249
249
MemberExpression left ;
250
- ConstantExpression right ;
250
+ Expression right ;
251
251
252
252
// {model}
253
253
var parameter = Expression . Parameter ( concreteType , "model" ) ;
@@ -287,8 +287,8 @@ private static IQueryable<TSource> CallGenericWhereMethod<TSource>(IQueryable<TS
287
287
// convert the incoming value to the target value type
288
288
// "1" -> 1
289
289
var convertedValue = TypeHelper . ConvertType ( filter . Value , property . PropertyType ) ;
290
- // {1}
291
- right = Expression . Constant ( convertedValue , property . PropertyType ) ;
290
+
291
+ right = CreateTupleAccessForConstantExpression ( convertedValue , property . PropertyType ) ;
292
292
}
293
293
294
294
var body = GetFilterExpressionLambda ( left , right , filter . Operation ) ;
@@ -302,6 +302,31 @@ private static IQueryable<TSource> CallGenericWhereMethod<TSource>(IQueryable<TS
302
302
}
303
303
}
304
304
305
+ private static Expression CreateTupleAccessForConstantExpression ( object value , Type type )
306
+ {
307
+ // To enable efficient query plan caching, inline constants (that vary per request) should be converted into query parameters.
308
+ // https://stackoverflow.com/questions/54075758/building-a-parameterized-entityframework-core-expression
309
+
310
+ // This method can be used to change a query like:
311
+ // SELECT ... FROM ... WHERE x."Age" = 3
312
+ // into:
313
+ // SELECT ... FROM ... WHERE x."Age" = @p0
314
+
315
+ // The code below builds the next expression for a type T that is unknown at compile time:
316
+ // Expression.Property(Expression.Constant(Tuple.Create<T>(value)), "Item1")
317
+ // Which represents the next C# code:
318
+ // Tuple.Create<T>(value).Item1;
319
+
320
+ MethodInfo tupleCreateMethod = typeof ( Tuple ) . GetMethods ( )
321
+ . Single ( m => m . Name == "Create" && m . IsGenericMethod && m . GetGenericArguments ( ) . Length == 1 ) ;
322
+ MethodInfo constructedTupleCreateMethod = tupleCreateMethod . MakeGenericMethod ( type ) ;
323
+
324
+ ConstantExpression constantExpression = Expression . Constant ( value , type ) ;
325
+
326
+ MethodCallExpression tupleCreateCall = Expression . Call ( constructedTupleCreateMethod , constantExpression ) ;
327
+ return Expression . Property ( tupleCreateCall , "Item1" ) ;
328
+ }
329
+
305
330
private static IQueryable < TSource > CallGenericSelectMethod < TSource > ( IQueryable < TSource > source , List < string > columns )
306
331
{
307
332
var sourceBindings = new List < MemberAssignment > ( ) ;
0 commit comments