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

怎甘沉沦 提交于 2019-12-10 13:37:27

问题


How can I map

from: Expression<Func<TEntity, bool>> to: Expression<Func<TDbEntity, bool>>

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

TEntity is from Domain and TDbEntity is from Infrastructure layer, but have same properties.

It is possible?


回答1:


For relatively simple cases (and I guess in your case expressions are simple) you can use ExpressionVisitor with only a couple of overrides. For example:

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);
        }
    }
}

Usage is:

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



回答2:


As written by Evk you'll need an ExpressionVisitor.

For little more complex expressions than the ones supported by Evk, like multiple parameters, use of the constructor and of member initializer list (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());
        }
    }
}

And you use like this:

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);

Note that this code shows how much pain can be doing this conversion in the most complex cases... This code isn't complete... There are many corner cases not covered here! (for example this isn't supported: x.SomeMethod(new ClassOriginal()) -> x.SomeMethod(new ClassConverted().

Added support for:

x => x;
x => null;

(they needed special handling)




回答3:


I would extract those "same properties" to an interface, and let the classes implement it...




回答4:


I had haved this same problem some time ago, the solution I found was convert the primary entity to the target entity. But it works just if the properties have the same name, otherwise you will have an extra work to map the properties on filter.

So lets go to code, try do it:

  • First create a extension 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);
        }
    }
    

Now create de Visitor 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;
    }
 }

And to use: // add the using to use your extension method

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

I hope it can help you.



来源:https://stackoverflow.com/questions/42902164/how-to-map-expressionfunctentity-bool-to-expressionfunctdbentity-bool

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!