Last active
June 28, 2021 05:02
-
-
Save imgen/3746a3b34b2bdd392ce10e4cf6879e35 to your computer and use it in GitHub Desktop.
Get ride of `Compile` method in the expression trees, can be used to avoid Client Side Evaluations (EF Core 2.x) or InvalidOperation exception (EF Core 3+) when used with Entity Framework Core
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// From below CodeProject article | |
// https://www.codeproject.com/Articles/894936/Manipulate-your-expression-trees-with-elegance | |
using System; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
namespace ManipulateExpressionTrees | |
{ | |
public static class ReflectionHelpers | |
{ | |
public static Expression<TDelegate> GetRidOfCompile<TDelegate>(this Expression<TDelegate> expression) | |
{ | |
var visitor = new CompileMethodVisitor(); | |
return Expression.Lambda<TDelegate>( | |
visitor.Visit(expression.Body), | |
expression.Parameters); | |
} | |
public static Expression<TDelegate> GetRidOfInterfaceCast<TDelegate>(this Expression<TDelegate> expression) | |
{ | |
var visitor = new InterfaceCastVisitor(); | |
return Expression.Lambda<TDelegate>( | |
visitor.Visit(expression.Body), | |
expression.Parameters); | |
} | |
private class CompileMethodVisitor : ExpressionVisitor | |
{ | |
protected override Expression VisitInvocation(InvocationExpression node) | |
{ | |
if (node.NodeType != ExpressionType.Invoke) | |
return base.VisitInvocation(node); | |
var source = node.Expression as MethodCallExpression; | |
if (source == null) | |
return base.VisitInvocation(node); | |
var methodInfo = source.Method; | |
if (methodInfo.DeclaringType == null || | |
!methodInfo.DeclaringType.IsGenericType) | |
return base.VisitInvocation(node); | |
var methodDeclaringType = methodInfo.DeclaringType.GetGenericTypeDefinition(); | |
if (methodDeclaringType != typeof(Expression<>)) | |
return base.VisitInvocation(node); | |
var expr = (LambdaExpression)Evaluate(source.Object); | |
var body = expr.Body; | |
for (int i = 0; i < expr.Parameters.Count; i++) | |
{ | |
var visitor = new ParameterUpdateVisitor(expr.Parameters[i], node.Arguments[i]); | |
body = visitor.Visit(body); | |
} | |
return body; | |
} | |
private object Evaluate(Expression exp) | |
{ | |
return Expression.Lambda(exp).Compile().DynamicInvoke(); | |
} | |
} | |
private class InterfaceCastVisitor : ExpressionVisitor | |
{ | |
protected override Expression VisitMember(MemberExpression node) | |
{ | |
var interfaceProperty = node.Member; | |
if (interfaceProperty.MemberType != MemberTypes.Property) | |
return base.VisitMember(node); | |
var interfaceType = interfaceProperty.DeclaringType; | |
if (interfaceType == null || !interfaceType.IsInterface) | |
return base.VisitMember(node); | |
var interfaceCast = node.Expression as UnaryExpression; | |
if (interfaceCast == null || | |
interfaceCast.NodeType != ExpressionType.Convert) | |
return base.VisitMember(node); | |
var instanceType = interfaceCast.Operand.Type; | |
var instanceProperty = GetImplementedProperty(instanceType, interfaceProperty); | |
return Expression.MakeMemberAccess( | |
base.Visit(interfaceCast.Operand), | |
instanceProperty); | |
} | |
private PropertyInfo GetImplementedProperty(Type classType, MemberInfo interfaceProperty) | |
{ | |
return classType.GetProperty(interfaceProperty.Name); | |
} | |
} | |
/// <summary> | |
/// updates the parameter in the expression | |
/// </summary> | |
private class ParameterUpdateVisitor : ExpressionVisitor | |
{ | |
private readonly ParameterExpression oldParameter; | |
private readonly Expression newParameter; | |
public ParameterUpdateVisitor(ParameterExpression oldParameter, Expression newParameter) | |
{ | |
this.oldParameter = oldParameter; | |
this.newParameter = newParameter; | |
} | |
protected override Expression VisitParameter(ParameterExpression node) | |
{ | |
if (object.ReferenceEquals(node, oldParameter)) | |
return newParameter; | |
return base.VisitParameter(node); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment