Ravendb Session.Query<T>() method does not return any result when sending Expression<Func<T, bool>> to Where() Extension method - linq

I've got a problem with Ravendb session.Query.Where().
When I use it like this it works perfectly and returns all of UserIdentitiy with User name 'John' :
users = session.Query<UserIdentity>().Where(x => x.UserName == "John").ToList();
But when I try to send the Expression as a variable to Where method it fails silently and returns nothing.
Expression<Func<UserIdentity, bool>> whereClause = x => x.UserName == "John";
using (var session = _store.OpenSession())
{
users = session.Query<UserIdentity>().Where(whereClause).ToList();
}
What's wrong?

Try the following code:
public static class ExpressionTreesExtension
{
public static Expression<Func<T, bool>> EqualExpression<T>(string columnName,object value)
{
ParameterExpression parameterType = Expression.Parameter(typeof(T), "object");
MemberExpression typeColumn = Expression.Property(parameterType, columnName);
ConstantExpression constant = Expression.Constant(value, typeof(string));
BinaryExpression binaryExpression = Expression.Equal(typeColumn, constant);
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameterType);
}
}
Code Usage:
Expression<Func<UserIdentity, bool>> whereClause = ExpressionTreesExtension.EqualExpression<UserIdentity>("UserName","John");

Related

EF Core, append to predicate builder in Any condition

I'm trying to merge two predicates in then Any clause of the following code, but i cannot find a way to do this.
private static Expression<Func<Order, bool>> BuildWhereExpression(DataFilterOrder filter, AppDbContext dbContext)
{
var predicate = PredicateBuilder.True<Order>();
var confirmationPredicate = PredicateBuilder.True<HConfirmation>();
if (!string.IsNullOrWhiteSpace(filter.ConfirmationNumber))
{
confirmationPredicate = confirmationPredicate.And(r =>
r.Confirmation.Document.Number == filter.ConfirmationNumber);
}
if (filter.ConfirmationDateFrom != null)
{
confirmationPredicate = confirmationPredicate.And(r =>
r.Confirmation.Document.Date >= filter.ConfirmationDateFrom);
}
.....
predicate = predicate.And(o =>
dbContext.Confirmations
.Join(
dbContext.DocumentHierarchies,
c => c.DocumentId,
h => h.ChildDocumentId,
(c, h) => new HConfirmation { Confirmation = c, Hierarchy = h })
.Any(r => r.Hierarchy.ParentDocumentId == o.DocumentId &&
???confirmationPredicate???)
return predicate;
}
....
// called by
var wherePredicate = BuildWhereExpression(filter, dbContext);
var list = await dbContext.Orders
.Where(wherePredicate)
.ToListAsync();
Any help? Thanks very much.
PredicateBuilder class:
public static class PredicateBuilder
{
// Creates a predicate that evaluates to true.
public static Expression<Func<T, bool>> True<T>() { return param => true; }
// Creates a predicate that evaluates to false.
public static Expression<Func<T, bool>> False<T>() { return param => false; }
// Creates a predicate expression from the specified lambda expression.
public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
public static Expression<Func<T, bool>> Create1<T, K>(Expression<Func<T, bool>> predicate, K obj) { return predicate; }
// Combines the first predicate with the second using the logical "and".
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.AndAlso);
}
// Combines the first predicate with the second using the logical "or".
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
{
return first.Compose(second, Expression.OrElse);
}
// Negates the predicate.
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
{
var negated = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
}
// Combines the first expression with the second using the specified merge function.
static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
{
// zip parameters (map from parameters of second to parameters of first)
var map = first.Parameters
.Select((f, i) => new { f, s = second.Parameters[i] })
.ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with the parameters in the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// create a merged lambda expression with parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
class ParameterRebinder : ExpressionVisitor
{
readonly Dictionary<ParameterExpression, ParameterExpression> map;
ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
{
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
{
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p)
{
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement))
{
p = replacement;
}
return base.VisitParameter(p);
}
}
}
Well, tried to simplify solution, but looks like it is needed to build Any part dynamically. Sorry not tested and some small mistakes can be here.
private static Expression<Func<Order, bool>> BuildWhereExpression(DataFilterOrder filter, AppDbContext dbContext)
{
var predicate = PredicateBuilder.True<Order>();
var confirmationPredicate = PredicateBuilder.True<HConfirmation>();
if (!string.IsNullOrWhiteSpace(filter.ConfirmationNumber))
{
confirmationPredicate = confirmationPredicate.And(r =>
r.Confirmation.Document.Number == filter.ConfirmationNumber);
}
if (filter.ConfirmationDateFrom != null)
{
confirmationPredicate = confirmationPredicate.And(r =>
r.Confirmation.Document.Date >= filter.ConfirmationDateFrom);
}
.....
// we can write this query separately
var confirmations = dbContext.Confirmations
.Join(dbContext.DocumentHierarchies,
c => c.DocumentId,
h => h.ChildDocumentId,
(c, h) => new HConfirmation { Confirmation = c, Hierarchy = h }
);
var orderParam = Expression.Parameter(typeof(Order), "o");
var hConfirmationParam = Expression.Parameter(typeof(HConfirmation), "r");
// r.Hierarchy.ParentDocumentId == o.DocumentId
var anyPredicate = (Expression)Expression.Equal(Expression.Property(Expression.Property(hConfirmationParam, "Hierarchy"), "ParentDocumentId"),
Expression.Property(orderParam, "DocumentId"));
// r.Confirmation
var confirmationAccess = Expression.Property(hConfirmationParam, "Confirmation");
// correcting confirmation predicate
var confirmationPredicateCorrected = ExpressionReplacer.GetBody(confirmationPredicate, confirmationAccess);
// r.Hierarchy.ParentDocumentId == o.DocumentId && confirmationPredicateCorrected
anyPredicate = Expression.AndAlso(anyPredicate, confirmationPredicateCorrected);
// r => r.Hierarchy.ParentDocumentId == o.DocumentId && confirmationPredicateCorrected
var anyLambda = Expression.Lambda(anyPredicate, hConfirmationParam);
var anyCall = Expression.Call(typeof(Queryable), "Any", new [] { typeof(HConfirmation) }, confirmations.Expression, Expression.Quote(anyLambda));
var additionalPredicate = Expression.Lambda<Func<Order, bool>>(anyCall, orderParam);
predicate = predicate.And(additionalPredicate);
return predicate;
}
Anyawy, additional helper class is needed:
public class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
Strongly recommend this extension for Expression Tree visualization and debugging: https://marketplace.visualstudio.com/items?itemName=vs-publisher-1232914.ReadableExpressionsVisualizers

.net core - combine a list of func with or to a single func

Hello i try to generate a single Func from a list combined by or.
var funcs = new List<Func<User, bool>>()
{
(u) => u.Id.Equals(entityToFind.Id),
(u) => u.UserName == entityToFind.UserName,
(u) => u.Email == entityToFind.Email
};
//TODO: Some magic that funs is euqaly to that:
Func<User, bool> func = (u) => u.Id.Equals(entityToFind.Id) || u.UserName == entityToFind.UserName || u.Email == entityToFind.Email;
I also tried it with Expressions, like that:
private Dictionary<string, Expression<Func<User, bool>>> private Dictionary<string, Expression<Func<User, bool>>> test(User entityToFind)
{
return new Dictionary<string, Expression<Func<User, bool>>>() {
{"Id", (u) => u.Id.Equals(entityToFind.Id) },
{"Name", (u) => u.UserName == entityToFind.UserName },
{"Email", (u) => u.Email == entityToFind.Email }
};
}
public static Expression<Func<T, bool>> ToOrExpression<T>(this Dictionary<string, Expression<Func<T, bool>>> dict)
{
var expressions = dict.Values.ToList();
if (!expressions.Any())
{
return t => true;
}
var delegateType = typeof(Func<T, bool>)
.GetGenericTypeDefinition()
.MakeGenericType(new[]
{
typeof(T),
typeof(bool)
}
);
var tfd = Expression.OrElse(expressions[0], expressions[1]);
var combined = expressions
.Cast<Expression>()
.Aggregate( (e1, e2) => Expression.OrElse(e1, e2) );
return (Expression<Func<T, bool>>)Expression.Lambda(delegateType, combined);
}
test(entityToFind).ToOrExpression();
But there i will get the following error:
The binary operator OrElse is not defined for the types 'System.Func2[Models.User,System.Boolean]' and
'System.Func2[Models.User,System.Boolean]'
While you could create a wrapper method to combine a bunch of Funcs, because you are using Entity Framework, that would cause the entire dataset to be downloaded into memory and the search done locally. What you should be using is Expression<Func<T, bool>> instead.
Fortunately Marc Gravell has already written a handy bit of code to combine expressions. Your question is strictly a dupliucate because you want to combine more than 2 together, but that is quite easy with a little Linq. So, lets start with your expressions first, the code barely changes:
var expressions = new List<Expression<Func<User, bool>>>()
{
(u) => u.Id.Equals(entityToFind.Id),
(u) => u.UserName == entityToFind.UserName,
(u) => u.Email == entityToFind.Email
};
Now using Marc's code and modifying it to be or instead of and:
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> OrElse<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.OrElse(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);
}
}
}
No you combine your expressions with the Linq Aggregate method:
var combinedExpression = expressions.Aggregate((x, y) => x.OrElse(y));
And use it something like this:
var result = db.Things.Where(combinedExpression);

