How to convert Expression<Func<A, bool>>
to Expression<Func<B, bool>>
in C#?
The A
and B
can be really different classes (e.g. no common parent).
I would want to do something like: hi expression working on type A
. The property you use from type A
are e.g. SomeProperty
from type B
. So run the expression on type B
.
I have read that I could use ExpressionVisitor
class, but I really even don't imagine how could I use it because I don't know how it works.
This is example in code:
public async Task FindAsync(Expression<Func<A, bool>> where, CancellationToken cancellationToken = default)
{
// _dbContext.B.Where is here for B type, like .Where<B>()
// But the method parameter where is for type A
return _dbContext.B.Where(where).SingleOrDefaultAsync(cancellationToken);
}
So I want run expression of type A
on type B
, but don't know how to convert it.
The types could looks like this:
public class A
{
public string AProperty1 { get; set; }
public string AProperty2 { get; set; }
}
public class B
{
public string BProperty1 { get; set; }
public string BProperty2 { get; set; }
}
And this is example mapping between these 2 types:
var a = new A
{
AProperty1 = "x",
AProperty2 = "2"
};
var b = new B
{
BProperty1 = a.AProperty1,
BProperty2 = a.AProperty2
};
And it's result I would want to achieve:
Expression<Func<A, bool>> where = p => p.AProperty1 == "x" && p.AProperty2 == "2";
// Convert result will be as below:
Expression<Func<B, bool>> result = p => p.BProperty1 == "x" && p.BProperty2 == "2";
I hope I expressed my problem well. Please ask if you have any questions.
Given a Dictionary<string, string>
that maps all referenced properties from the original parameter type to the new parameter type:
var mapping = new Dictionary<string, string> {
{ nameof(A.AProperty1), nameof(B.BProperty1) },
{ nameof(A.AProperty2), nameof(B.BProperty2) }
};
You can use an ExpressionVisitor
subclass to replace the lambda parameter with a new parameter of the new type and all member expressions with new member expressions referencing the new parameter and the mapped replacing property:
var resExpr = whereExpr.ReplaceLambdaParameter<A, B>(mapping);
Here is the extension method and ExpressionVisitor
subclass:
public static class ExpressionExt {
public static Expression<Func<T2, bool>> ReplaceLambdaParameter<T1, T2>(this Expression<Func<T1, bool>> orig, Dictionary<string, string> pMap) =>
(Expression<Func<T2, bool>>)(new LambdaParameterReplacer<T1, T2>(pMap).Visit(orig));
}
public class LambdaParameterReplacer<T1, T2> : ExpressionVisitor {
Dictionary<string, string> ParameterMap;
ParameterExpression oldP, newP = Expression.Parameter(typeof(T2), "t2");
public LambdaParameterReplacer(Dictionary<string, string> pMap) => ParameterMap = pMap;
[return: NotNullIfNotNull("node")]
public override Expression Visit(Expression node) {
if (node is Expression<Func<T1, bool>> lambdaExpr)
oldP = lambdaExpr.Parameters[0]; // save original parameter to replace later
return base.Visit(node);
}
protected override Expression VisitLambda<T>(Expression<T> lambdaExpr)
=> Expression.Lambda(Visit(lambdaExpr.Body), newP);
protected override Expression VisitParameter(ParameterExpression parmExpr) {
if (parmExpr == oldP)
return newP;
else
return base.VisitParameter(parmExpr);
}
protected override Expression VisitMember(MemberExpression memberExpr) {
if (memberExpr.Expression == oldP)
return Expression.Property(newP, ParameterMap[memberExpr.Member.Name]);
else
return base.Visit(memberExpr);
}
}