I have an Expression<Func<Entity, string>>
that can be either a property or a nested property accessor
y => y.SearchColumn
or
y => y.SubItem.SubColumn
I am building up an expression tree dynamically and would like to get an InvokeExpression like this
x => x.SearchColumn.Contains("foo");
x => x.Sub.SearchColumn.Contains("foo");
I'm pretty sure I can unwrap the Expression body, then partially apply the x ParameterExpression
to it but I can't figure out the magical incantations to make this happen
So I have something like
Expression<Func<Entity, string>> createContains(Expression<Func<Entity, string>> accessor) {
var stringContains = typeof(String).GetMethod("Contains", new [] { typeof(String) });
var pe = Expression.Parameter(typeof(T), "__x4326");
return Expression.Lambda<Func<Entity, bool>>(
Expression.Call(
curryExpression(accessor.Body, pe),
stringContains,
Expression.Constant("foo")
)
, pe
);
}
static Expression curryExpression(Expression from, ParameterExpression parameter) {
// this doesn't handle the sub-property scenario
return Expression.Property(parameter, ((MemberExpression) from).Member.Name);
//I thought this would work but it does not
//return Expression.Lambda<Func<Entity,string>>(from, parameter).Body;
}
Edit: Here is the full thing I'm trying to do along with the error
You need to do two things - first one you can use the same accessor.Body
, but it will reference to incorrect Parameter, as you created a new one. Second one you need to write custom ExpressionVisitor
that will replace all usage of original y
ParameterExpression
to a new created, so result expression will be compiled fine.
If you don't need to create new ParameterExpression
, then you can just use the same accessor.Body
and resent original ParameterExpression
to a new tree.
So here is my test working copy with a new ParameterExpression
- https://dotnetfiddle.net/uuPVAl
And the code itself
public class Program
{
public static void Main(string[] args)
{
Expression<Func<Entity, string>> parent = (y) => y.SearchColumn;
Expression<Func<Entity, string>> sub = (y) => y.Sub.SearchColumn;
var result = Wrap(parent);
Console.WriteLine(result);
result.Compile();
result = Wrap(sub);
Console.WriteLine(result);
result.Compile();
result = Wrap<Entity>((y) => y.Sub.Sub.Sub.SearchColumn);
Console.WriteLine(result);
result.Compile();
}
private static Expression<Func<T, bool>> Wrap<T>(Expression<Func<T, string>> accessor)
{
var stringContains = typeof (String).GetMethod("Contains", new[] {typeof (String)});
var pe = Expression.Parameter(typeof (T), "__x4326");
var newBody = new ParameterReplacer(pe).Visit(accessor.Body);
var call = Expression.Call(
newBody,
stringContains,
Expression.Constant("foo")
);
return Expression.Lambda<Func<T, bool>>(call, pe);
}
}
public class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression _target;
public ParameterReplacer(ParameterExpression target)
{
_target = target;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// here we are replacing original to a new one
return _target;
}
}
public class Entity
{
public string SearchColumn { get; set; }
public Entity Sub { get; set; }
}
PS: this example will work only if you have only one ParameterExpression
in original query, otherwise visitor should differentiate them
UPDATE
Here is my working answer with your full example in update - https://dotnetfiddle.net/MXP7wE
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With