如何将 Expression<Func<TEntity, bool>> 映射到 Expression<Func<TDbEntity, bool>>

How to map Expression<Func<TEntity, bool>> to Expression<Func<TDbEntity, bool>>

如何映射

来自:Expression<Func<TEntity, bool>> 至:Expression<Func<TDbEntity, bool>>

where TEntity: class, new() and TDbEntity: class, new()

TEntity 来自域,TDbEntity 来自基础设施层,但具有相同的属性。

有可能吗?

对于相对简单的情况(我想在你的情况下表达式很简单)你可以使用 ExpressionVisitor 只有几个覆盖。例如:

public static class ExpressionExtensions {
    public static Expression<Func<TTo, bool>> ReplaceParameter<TFrom, TTo>(this Expression<Func<TFrom, bool>> target) {
        return (Expression<Func<TTo, bool>>) new WhereReplacerVisitor<TFrom, TTo>().Visit(target);
    }
    private class WhereReplacerVisitor<TFrom, TTo> : ExpressionVisitor {
        private readonly ParameterExpression _parameter = Expression.Parameter(typeof(TTo), "c");

        protected override Expression VisitLambda<T>(Expression<T> node) {
            // replace parameter here
            return Expression.Lambda(Visit(node.Body), _parameter);
        }            

        protected override Expression VisitMember(MemberExpression node) {
            // replace parameter member access with new type
            if (node.Member.DeclaringType == typeof(TFrom) && node.Expression is ParameterExpression) {
                return Expression.PropertyOrField(_parameter, node.Member.Name);
            }
            return base.VisitMember(node);
        }
    }
}

用法是:

Expression<Func<ErrorModel, bool>> where = (c) => c.Created <= DateTime.UtcNow && c.ErrorCode == "code";
var replaced = where.ReplaceParameter<ErrorModel, Error>();

我前段时间遇到过同样的问题,我找到的解决方案是将主要实体转换为目标实体。但只有当属性具有相同的名称时它才有效,否则你将有额外的工作来映射过滤器上的属性。

所以让我们开始编写代码,尝试这样做:

  • 首先创建一个扩展class

    public static class ExtensionToExpression
    {
        public static Expression<Func<TTo, bool>> Converter<TFrom, TTo>(this Expression<Func<TFrom, bool>> expression, TTo type) where TTo : TFrom
        {
             // here we get the expression parameter the x from (x) => ....
             var parameterName = expression.Parameters.First().Name;
             // create the new parameter from the correct type
             ParameterExpression parameter = Expression.Parameter(typeof(TTo), parameterName);
             // asigne to allow the visit from or visitor
             Expression body = new ConvertVisitor(parameter).Visit(expression.Body);
             // recreate the expression
             return Expression.Lambda<Func<TTo, bool>>(body, parameter);
        }
    }
    

现在创建访客 class

public class ConvertVisitor : ExpressionVisitor
{
    private ParameterExpression Parameter;

    public Visitante(ParameterExpression parameter)
    {
        Parameter = parameter;
    }

    protected override Expression VisitParameter(ParameterExpression item)
    {
        // we just check the parameter to return the new value for them
        if(!item.Name.Equals(Parameter.Name))
            return item;
        return Parameter;
    }
 }

并使用: // 添加 using 以使用您的扩展方法

public void YouMethod(Expression<Func<TEntity, bool>> filter)
{
     var whereExpression = filter.Convert(default(TDbEntity));
     var result = yourContext.Where(whereExpression).ToArray();
     // do something
}

希望对您有所帮助

正如 Evk 所写,您需要 ExpressionVisitor.

对于比 Evk 支持的表达式更复杂的表达式,例如多参数,使用构造函数和成员初始化列表 (new { Prop1 = true }):

public class TypeReplacer : ExpressionVisitor
{
    public readonly Dictionary<Type, Type> Conversions = new Dictionary<Type, Type>();
    private readonly Dictionary<Expression, Expression> ParameterConversions = new Dictionary<Expression, Expression>();

    protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
    {
        Type to;

        if (Conversions.TryGetValue(node.Member.DeclaringType, out to))
        {
            var member = ConvertMember(node.Member, to);
            node = Expression.Bind(member, node.Expression);
        }

        return base.VisitMemberAssignment(node);
    }

    public override Expression Visit(Expression node)
    {
        if (node.NodeType == ExpressionType.Lambda)
        {
            var lambda = (LambdaExpression)node;

            var parameters = lambda.Parameters.ToArray();

            for (int i = 0; i < parameters.Length; i++)
            {
                ParameterExpression parameter = parameters[i];

                Type to;

                if (Conversions.TryGetValue(parameter.Type, out to))
                {
                    var oldParameter = parameter;
                    parameter = Expression.Parameter(to, parameter.Name);
                    ParameterConversions.Add(oldParameter, parameter);
                }
            }

            var body = base.Visit(lambda.Body);

            for (int i = 0; i < parameters.Length; i++)
            {
                var parameter = (ParameterExpression)base.Visit(parameters[i]);
                parameters[i] = parameter;
            }

            // Handling of the delegate type
            var arguments = node.Type.GetGenericArguments();

            {
                Type to;

                for (int i = 0; i < arguments.Length; i++)
                {
                    if (Conversions.TryGetValue(arguments[i], out to))
                    {
                        arguments[i] = to;
                    }
                }
            }

            var delegateType = node.Type.GetGenericTypeDefinition().MakeGenericType(arguments);

            node = Expression.Lambda(delegateType, body, parameters);
            return node;
        }

        return base.Visit(node);
    }

    protected override Expression VisitConstant(ConstantExpression node)
    {
        Type to;

        if (Conversions.TryGetValue(node.Type, out to))
        {
            node = Expression.Constant(node.Value, to);
        }

        return base.VisitConstant(node);
    }

    protected override Expression VisitNew(NewExpression node)
    {
        Type to;

        if (Conversions.TryGetValue(node.Type, out to))
        {
            var constructor = node.Constructor;

            BindingFlags bf = (constructor.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
                BindingFlags.Instance;

            var parameters = constructor.GetParameters();
            var types = Array.ConvertAll(parameters, x => x.ParameterType);

            var constructor2 = to.GetConstructor(bf, null, types, null);

            if (node.Members != null)
            {
                // Shouldn't happen. node.Members != null with anonymous types
                IEnumerable<MemberInfo> members = node.Members.Select(x => ConvertMember(x, to));
                node = Expression.New(constructor2, node.Arguments, members);
            }
            else
            {
                node = Expression.New(constructor2, node.Arguments);
            }
        }

        return base.VisitNew(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        Type to = null;

        Expression expression = null;

        if (node.Expression != null)
        {
            if (ParameterConversions.TryGetValue(node.Expression, out expression))
            {
                to = expression.Type;
            }
        }

        if (to != null || (node.Expression == null && Conversions.TryGetValue(node.Member.DeclaringType, out to)))
        {
            MemberInfo member = ConvertMember(node.Member, to);

            node = Expression.MakeMemberAccess(expression, member);
        }

        return base.VisitMember(node);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        Expression to;

        if (ParameterConversions.TryGetValue(node, out to))
        {
            node = (ParameterExpression)to;
        }

        return base.VisitParameter(node);
    }

    // Conversion of method/property/field accessor (supported indexers)
    private static MemberInfo ConvertMember(MemberInfo member, Type to)
    {
        switch (member.MemberType)
        {
            case MemberTypes.Field:
                {
                    var field = (FieldInfo)member;

                    BindingFlags bf = (field.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
                        (field.IsStatic ? BindingFlags.Static : BindingFlags.Instance);

                    var field2 = to.GetField(member.Name, bf);

                    return field2;
                }

            case MemberTypes.Property:
                {
                    var prop = (PropertyInfo)member;

                    var method = prop.GetMethod ?? prop.SetMethod;

                    BindingFlags bf = (method.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
                        (method.IsStatic ? BindingFlags.Static : BindingFlags.Instance);

                    var indexes = prop.GetIndexParameters();

                    var types = Array.ConvertAll(indexes, x => x.ParameterType);

                    var property2 = to.GetProperty(member.Name, bf, null, prop.PropertyType, types, null);

                    return property2;
                }

            case MemberTypes.Method:
                {
                    var method = (MethodInfo)member;

                    BindingFlags bf = (method.IsPublic ? BindingFlags.Public : BindingFlags.NonPublic) |
                        (method.IsStatic ? BindingFlags.Static : BindingFlags.Instance);

                    var parameters = method.GetParameters();
                    var types = Array.ConvertAll(parameters, x => x.ParameterType);

                    var method2 = to.GetMethod(member.Name, bf, null, types, null);

                    return method2;
                }

            default:
                throw new NotSupportedException(member.MemberType.ToString());
        }
    }
}

然后你这样使用:

Expression<Func<Class1a, Class1b, bool>> exp1 = (x, y) => x.Prop1;

var visitor = new TypeReplacer();
visitor.Conversions.Add(typeof(Class1a), typeof(Class2a));
visitor.Conversions.Add(typeof(Class1b), typeof(Class2b));

var result = (Expression<Func<Class2a, Class2b, bool>>)visitor.Visit(exp1);

请注意,此代码显示了在最复杂的情​​况下进行此转换会带来多大的痛苦...此代码完整...有许多极端情况这里不涉及! (例如,这不受支持:x.SomeMethod(new ClassOriginal()) -> x.SomeMethod(new ClassConverted().

添加了对以下内容的支持:

x => x;
x => null;

(他们需要特殊处理)

我会将那些 "same properties" 提取到接口中,然后让 类 实现它...