Generating lambda expression - Type conversion Error

I have a generic method that filters a list of entities, filtering is done by generating lambda expressions:
protected object initFilters<TEntity>(string targetEntity, List<SearchItem> searchItems, int page_size = 20, int offset = 0 , ExpressionSortCriteria<TEntity, string> SortCriteria)
{
var SearchQuery = new SearchQuery<TEntity>();
if (searchItems != null)
{
var predicat = ExpressionBuilder.GetExpression<TEntity>(searchItems).Compile();
SearchQuery.AddFilter(predicat);
}
//SearchQuery.AddSortCriteria(new ExpressionSortCriteria<Profile, string> { SortExpression = (profile => profile.Libelle), Direction = SortDirection.Ascending });
SearchQuery.Skip = offset;
SearchQuery.Take = page_size;
return (object)SearchQuery;
}
My SearchQuery contains criteria that will be applied to the list.
My predicate is build by a method in this way :
public static Expression<Func<T, bool>> GetExpression<T>(IList<SearchItem> filters)
{
some code ...
return Expression.Lambda<Func<T, bool>>(exp, param);
}
My AddFilter (in the searchQuery) is like this :
public void AddFilter(Expression<Func<TEntity, Boolean>> filter)
{
Filters.Add(filter);
}
I have a problem with this line :
SearchQuery.AddFilter(predicat);
Error:
cannot convert from System.Func to System.Linq.Expressions.Expression>
As you see , my two methods are using
Expression<Func<T, bool>>
Any idea please how to solve this ?
in your comment, you say you make
var SearchQuery = new SearchQuery<TEntity>();
if (searchItems != null) {
var predicat = ExpressionBuilder.GetExpression<TEntity>(searchItems).Compile();
SearchQuery.AddFilter(predicat);
}
of course, if your compile your Expression, it's no more an Expression<Func<T, bool>> but a Func<T, bool>
and AddFilter takes an Expression<Func<T, bool>> as argument.
So... remove the Compile()

binaryexpression contains method

I created a helper class which is able to build lambda expression from string parameters an I can filter a query result using this.
But I have little problem that the LINQ.Expressions.Expression does not have a Contains method.
this is my code:
string member = d.Member;
object value = d.Value;
System.Linq.Expressions.Expression expression = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
foreach (var property in member.Split('.'))
{
expression = System.Linq.Expressions.Expression.PropertyOrField(expression, property);
}
ConstantExpression c = System.Linq.Expressions.Expression.Constant(value, typeof(string));
BinaryExpression b = null;
switch (d.Operator)
{
case FilterOperator.IsEqualTo:
b = System.Linq.Expressions.Expression.Equal(expression, c);
break;
case FilterOperator.Contains:
b = GetExpression<T>(expression.ToString(), value.ToString()).Body as BinaryExpression;
break;
case FilterOperator.IsGreaterThanOrEqualTo:
b = System.Linq.Expressions.Expression.GreaterThanOrEqual(expression, c);
break;
case FilterOperator.IsLessThanOrEqualTo:
b = System.Linq.Expressions.Expression.LessThanOrEqual(expression, c);
break;
}
CriteriaCollection.Add(b);
static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return BinaryExpression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
It should be work but the how I can convert Expression to BinaryExpression?
Anybody knows this or knows an other solution which is working?
i know it's been a long time since the question was asked but, well, i've think i've found an answer, which, basically, lies on MakeBinary.
First of all, i had to create a method alike Contains, but not a self referencing extension method, like this:
public static bool Containss(string text, string text2)
{
return text.Contains(text2, StringComparison.OrdinalIgnoreCase);
}
Then you use it like this:
MethodInfo method = typeof(StackOverflowAnswer).GetMethod("Containss", new[] { typeof(string), typeof(string) });
var constainsExp = Expression.MakeBinary(ExpressionType.Equal, Expression.Constant("FULL TEXT"), Expression.Constant("TEXT"), false, method);
Something that may also help, considering your propable objective, is to receive the member, parameter and value to compose your final expression. Here's an example:
class StackOverflowAnswer
{
static void Main()
{
ParameterExpression parameter = Expression.Parameter(typeof(Student), typeof(Student).Name);
var exp1 = GetBinaryExpression(parameter, "Name", "Foo");
var exp2 = GetBinaryExpression(parameter, "Name", "Bar");
BinaryExpression[] expressions = new BinaryExpression[] { exp1, exp2 };
var bin = CombineExpressions(expressions, parameter);
var func = Expression.Lambda<Func<Student, bool>>(bin, parameter);
var exp = func.Compile();
Student student = new Student { Name = "Foo Bar" };
var x = exp(student);
Console.WriteLine(x);
}
public static BinaryExpression GetBinaryExpression(ParameterExpression parameter, string property, object comparissonValue)
{
MemberExpression member = Expression.Property(parameter, property);
ConstantExpression constant = Expression.Constant(comparissonValue, comparissonValue.GetType());
MethodInfo method = typeof(StackOverflowAnswer).GetMethod("Containss", new[] { typeof(string), typeof(string) });
var containsExp = Expression.MakeBinary(ExpressionType.Equal, member, constant, false, method);
return containsExp ;
}
public static BinaryExpression CombineExpressions(BinaryExpression[] expressions, ParameterExpression parameter)
{
bool first = true;
BinaryExpression expFull = expressions[0];
foreach (BinaryExpression item in expressions)
{
if (first)
first = false;
else
{
expFull = Expression.AndAlso(expFull, item);
}
}
return expFull;
}
internal class Student
{
public string Name { get; set; }
}
}
public class FilterExpressionHelper<T> where T : class
{
public FilterExpressionHelper()
{
CriteriaCollection = new List<BinaryExpression>();
}
public List<BinaryExpression> CriteriaCollection { get; set; }
public Expression<Func<T, bool>> NoFilterExpression { get; set; }
public void RemoveFilterCriteriaFilterDescriptor(Telerik.Windows.Data.FilterDescriptor d)
{
string member = d.Member;
object value = d.Value;
System.Linq.Expressions.Expression expression = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
foreach (var property in member.Split('.'))
{
expression = System.Linq.Expressions.Expression.PropertyOrField(expression, property);
}
ConstantExpression c = System.Linq.Expressions.Expression.Constant(value, typeof(string));
BinaryExpression b = System.Linq.Expressions.Expression.Equal(expression, c);
BinaryExpression expr = CriteriaCollection.Where(cr => cr.Right.ToString() == b.Right.ToString()).FirstOrDefault();
CriteriaCollection.Remove(expr);
}
public void AddFilterCriteriaFilterDescriptor(Telerik.Windows.Data.FilterDescriptor d)
{
string member = d.Member;
object value = d.Value;
System.Linq.Expressions.Expression expression = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
foreach (var property in member.Split('.'))
{
expression = System.Linq.Expressions.Expression.PropertyOrField(expression, property);
}
ConstantExpression c = System.Linq.Expressions.Expression.Constant(value, value.GetType());
BinaryExpression b = null;
switch (d.Operator)
{
case FilterOperator.IsEqualTo:
b = System.Linq.Expressions.Expression.Equal(expression, c);
break;
case FilterOperator.Contains:
//b = GetExpression<T>(expression.ToString(), value.ToString()).Body as BinaryExpression;
break;
case FilterOperator.IsGreaterThanOrEqualTo:
b = System.Linq.Expressions.Expression.GreaterThanOrEqual(expression, c);
break;
case FilterOperator.IsLessThanOrEqualTo:
b = System.Linq.Expressions.Expression.LessThanOrEqual(expression, c);
break;
}
CriteriaCollection.Add(b);
}
public Expression<Func<T, bool>> GetLambdaExpression()
{
ParameterExpression e = System.Linq.Expressions.Expression.Parameter(typeof(T), "e");
var orderedList = CriteriaCollection.OrderBy(cr => cr.Left.ToString()).ToList();
var disctinctValues = CriteriaCollection.Distinct(new BinaryExpressionComparer()).ToList();
List<BinaryExpression> orElseExpressionList = new List<BinaryExpression>();
foreach (var value in disctinctValues)
{
System.Linq.Expressions.BinaryExpression expression = null;
foreach (var criteria in orderedList.Where(cr => cr.Left.ToString().Equals(value.Left.ToString())))
{
if (expression == null)
{
expression = criteria;
}
else
{
if (expression.Left.ToString() == criteria.Left.ToString())
expression = System.Linq.Expressions.BinaryExpression.OrElse(expression, criteria);
else
expression = System.Linq.Expressions.BinaryExpression.AndAlso(expression, criteria);
}
}
orElseExpressionList.Add(expression);
}
System.Linq.Expressions.BinaryExpression expressionAnd = null;
foreach (var ex in orElseExpressionList)
{
if (expressionAnd == null)
{
expressionAnd = ex;
}
else
{
expressionAnd = System.Linq.Expressions.BinaryExpression.AndAlso(expressionAnd, ex);
}
}
if (expressionAnd != null)
{
return System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(expressionAnd, e);
}
else
{
return NoFilterExpression;
}
}
static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return BinaryExpression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}
private static System.Linq.Expressions.BinaryExpression Like(Expression lhs, Expression rhs)
{
//typeof(string).GetMethod("Contains", new Type[] { typeof(string) }, null);
Expression expression = Expression.Call(
typeof(FileInfoHelper).GetMethod("Like",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
, lhs, rhs);
return expression as BinaryExpression;
}
class BinaryExpressionComparer : IEqualityComparer<BinaryExpression>
{
#region IEqualityComparer<Contact> Members
public bool Equals(BinaryExpression x, BinaryExpression y)
{
return x.Left.ToString().Equals(y.Left.ToString());
}
public int GetHashCode(BinaryExpression obj)
{
return obj.Left.ToString().GetHashCode();
}
#endregion
}
}

Generic function for building Dynamic Linq query of string.contains against tokenized string

I am using Expression.And and Expression.Or to build dynamic linq queries. When the property/field being queried is a string, and the string contains spaces I would like to tokenize the string on the spaces and create an "And'd" sub query on the tokens.
Here is what I mean in a non generic fashion
var tokens = Code.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries);
var index = 0;
var firstToken = tokens[index ++];
Expression<Func<Entity, bool>> subQuery =
entity => entity.Code.Contains(firstToken);
for (; index < tokens.Length; index ++)
{
var tempToken = tokens[index];
subQuery = subQuery.And(entity => entity.Code.Contains(tempToken));
}
query = query.Or(subQuery);
What I'd like to do is find a way of writing a method which is generic enough to just call for example:
PredicateBuilder.BuildTokenizedStringQuery<Entity>(
tokens, entity => entity.Code);
and I end up with the same result. The following is where I'm at but I can't use the Func stringProp accessor in and Expression. I have to somehow combine an accessor expression (of the string property) with an invocation expression (that invokes string.Contains)
private Expression<Func<T, bool>> BuildTokenizedStringQuery<T>(string[] tokens,
Func<T, string> stringProp)
{
var index = 0;
var firstToken = tokens[index++];
Expression<Func<T, bool>> subQuery = entity =>
stringProp(entity).Contains(firstToken);
for (; index < tokens.Length; index++)
{
var tempToken = tokens[index];
subQuery = subQuery.And(
entity => stringProp(entity).Contains(tempToken));
}
return subQuery;
}
I'd also be interested to hear if this all looks like a bad idea.
Here's what I use to do this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
namespace MyLibrary.Extensions
{
/// <summary>Defines extension methods for building and working with Expressions.</summary>
public static class ExpressionExtensions
{
/// <summary>Ands the Expressions.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expressions">The Expression(s) to and.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> And<T>(this IEnumerable<Expression<Func<T, bool>>> expressions)
{
if (expressions.IsNullOrEmpty())
return null;
Expression<Func<T, bool>> finalExpression = expressions.First();
foreach (Expression<Func<T, bool>> e in expressions.Skip(1))
finalExpression = finalExpression.And(e);
return finalExpression;
}
/// <summary>Ors the Expressions.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expressions">The Expression(s) to or.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> Or<T>(this IEnumerable<Expression<Func<T, bool>>> expressions)
{
if (expressions.IsNullOrEmpty())
return null;
Expression<Func<T, bool>> finalExpression = expressions.First();
foreach (Expression<Func<T, bool>> e in expressions.Skip(1))
finalExpression = finalExpression.Or(e);
return finalExpression;
}
/// <summary>Ands the Expression with the provided Expression.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expression1">The left Expression to and.</param>
/// <param name="expression2">The right Expression to and.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
//Reuse the first expression's parameter
ParameterExpression param = expression1.Parameters.Single();
Expression left = expression1.Body;
Expression right = RebindParameter(expression2.Body, expression2.Parameters.Single(), param);
BinaryExpression body = Expression.AndAlso(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
/// <summary>Ors the Expression with the provided Expression.</summary>
/// <typeparam name="T">The target type of the Expression.</typeparam>
/// <param name="expression1">The left Expression to or.</param>
/// <param name="expression2">The right Expression to or.</param>
/// <returns>A new Expression.</returns>
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
//Reuse the first expression's parameter
ParameterExpression param = expression1.Parameters.Single();
Expression left = expression1.Body;
Expression right = RebindParameter(expression2.Body, expression2.Parameters.Single(), param);
BinaryExpression body = Expression.OrElse(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}
/// <summary>Updates the supplied expression using the appropriate parameter.</summary>
/// <param name="expression">The expression to update.</param>
/// <param name="oldParameter">The original parameter of the expression.</param>
/// <param name="newParameter">The target parameter of the expression.</param>
/// <returns>The updated expression.</returns>
private static Expression RebindParameter(Expression expression, ParameterExpression oldParameter, ParameterExpression newParameter)
{
if (expression == null)
return null;
switch (expression.NodeType)
{
case ExpressionType.Parameter:
{
ParameterExpression parameterExpression = (ParameterExpression)expression;
return (parameterExpression.Name == oldParameter.Name ? newParameter : parameterExpression);
}
case ExpressionType.MemberAccess:
{
MemberExpression memberExpression = (MemberExpression)expression;
return memberExpression.Update(RebindParameter(memberExpression.Expression, oldParameter, newParameter));
}
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
{
BinaryExpression binaryExpression = (BinaryExpression)expression;
return binaryExpression.Update(RebindParameter(binaryExpression.Left, oldParameter, newParameter), binaryExpression.Conversion, RebindParameter(binaryExpression.Right, oldParameter, newParameter));
}
case ExpressionType.Call:
{
MethodCallExpression methodCallExpression = (MethodCallExpression)expression;
return methodCallExpression.Update(RebindParameter(methodCallExpression.Object, oldParameter, newParameter), methodCallExpression.Arguments.Select(arg => RebindParameter(arg, oldParameter, newParameter)));
}
case ExpressionType.Invoke:
{
InvocationExpression invocationExpression = (InvocationExpression)expression;
return invocationExpression.Update(RebindParameter(invocationExpression.Expression, oldParameter, newParameter), invocationExpression.Arguments.Select(arg => RebindParameter(arg, oldParameter, newParameter)));
}
default:
{
return expression;
}
}
}
public static Expression<Func<T, bool>> BuildContainsExpression<T, R>(Expression<Func<T, R>> valueSelector, IEnumerable<R> values)
{
if (null == valueSelector)
throw new ArgumentNullException("valueSelector");
if (null == values)
throw new ArgumentNullException("values");
ParameterExpression parameterExpression = valueSelector.Parameters.Single();
IEnumerable<BinaryExpression> equalExpressions = null;
Expression aggregationExpression = null;
if (!values.IsNullOrEmpty())
return (e => false);
equalExpressions = values.Select(v => Expression.Equal(valueSelector.Body, Expression.Constant(v, typeof(R))));
aggregationExpression = equalExpressions.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));
return Expression.Lambda<Func<T, bool>>(aggregationExpression, parameterExpression);
}
public static Expression<Func<T, bool>> BuildDoesNotContainExpression<T, R>(Expression<Func<T, R>> valueSelector, IEnumerable<R> values)
{
if (null == valueSelector)
throw new ArgumentNullException("valueSelector");
ParameterExpression parameterExpression = valueSelector.Parameters.Single();
IEnumerable<BinaryExpression> notEqualExpressions = null;
Expression aggregationExpression = null;
if (!values.IsNullOrEmpty())
return (e => false);
notEqualExpressions = values.Select(v => Expression.NotEqual(valueSelector.Body, Expression.Constant(v, typeof(R))));
aggregationExpression = notEqualExpressions.Aggregate<Expression>((accumulate, equal) => Expression.And(accumulate, equal));
return Expression.Lambda<Func<T, bool>>(aggregationExpression, parameterExpression);
}
}
}
Usage
string query = "kill mockingbird";
string[] tokens = query.Split(' ');
Expression<Func<Book, string>> inClause = BuildContainsExpression<Book, string>(o => o.Title, tokens);
using (LibraryDataContext dataContext = new LibraryDataContext())
{
List<Book> matchingBooks = dataContext.Books.Where(inClause).ToList();
}
Results
This will find all books whose title contains the words "kill" or "mockingbird."
The answer Josh provided is awesome and helped me to get exactly what I want. It however tests for equality of each token (it is also more generic as equality can be tested against any type) as opposed to a string.Contains test. Here is a solution that gives a string.Contains result:
public static Expression<Func<T, bool>>
BuildTokenizedStringQuery<T>(string[] tokens,
Expression<Func<T, string>> stringPropertyAccessor)
{
ParameterExpression parameterExpression = stringPropertyAccessor.Parameters
.Single();
var index = 0;
var firstToken = tokens[index ++];
Expression<Func<string, bool>> contains =
aString => aString.Contains(firstToken);
var invocation = Expression.Invoke(contains, stringPropertyAccessor.Body);
Expression<Func<T, bool>> expression = Expression
.Lambda<Func<T, bool>>(invocation, parameterExpression);
for (; index < tokens.Length; index++)
{
var tempToken = tokens[index];
contains = aString => aString.Contains(tempToken);
invocation = Expression.Invoke(contains, stringPropertyAccessor.Body);
expression = expression.And(Expression
.Lambda<Func<T, bool>>(invocation, parameterExpression));
}
return expression;
}

Resources