How to re-wrap a Linq Expression Tree - c#

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

You just need to a fix a couple of things:
Return type of your method should be Expression<Func<T, bool>>.
The 1st parameter to Expression.Call() should simply be accessor.Body.
The ParameterExpression parameter to the Expression.Lambda<Func<T, bool>>() method call should simply be set from the accessor's parameter.
Method:
Expression<Func<T, bool>> CreateContains<T>(Expression<Func<T, string>> accessor)
{
var stringContains = typeof(String).GetMethod("Contains", new[] { typeof(String) });
return Expression.Lambda<Func<T, bool>>(
Expression.Call(
accessor.Body,
stringContains,
Expression.Constant("foo")
)
, accessor.Parameters[0]
);
}

Related

C# Expression String.IndexOf case insensitive with the property selector

I have the following class and extension method to invoke String.Contains method. How can I change it to be case insensitive ? Something like in Expression tree for String.IndexOf method but I don't have an ideas so far how to adjust that code into my code. Any help ?
public class testItem
{
public string SomeProperty { get; set; }
}
public static IQueryable<testItem> PropertyContainsNEW<testItem>(this IQueryable<testItem> source,
Expression<Func<testItem, string>> selector,
string value)
{
ParameterExpression parameter = Expression.Parameter(typeof(testItem), "x");
Expression property = Expression.Property(parameter, ((MemberExpression)selector.Body).Member.Name);
var search = Expression.Constant(value, typeof(string));
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(property, method, search);
var predicate = Expression.Lambda<Func<testItem, bool>>(containsMethodExp, parameter);
return source.Where(predicate);
}
In order to use the StringComparison parameter, you need to correctly identify that method.
Is this what you need?:
public static IQueryable<testItem> PropertyContainsNEW(this IQueryable<testItem> source,
Expression<Func<testItem, string>> selector,
string value)
{
var parameter = Expression.Parameter(typeof(testItem), "x");
var property = Expression.Property(parameter, ((MemberExpression)selector.Body).Member.Name);
var search = Expression.Constant(value, typeof(string));
var parms = new Expression[] { search,
Expression.Constant(StringComparison.OrdinalIgnoreCase) };
var method = typeof(string).GetMethod("Contains", new[] { typeof(string), typeof(StringComparison) });
var containsMethodExp = Expression.Call(property, method, parms);
var predicate = Expression.Lambda<Func<testItem, bool>>(containsMethodExp, parameter);
return source.Where(predicate);
}

Expression parameter is not defined

I am trying to perform a query against a list to give immediate results using an expression that is set elsewhere in the code, while a second thread goes off and uses it to get a full set of results from a database in a Linq query.
I know that the expression itself is OK as when I send it over the wire to the server side and apply it against an IQueryable then it will work. However, when applied on the client side the following error is produced:
System.InvalidOperationException: 'variable 'item' of type 'MissionControlSuite.Shared.Interface.Model.IBox' referenced from scope '', but it is not defined'
This code that performs the filtering:
public abstract class GridWithPaging<T> where T : class
{
List<T> _items
public Expression<Func<T, bool>> Filter { get; set; }
public ObservableCollection<IGridFilter<T>> Filters { get; set; }
// more code skipped for breveity
public void FiltersChanged(object sender, EventArgs e)
{
Expression<Func<T, bool>> combined = item => true;
foreach (var filter in Filters)
combined = Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(filter.Expression.Body, combined.Body),
combined.Parameters.Single());
Filter = combined;
NotifyPropertyChanged("Filter");
}
public void AddFilter(IGridFilter<T> filter)
{
Filters.Add(filter);
NotifyPropertyChanged("Filters");
}
private void DoFiltering(int start)
{
var newView = _items.Skip(start);
if(Filter != null)
newView = newView.AsQueryable().Where(Filter);
//some more code that acts on the filtered list
}
}
The expression is set elsewhere like this:
Expression<Func<IBox, bool>> expression = item => item.BoxId.Contains(v);
var filter = new GridFilter<IBox>()
{
Name = string.Format("{0} contains {1}", Property, Value),
Expression = expression
};
Grid.AddFilter(filter);
This answer will help you: Combining two expressions (Expression<Func<T, bool>>)
To summarize: The problem is that the parameters, although with the same name, are not the same instance of the ParameterExpression - which is included in the comment to that answer.
The solution is to use a Visitor (code copied over from linked post):
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
Also note: the ORM provider (or another IQueryable you use) probably works because it translates that to text directly, whereas executing (Invokeing) this LambdaExpression causes the error as it's more strict, in a way. Some IQueryables can also accept simple string parameters and parse that on their own, which should indicate that they have a wider range of acceptable inputs.

Build Lambda Expressions with Contains

I have a problem converting simple linq query to Lambda Expression.
My queries look like this:
int[] array = List<int> array2 = sql.OfType<Table1>().Select(x=>x.ID).Take(10).ToList();
var result = sql.OfType<Table1>().Where(x => array.Contains(x.ID)).Take(10).ToList();
and the final result should be:
static void DynamicSQLQuery<T>(IQueryable<T> sql, string fieldName)
{
List<int> array = sql.OfType<T>().Select(SelectExpression<T>(fieldName)).Take(10).ToList();
var result = sql.OfType<T>().Where(InExpression<T>(fieldName, array)).Take(10).ToList();
}
Class
public class Table1
{
public int ID { get; set; }
public string Name { get; set; }
}
I already converted the first lambda:
public static Expression<Func<T, int>> SelectExpression<T>(string fieldName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression selection = Expression.PropertyOrField(param, fieldName);
var lambdaExp = Expression.Lambda<Func<T, int>>(selection, param);
return lambdaExp;
}
But stuck on the second one:
static Expression<Func<T, bool>> InExpression<T>(string propertyName,IEnumerable<int> array)
{
System.Reflection.MethodInfo containsMethod = typeof(IEnumerable<int>).GetMethod("Contains");
ParameterExpression param = Expression.Parameter(typeof(T), "x");
MemberExpression member = Expression.PropertyOrField(param, propertyName);//x.{property}
var constant = Expression.Constant(3);
var body = Expression.GreaterThanOrEqual(member, constant); //x.{property} >= 3 but I need array.Contains(x.{property})
var finalExpression = Expression.Lambda<Func<T, bool>>(body, param);
return finalExpression;
}
Can anyone help me to make the lambda expression x=>array2.Contains(x.ID) in InExpression method?
Also, I'll be very grateful for some link to article/tutorial about creating these type of expressions.
Probably something like:
static Expression<Func<T, bool>> InExpression<T>(
string propertyName, IEnumerable<int> array)
{
var p = Expression.Parameter(typeof(T), "x");
var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Single(x => x.Name == "Contains" && x.GetParameters().Length == 2)
.MakeGenericMethod(typeof(int));
var property = Expression.PropertyOrField(p, propertyName);
var body = Expression.Call(contains, Expression.Constant(array), property);
return Expression.Lambda<Func<T, bool>>(body, p);
}
The trick here is to start off with something simple that compiles; for example:
using System.Linq;
using System;
using System.Linq.Expressions;
using System.Collections.Generic;
public class C {
static Expression<Func<Foo, bool>> InExpression<T>(
string propertyName,IEnumerable<int> array)
{
return x => array.Contains(x.Id);
}
}
class Foo {
public int Id {get;set;}
}
Now either compile it and look in ildasm/reflector, or (and much simpler): run that through https://sharplab.io specifying C# as the output, like this
This shows you the code that the compiler generated:
private static Expression<Func<Foo, bool>> InExpression<T>(string propertyName, IEnumerable<int> array)
{
C.<>c__DisplayClass0_0<T> <>c__DisplayClass0_ = new C.<>c__DisplayClass0_0<T>();
<>c__DisplayClass0_.array = array;
ParameterExpression parameterExpression = Expression.Parameter(typeof(Foo), "x");
Expression arg_77_0 = null;
MethodInfo arg_77_1 = methodof(IEnumerable<!!0>.Contains(!!0));
Expression[] expr_38 = new Expression[2];
expr_38[0] = Expression.Field(Expression.Constant(<>c__DisplayClass0_, typeof(C.<>c__DisplayClass0_0<T>)), fieldof(C.<>c__DisplayClass0_0<T>.array));
Expression[] expr_5F = expr_38;
expr_5F[1] = Expression.Property(parameterExpression, methodof(Foo.get_Id()));
Expression arg_86_0 = Expression.Call(arg_77_0, arg_77_1, expr_5F);
ParameterExpression[] expr_82 = new ParameterExpression[1];
expr_82[0] = parameterExpression;
return Expression.Lambda<Func<Foo, bool>>(arg_86_0, expr_82);
}
Note that there's a few things in here we need to fixup, but it allows us to see what it is doing - things like memberof and fieldof don't actually exist, for example, so we need to look them up via reflection.
A humanized version of the above:
private static Expression<Func<Foo, bool>> InExpression<T>(string propertyName, IEnumerable<int> array)
{
ExpressionState state = new ExpressionState();
state.array = array;
ParameterExpression parameterExpression = Expression.Parameter(typeof(Foo), "x");
MethodInfo contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
.Single(x => x.Name == nameof(Enumerable.Contains) && x.GetParameters().Length == 2)
.MakeGenericMethod(typeof(int));
Expression[] callArgs = new Expression[2];
callArgs[0] = Expression.Field(Expression.Constant(state, typeof(ExpressionState)), nameof(ExpressionState.array));
callArgs[1] = Expression.Property(parameterExpression, propertyName);
Expression body = Expression.Call(null, contains, callArgs);
ParameterExpression[] parameters = new ParameterExpression[1];
parameters[0] = parameterExpression;
return Expression.Lambda<Func<Foo, bool>>(body, parameters);
}
with:
class ExpressionState
{
public IEnumerable<int> array;
}

How to find a row where all search terms are contained within selected columns using Entity Framework

For context, this question is related to this question and this question. In this case, the user can specify an array of phrases. I'd like to expand upon the previous answer by asking how I can create a generic way to find entities where all the words of any of the phrases are contained within any of the specified columns.
To give you a better idea of what I'm talking about, if I were going to write this as a non-generic method, it would look something like this:
var searchPhrases = new [] {"John Smith", "Smith Bob"};
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
contacts.Where(c =>
searchTermSets.Any(searchTerms =>
searchTerms.All(searchTerm =>
c.FullName.Contains(searchTerm)
|| c.FirstName.Contains(searchTerm)
|| c.LastName.Contains(searchTerm))));
What I'm trying to do is make an extension method where I can do something like this:
contact.WhereIn(
searchPhrases,
c => c.FullName,
c => c.FirstName,
c => c.LastName);
And the extension method signature would look something like this:
IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
I tried following the same pattern from the previous questions I linked to, but I didn't get very far. That call to All() is tripping me up.
Expression like the predicate for
contacts.Where(c =>
searchTermSets.Any(searchTerms =>
searchTerms.All(searchTerm =>
c.FullName.Contains(searchTerm)
|| c.FirstName.Contains(searchTerm)
|| c.LastName.Contains(searchTerm))));
can be build dynamically with Expression.Call to Enumerable.Any and Enumerable.All.
First we'll need a simple parameter replacer so we can bind all the passed Expression<Func<T, string>> to a single parameter:
public static class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
Then the implementation could be like this:
public static class QueryableExtensions
{
public static IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
{
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
var c = Expression.Parameter(typeof(T), "c");
var searchTerms = Expression.Parameter(typeof(string[]), "searchTerms");
var searchTerm = Expression.Parameter(typeof(string), "searchTerm");
var allCondition = propertySelectors
.Select(propertySelector => (Expression)Expression.Call(
propertySelector.Body.ReplaceParameter(propertySelector.Parameters[0], c),
"Contains", Type.EmptyTypes, searchTerm))
.Aggregate(Expression.OrElse);
var allPredicate = Expression.Lambda<Func<string, bool>>(allCondition, searchTerm);
var allCall = Expression.Call(
typeof(Enumerable), "All", new[] { typeof(string) },
searchTerms, allPredicate);
var anyPredicate = Expression.Lambda<Func<string[], bool>>(allCall, searchTerms);
var anyCall = Expression.Call(
typeof(Enumerable), "Any", new[] { typeof(string[]) },
Expression.Constant(searchTermSets), anyPredicate);
var predicate = Expression.Lambda<Func<T, bool>>(anyCall, c);
return source.Where(predicate);
}
}
The problem is though that it doesn't work. If you try to run your non generic query, you'll get EntityCommandCompilationException with inner NotSupportedException saying
The nested query is not supported. Operation1='Case' Operation2='Collect'
The same will happen with the dynamically built query.
So what should we do? Well, taking into account that searchPhrases (thus searchTermSets and searchTerms) are known, we can treat them as constants, and all we need to get the desired result is to replace Any with Or expressions and All with And expressions.
The working solution looks like this (using the same parameter replacer):
public static class QueryableExtensions
{
public static IQueryable<T> WhereIn<T>(this IQueryable<T> source, IEnumerable<string> searchPhrases, params Expression<Func<T, string>>[] propertySelectors)
{
var searchTermSets = searchPhrases.Select(x => x.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
var c = Expression.Parameter(typeof(T), "c");
var body = searchTermSets
.Select(searchTerms => searchTerms
.Select(searchTerm => propertySelectors
.Select(propertySelector => (Expression)Expression.Call(
propertySelector.Body.ReplaceParameter(propertySelector.Parameters[0], c),
"Contains", Type.EmptyTypes, Expression.Constant(searchTerm)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso))
.Aggregate(Expression.OrElse);
var predicate = Expression.Lambda<Func<T, bool>>(body, c);
return source.Where(predicate);
}
}

LINQ Filter Implementation with Expressions

In MVC4 , I am providing Search box to the user to search any value in Table.
So I am implementing Generic Filter Condition at server side in C#
Need a help to combine multiple expressions to form single expression
Expression<Func<T, bool>>
For Example
Table Columns
MenuText, Role Name (Role.Name mapping), ActionName
Now If user entered in search box for ABC , which can be in any of the rows in shown columns, need to filter.
Model
public class Menu
{
public string MenuText {get;set;}
public Role Role {get;set;}
public string ActionName {get;set;}
}
public class Role
{
public string Name {get;set;}
}
So far I have implemented
/// <summary>
/// string[] properties property.Name (MenuText, ActionName), including deeper Mapping names such as (Role.Name)
/// </summary>
public static Expression<Func<T, bool>> FilterKey<T>(string filterText, params string[] properties)
{
ParameterExpression parameter = Expression.Parameter(typeof (T));
Expression[] propertyExpressions = properties.Select(
x => !string.IsNullOrEmpty(x) ? GetDeepPropertyExpression(parameter, x) : null).ToArray();
Expression<Func<T, bool>> predicate = PredicateBuilder.False<T>();
foreach (Expression expression in propertyExpressions)
{
var toLower = Expression.Call(expression, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
var like = Expression.Call(toLower, typeof(string).GetMethod("Contains"), Expression.Constant(filterText.ToLower()));
//TODO: Combine expressions to form single Expression<Func<T, bool>> expression
}
return predicate;
}
/// <summary>
/// To Get Deeper Properties such as Role.Name Expressions
/// </summary>
private static Expression GetDeepPropertyExpression(Expression initialInstance, string property)
{
Expression result = null;
foreach (string propertyName in property.Split('.'))
{
Expression instance = result ?? initialInstance;
result = Expression.Property(instance, propertyName);
}
return result;
}
I have created a few search IQueryable extension methods that you should be able to use
Full blog post is here:
http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable
GitHub project is here (has a couple of extra extensions for OR searches:
https://github.com/ninjanye/SearchExtensions
public static class QueryableExtensions
{
public static IQueryable<T> Search<T>(this IQueryable<T> source, Expression<Func<T, string>> stringProperty, string searchTerm)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
// The below represents the following lamda:
// source.Where(x => x.[property] != null
// && x.[property].Contains(searchTerm))
//Create expression to represent x.[property] != null
var isNotNullExpression = Expression.NotEqual(stringProperty.Body, Expression.Constant(null));
//Create expression to represent x.[property].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
//Join not null and contains expressions
var notNullAndContainsExpression = Expression.AndAlso(isNotNullExpression, checkContainsExpression);
var methodCallExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<T, bool>>(notNullAndContainsExpression, stringProperty.Parameters));
return source.Provider.CreateQuery<T>(methodCallExpression);
}
}
This allows you to write something like:
string searchTerm = "test";
var results = context.Menu.Search(menu => menu.MenuText, searchTerm).ToList();
//OR for Role name
string searchTerm = "test";
var results = context.Menu.Search(menu => menu.Role.Name, searchTerm).ToList();
You might also find the following posts useful:
Search extension method that allows search accros multiple properties:
http://jnye.co/Posts/7/generic-iqueryable-or-search-on-multiple-properties-using-expression-trees
Search extension method that allows multiple or search terms on a property:
http://jnye.co/Posts/8/generic-iqueryable-or-search-for-multiple-search-terms-using-expression-trees
Thanks to NinjaNye , I have borrowed BuildOrExpression which resolved my problem
Here is the solution
public static Expression<Func<T, bool>> FilterKey<T>(string filterText, params string[] properties)
{
ParameterExpression parameter = Expression.Parameter(typeof (T));
Expression[] propertyExpressions = properties.Select(
x => !string.IsNullOrEmpty(x) ? GetDeepPropertyExpression(parameter, x) : null).ToArray();
Expression like= propertyExpressions.Select(expression => Expression.Call(expression, typeof (string).GetMethod("ToLower", Type.EmptyTypes))).Select(toLower => Expression.Call(toLower, typeof (string).GetMethod("Contains"), Expression.Constant(filterText.ToLower()))).Aggregate<MethodCallExpression, Expression>(null, (current, ex) => BuildOrExpression(current, ex));
return Expression.Lambda<Func<T, bool>>(like, parameter);
}
private static Expression BuildOrExpression(Expression existingExpression, Expression expressionToAdd)
{
if (existingExpression == null)
{
return expressionToAdd;
}
//Build 'OR' expression for each property
return Expression.OrElse(existingExpression, expressionToAdd);
}
private static Expression GetDeepPropertyExpression(Expression initialInstance, string property)
{
Expression result = null;
foreach (string propertyName in property.Split('.'))
{
Expression instance = result ?? initialInstance;
result = Expression.Property(instance, propertyName);
}
return result;
}

Categories