Skip to content

Instantly share code, notes, and snippets.

@imgen
Last active June 28, 2021 05:02
Show Gist options
  • Save imgen/3746a3b34b2bdd392ce10e4cf6879e35 to your computer and use it in GitHub Desktop.
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
// 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