diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs index 858532d7c2..d1113946ad 100644 --- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SelectClauseBuilder.cs @@ -93,9 +93,9 @@ private Expression CreateLambdaBodyInitializerForTypeHierarchy(FieldSelection se private static BinaryExpression CreateRuntimeTypeCheck(LambdaScope lambdaScope, Type concreteClrType) { // Emitting "resource.GetType() == typeof(Article)" instead of "resource is Article" so we don't need to check for most-derived - // types first. This way, we can fallback to "anything else" at the end without worrying about order. + // types first. This way, we can fall back to "anything else" at the end without worrying about order. - Expression concreteTypeConstant = concreteClrType.CreateTupleAccessExpressionForConstant(typeof(Type)); + Expression concreteTypeConstant = SystemExpressionBuilder.CloseOver(concreteClrType); MethodCallExpression getTypeCall = Expression.Call(lambdaScope.Accessor, TypeGetTypeMethod); return Expression.MakeBinary(ExpressionType.Equal, getTypeCall, concreteTypeConstant, false, TypeOpEqualityMethod); diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs index b48fa696db..4c54586cb6 100644 --- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/SkipTakeClauseBuilder.cs @@ -36,7 +36,7 @@ public override Expression VisitPagination(PaginationExpression expression, Quer private static Expression ExtensionMethodCall(Expression source, string operationName, int value, QueryClauseBuilderContext context) { - Expression constant = value.CreateTupleAccessExpressionForConstant(typeof(int)); + Expression constant = SystemExpressionBuilder.CloseOver(value); return Expression.Call(context.ExtensionType, operationName, [context.LambdaScope.Parameter.Type], source, constant); } diff --git a/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs index f14d795f7b..b5fafdd21b 100644 --- a/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/QueryableBuilding/WhereClauseBuilder.cs @@ -245,7 +245,6 @@ public override Expression VisitNullConstant(NullConstantExpression expression, public override Expression VisitLiteralConstant(LiteralConstantExpression expression, QueryClauseBuilderContext context) { - Type type = expression.TypedValue.GetType(); - return expression.TypedValue.CreateTupleAccessExpressionForConstant(type); + return SystemExpressionBuilder.CloseOver(expression.TypedValue); } } diff --git a/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs b/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs new file mode 100644 index 0000000000..f625ee9b4f --- /dev/null +++ b/src/JsonApiDotNetCore/Queries/SystemExpressionBuilder.cs @@ -0,0 +1,34 @@ +using System.Linq.Expressions; +using System.Reflection; + +#pragma warning disable AV1008 + +namespace JsonApiDotNetCore.Queries; + +internal static class SystemExpressionBuilder +{ + private static readonly MethodInfo CloseOverOpenMethod = + typeof(SystemExpressionBuilder).GetMethods().Single(method => method is { Name: nameof(CloseOver), IsGenericMethod: true }); + + // To enable efficient query plan caching, inline constants (that vary per request) should be converted into query parameters. + // https://stackoverflow.com/questions/54075758/building-a-parameterized-entityframework-core-expression + // + // CloseOver can be used to change a query like: + // SELECT ... FROM ... WHERE x."Age" = 3 + // into: + // SELECT ... FROM ... WHERE x."Age" = @p0 + + public static Expression CloseOver(object value) + { + ArgumentGuard.NotNull(value); + + MethodInfo closeOverClosedMethod = CloseOverOpenMethod.MakeGenericMethod(value.GetType()); + return (Expression)closeOverClosedMethod.Invoke(null, [value])!; + } + + public static Expression CloseOver(T value) + { + // From https://github.com/dotnet/efcore/issues/28151#issuecomment-1374480257. + return ((Expression>)(() => value)).Body; + } +} diff --git a/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs b/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs deleted file mode 100644 index ef81aece33..0000000000 --- a/src/JsonApiDotNetCore/Queries/SystemExpressionExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace JsonApiDotNetCore.Queries; - -internal static class SystemExpressionExtensions -{ - public static Expression CreateTupleAccessExpressionForConstant(this object? value, Type type) - { - // To enable efficient query plan caching, inline constants (that vary per request) should be converted into query parameters. - // https://stackoverflow.com/questions/54075758/building-a-parameterized-entityframework-core-expression - - // This method can be used to change a query like: - // SELECT ... FROM ... WHERE x."Age" = 3 - // into: - // SELECT ... FROM ... WHERE x."Age" = @p0 - - // The code below builds the next expression for a type T that is unknown at compile time: - // Expression.Property(Expression.Constant(Tuple.Create(value)), "Item1") - // Which represents the next C# code: - // Tuple.Create(value).Item1; - - MethodInfo tupleCreateUnboundMethod = typeof(Tuple).GetMethods() - .Single(method => method is { Name: "Create", IsGenericMethod: true } && method.GetGenericArguments().Length == 1); - - MethodInfo tupleCreateClosedMethod = tupleCreateUnboundMethod.MakeGenericMethod(type); - - ConstantExpression constantExpression = Expression.Constant(value, type); - - MethodCallExpression tupleCreateCall = Expression.Call(tupleCreateClosedMethod, constantExpression); - return Expression.Property(tupleCreateCall, "Item1"); - } -} diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 9d282b4938..f8af9dfa2f 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -102,7 +102,7 @@ public NewExpression CreateNewExpression(Type resourceClrType) { object constructorArgument = ActivatorUtilities.GetServiceOrCreateInstance(_serviceProvider, constructorParameter.ParameterType); - Expression argumentExpression = constructorArgument.CreateTupleAccessExpressionForConstant(constructorArgument.GetType()); + Expression argumentExpression = SystemExpressionBuilder.CloseOver(constructorArgument); constructorArguments.Add(argumentExpression); } #pragma warning disable AV1210 // Catch a specific exception instead of Exception, SystemException or ApplicationException pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy