18

(電子メールの会話に基づいており、現在は情報共有のために記録されています) 異なるレイヤーで使用される 2 つのモデルがあります。

public class TestDTO {
    public int CustomerID { get; set; }
}
//...
public class Test {
    public int CustomerID { get; set; }
}

そして、私のDTOレイヤーに関するラムダ:

Expression<Func<TestDTO, bool>> fc1 =
   (TestDTO c1) => c1.CustomerID <= 100 && c1.CustomerID >= 10;

そのラムダ(一般的な場合)を他のモデルについて話すように変換するにはどうすればよいですか:

Expression<Func<Test, bool>> fc2 = {insert magic here, based on fc1}

(明らかに、同じテスト条件を求めていますが、Test型を使用しています)

?

4

2 に答える 2

20

そのためには、式ツリーを完全に再構築する必要があります。パラメーターを再マッピングする必要があり、現在異なる型と通信しているすべてのメンバー アクセスを再適用する必要があります。ExpressionVisitor幸いなことに、これの多くはクラスによって簡単になります。たとえば(Func<T,bool>述語の使用法だけでなく、一般的なケースですべてを実行します):

class TypeConversionVisitor : ExpressionVisitor
{
    private readonly Dictionary<Expression, Expression> parameterMap;

    public TypeConversionVisitor(
        Dictionary<Expression, Expression> parameterMap)
    {
        this.parameterMap = parameterMap;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        // re-map the parameter
        Expression found;
        if(!parameterMap.TryGetValue(node, out found))
            found = base.VisitParameter(node);
        return found;
    }
    protected override Expression VisitMember(MemberExpression node)
    {
        // re-perform any member-binding
        var expr = Visit(node.Expression);
        if (expr.Type != node.Type)
        {
            MemberInfo newMember = expr.Type.GetMember(node.Member.Name)
                                       .Single();
            return Expression.MakeMemberAccess(expr, newMember);
        }
        return base.VisitMember(node);
    }
}

ここでは、再マップするパラメータのディクショナリを渡し、それを に適用しVisitParameterます。また、 ではVisitMember、型を切り替えたかどうかを確認します (これVisitは、 aParameterExpressionまたは anotherが関係する場合MemberExpressionにいつでも発生する可能性があります): 切り替えた場合は、同じ名前の別のメンバーを見つけようとします。

次に、汎用のラムダ変換リライター メソッドが必要です。

// allows extension to other signatures later...
private static Expression<TTo> ConvertImpl<TFrom, TTo>(Expression<TFrom> from)
    where TFrom : class
    where TTo : class
{
    // figure out which types are different in the function-signature
    var fromTypes = from.Type.GetGenericArguments();
    var toTypes = typeof(TTo).GetGenericArguments();
    if (fromTypes.Length != toTypes.Length)
        throw new NotSupportedException(
            "Incompatible lambda function-type signatures");
    Dictionary<Type, Type> typeMap = new Dictionary<Type,Type>();
    for (int i = 0; i < fromTypes.Length; i++)
    {
        if (fromTypes[i] != toTypes[i])
            typeMap[fromTypes[i]] = toTypes[i];
    }

    // re-map all parameters that involve different types
    Dictionary<Expression, Expression> parameterMap
        = new Dictionary<Expression, Expression>();
    ParameterExpression[] newParams =
        new ParameterExpression[from.Parameters.Count];
    for (int i = 0; i < newParams.Length; i++)
    {
        Type newType;
        if(typeMap.TryGetValue(from.Parameters[i].Type, out newType))
        {
            parameterMap[from.Parameters[i]] = newParams[i] =
                Expression.Parameter(newType, from.Parameters[i].Name);
        }
        else
        {
            newParams[i] = from.Parameters[i];
        }
    }

    // rebuild the lambda
    var body = new TypeConversionVisitor(parameterMap).Visit(from.Body);
    return Expression.Lambda<TTo>(body, newParams);
}

これは、任意Expression<TFrom>の と を取り、次のTTo方法で に変換しExpression<TTo>ます。

  • TFrom/の間でどのタイプが異なるかを見つけるTTo
  • それを使用してパラメーターを再マップします
  • 作成したばかりの式ビジターを使用して
  • 最後に、目的の署名の新しいラムダ式を構築します

次に、すべてをまとめて、拡張メソッドを公開します。

public static class Helpers {
    public static Expression<Func<TTo, bool>> Convert<TFrom, TTo>(
        this Expression<Func<TFrom, bool>> from)
    {
        return ConvertImpl<Func<TFrom, bool>, Func<TTo, bool>>(from);
    }

    // insert from above: ConvertImpl
    // insert from above: TypeConversionVisitor
}

出来上がり。以下の特定の実装を備えた汎用ラムダ変換ルーチン:

Expression<Func<Test, bool>> fc2 = fc1.Convert<TestDTO, Test>();
于 2012-12-22T22:30:44.333 に答える
5

AutoMapper(式ツリーなし)を使用できます。

Mapper.CreateMap<Test, TestDTO>();

...

Func<TestDTO, bool> fc1 =
  (TestDTO c1) => c1.CustomerID <= 100 && c1.CustomerID >= 10;

Func<Test, bool> fc2 =
  (Test t) => fc1(Mapper.Map<Test, TestDTO>(t));
于 2012-12-22T22:43:58.610 に答える