问题
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