Dynamic where clause in lambda - c#

I am using entity framework, and I need to create a dynamic expressions like:
var sel = Expression.Lambda<Func<TEntity, bool>>(propertyAccess, parameter);
var compiledSel = sel.Compile();
// sel = x.Name
// filter.Value = "John"
Repository.GetData.Where(item => compiledSel(item) != null && compiledSel(item).ToLower().StartsWith(filter.Value.ToString().ToLower()))
The above works with IQueriable, but I need it to work with entity framework.
That means I need to parse
item => compiledSel(item) != null && compiledSel(item).ToLower().StartsWith(filter.Value.ToString().ToLower())
to e.g.
x => x.Name != null && x.Name.StartsWith("John")
The reason I am doing this is because I have multiple entities I want to be able to filter dynamically.
Any suggestions?
Edit:
The query itself against EF is run here:
private IList<TEntity> GetCollection(Expression<Func<TEntity, bool>> where, Expression<Func<TEntity, object>>[] includes)
{
return DbSet
.Where(where)
.ApplyIncludes(includes)
.ToList();
}
When I run the query now the data where clause is Param_0 => (((Invoke(value(.... and I get The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. error

First off, if the propertyAccess is accessor to a string property, the following
var sel = Expression.Lambda<Func<TEntity, bool>>(propertyAccess, parameter);
should be
var sel = Expression.Lambda<Func<TEntity, string>>(propertyAccess, parameter);
Second, the Compile does not work inside the EF expressions. You can build the whole predicate expression manually using the methods of the Expression class, but that's relatively hard. What I could suggest you is to use a "prototype" expression and simple parameter replacer like this:
var selector = Expression.Lambda<Func<TEntity, string>>(propertyAccess, parameter);
var value = filter.Value.ToString().ToLower();
Expression<Func<string, bool>> prototype =
item => item != null && item.ToLower().StartsWith(value);
var predicate = Expression.Lambda<Func<T, bool>>(
prototype.Body.ReplaceParameter(prototype.Parameters[0], selector.Body),
selector.Parameters[0]);
Here is the code of the used helper method:
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);
}
}
}

Dynamic Linq might be an option for you:
Repository.People.Where("Name != null && Name.StartsWith(\"John\")")
https://www.nuget.org/packages/System.Linq.Dynamic

Related

EF Core - Expression Tree Equivalent for IQueryable Search

I have an initial workflow put together that allows me to perform inclusive searches for string properties of objects contained in an IQueryable:
public static IQueryable ApplySearch(this IQueryable queryable, string search)
{
// validation omitted for brevity
var expression = queryable
.Cast<object>()
.Where(item => item.SearchStringTree(search))
.Expression;
var result = queryable.Provider.CreateQuery(expression);
return result;
}
static bool SearchStringTree<T>(this T value, string search) =>
value.GetObjectStrings().Any(s => s.Contains(search.ToLower()));
static IEnumerable<string> GetObjectStrings<T>(this T value)
{
var strings = new List<string>();
var properties = value.GetType()
.GetProperties()
.Where(x => x.CanRead);
foreach (var prop in properties)
{
var t = prop.PropertyType.ToString().ToLower();
var root = t.Split('.')[0];
if (t == "system.string")
{
strings.Add(((string)prop.GetValue(value)).ToLower());
}
else if (!(root == "system"))
{
strings.AddRange(prop.GetValue(value).GetObjectStrings());
}
}
return strings;
}
Would it be possible to apply this concept in a way that Entity Framework can translate prior to DbContext execution?
I've been looking into potentially using Expression Trees to accomplish this.
Here's a working Repl.it showing the IQueryable implementation above.
You definitely need to build expression tree, basically multi or (C# ||) predicate expression for all (nested) string properties.
Something like this (expression version of your code):
public static class FilterExpression
{
public static IQueryable<T> ApplySearch<T>(this IQueryable<T> source, string search)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (string.IsNullOrWhiteSpace(search)) return source;
var parameter = Expression.Parameter(typeof(T), "e");
// The following simulates closure to let EF Core create parameter rather than constant value (in case you use `Expresssion.Constant(search)`)
var value = Expression.Property(Expression.Constant(new { search }), nameof(search));
var body = SearchStrings(parameter, value);
if (body == null) return source;
var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(predicate);
}
static Expression SearchStrings(Expression target, Expression search)
{
Expression result = null;
var properties = target.Type
.GetProperties()
.Where(x => x.CanRead);
foreach (var prop in properties)
{
Expression condition = null;
var propValue = Expression.MakeMemberAccess(target, prop);
if (prop.PropertyType == typeof(string))
{
var comparand = Expression.Call(propValue, nameof(string.ToLower), Type.EmptyTypes);
condition = Expression.Call(comparand, nameof(string.Contains), Type.EmptyTypes, search);
}
else if (!prop.PropertyType.Namespace.StartsWith("System."))
{
condition = SearchStrings(propValue, search);
}
if (condition != null)
result = result == null ? condition : Expression.OrElse(result, condition);
}
return result;
}
}
The non generic version is not much different - just instead of Where extension method you need to generate a "call" to it in the query expression tree:
public static IQueryable ApplySearch(this IQueryable source, string search)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (string.IsNullOrWhiteSpace(search)) return source;
var parameter = Expression.Parameter(source.ElementType, "e");
var value = Expression.Property(Expression.Constant(new { search }), nameof(search));
var body = SearchStrings(parameter, value);
if (body == null) return source;
var predicate = Expression.Lambda(body, parameter);
var filtered = Expression.Call(
typeof(Queryable), nameof(Queryable.Where), new[] { source.ElementType },
source.Expression, Expression.Quote(predicate));
return source.Provider.CreateQuery(filtered);
}
While this works, it's not much useful because all LINQ extensions methods (including AsEnumerable(),ToList()` etc.) work with generic interface.
Also in both cases, the type of the query element must be known in advance, e.g. T in the generic version, query.ElementType in the non generic version. This is because expression tree are processed in advance, when there are no "objects", hence it can't use item.GetType(). For the same reason, IQueryable translators like EF Core don't like Cast "calls" inside the query expression tree.

Convert Expression to Expression<Func<T, bool>>

Is that possible to convert Expression to Expression<Func<T, bool>> if instance of Expression was created on T ?
At the end I have list List<Expression> and need to produce on Expression<Func<T, bool>> where each expression of List<Expression> is agregated with AND.
Yes; just call Expression.Lambda<Func<T, bool>>(..., parameter), where ... is an expression composed of the expressions you want to combine.
You'd probably want list.Aggregate(Expressions.AndAlso).
If your expressions don't all share the same ParameterExpression, you'll need to rewrite them to do so. (use ExpressionVisitor)
It's possible, but every expression in the list must actually be a Expression<Func<T, bool>> instance.
EDIT: It turns out that you use Kendo.Mvc.IFilterDescriptor.CreateFilterExpression which actually builds a MethodCallExpressions.
The following helper method should do the job (works with both lambda and method call expressions):
public static class Utils
{
public static Expression<Func<T, bool>> And<T>(List<Expression> expressions)
{
var item = Expression.Parameter(typeof(T), "item");
var body = expressions[0].GetPredicateExpression(item);
for (int i = 1; i < expressions.Count; i++)
body = Expression.AndAlso(body, expressions[i].GetPredicateExpression(item));
return Expression.Lambda<Func<T, bool>>(body, item);
}
static Expression GetPredicateExpression(this Expression target, ParameterExpression parameter)
{
var lambda = target as LambdaExpression;
var body = lambda != null ? lambda.Body : target;
return new ParameterBinder { value = parameter }.Visit(body);
}
class ParameterBinder : ExpressionVisitor
{
public ParameterExpression value;
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type == value.Type ? value : base.VisitParameter(node);
}
}
}

Subquery with "ANY" and local array generate nested too deep SQL Statement

public IEnumerable<Table1> GetMatchingTable1(string param, double[] Thicknesses)
{
return DBContext.Table1.Where(c => c.Field1 == param
&& Thicknesses.Any(Thickness => Thickness >= c.MinThickness && Thickness <= c.MaxThickness))
.ToList();
}
Above query return the following exception. "Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries."
So far, all my research on the web for this error pointed toward replacing "ANY" with "CONTAINS". Here is one site where they fix the problem using this solution : http://blog.hompus.nl/2010/08/26/joining-an-iqueryable-with-an-ienumerable/
But in my case, "CONTAINS" doesn't seem usable since I check a RANGE with Min and Max.
How should this query be written to have a proper SQL Statement generated by LinqToEntity?
Thanks
You could try to build the query dynamically:
public IEnumerable<Table1> GetAllCoilLengthSettingsWithChilds(string param, double[] Thicknesses)
{
// Base query
var query = LinqKit.Extensions.AsExpandable(DBContext.Table1.Where(c => c.Field1 == param));
// All the various || between the Thickness ranges
var predicate = LinqKit.PredicateBuilder.False<Table1>();
foreach (double th in Thicknesses)
{
// Don't want a closure around th
double th2 = th;
predicate = predicate.Or(c => th2 >= c.MinThickness && th2 <= c.MaxThickness);
}
// This is implicitly in && with the other Where
query = query.Where(predicate);
return query.ToList();
}
The PredicateBuilder helps you build an || query. Take it from the LinqKit (source available)
I've tested it with 1000 parameters (but they where DateTime, and I didn't have other query pieces), and it seems to work. Note that the program uses another extension of LinqPad, AsExpandable, used to make the PredicateBuilder "trick" work. Note that I'm using EF 6.1.3, so your mileage may vary.
If you don't want to use LinqKit, I'm appending my version of PredicateBuilder. It doesn't require the use of AsExpandable(), but its syntax is slightly different:
public class PredicateBuilder<T>
{
// We share a single parameter for all the PredicatBuilder<T>
// istances. This isn't a proble, because Expressions are immutable
protected static readonly ParameterExpression Parameter = Expression.Parameter(typeof(T), "x");
protected Expression Current { get; set; }
// Returns an empty PredicateBuilder that, if used, is true
public PredicateBuilder()
{
}
// Use it like this: .Where(predicate) or .Any(predicate) or
// .First(predicate) or...
public static implicit operator Expression<Func<T, bool>>(PredicateBuilder<T> predicate)
{
if (object.ReferenceEquals(predicate, null))
{
return null;
}
// Handling of empty PredicateBuilder
Expression current = predicate.Current ?? Expression.Constant(true);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(current, Parameter);
return lambda;
}
public static implicit operator PredicateBuilder<T>(Expression<Func<T, bool>> expression)
{
var predicate = new PredicateBuilder<T>();
if (expression != null)
{
// Equivalent to predicate.Or(expression)
predicate.And(expression);
}
return predicate;
}
public void And(Expression<Func<T, bool>> expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
this.Current = this.Current != null ? Expression.AndAlso(this.Current, expression2) : expression2;
}
public void Or(Expression<Func<T, bool>> expression)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
var expression2 = new ParameterConverter(expression.Parameters[0], Parameter).Visit(expression.Body);
this.Current = this.Current != null ? Expression.OrElse(this.Current, expression2) : expression2;
}
public override string ToString()
{
// We reuse the .ToString() of Expression<Func<T, bool>>
// Implicit cast here :-)
Expression<Func<T, bool>> expression = this;
return expression.ToString();
}
// Small ExpressionVisitor that replaces the ParameterExpression of
// an Expression with another ParameterExpression (to make two
// Expressions "compatible")
protected class ParameterConverter : ExpressionVisitor
{
public readonly ParameterExpression From;
public readonly ParameterExpression To;
public ParameterConverter(ParameterExpression from, ParameterExpression to)
{
this.From = from;
this.To = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == this.From)
{
node = this.To;
}
return base.VisitParameter(node);
}
}
}
public static class PredicateBuilder
{
// The value of source isn't really necessary/interesting. Its type
// is :-) By passing a query you are building to Create, the compiler
// will give to Create the the of the object returned from the query
// Use it like:
// var predicate = PredicateBuilder.Create<MyType>();
// or
// var predicate = PredicateBuilder.Create(query);
public static PredicateBuilder<T> Create<T>(IEnumerable<T> source = null)
{
return new PredicateBuilder<T>();
}
// Useful if you want to start with a query:
// var predicate = PredicateBuilder.Create<MyType>(x => x.ID != 0);
// Note that if expression == null, then a new PredicateBuilder<T>()
// will be returned (that by default is "true")
public static PredicateBuilder<T> Create<T>(Expression<Func<T, bool>> expression)
{
// Implicit cast to PredicateBuilder<T>
return expression;
}
}
Use it like:
var predicate = PredicateBuilder.Create(query);
and then everything is the same (but remove the LinqKit.Extensions.AsExpandable part)

Generic Linq to Entities filter method that accepts filter criteria and properties to be filtered

I've looked into many generic linq filtering questions and their answers here in SO but none of them satisfy my needs so I thought I should create a question.
I've created many of what I call "filter provider" classes, one for each entity class in my model, to provide a simplistic search for my application. I didn't want to go into more advanced solutions like Lucene.Net because a basic filtering with matching score would suffice.
Inside each one of these provider classes there are multiple methods that will receive the filtering terms and query specific properties, returning a score for each match based on the relevance of the property. Most methods will filter multiple properties at once, but not all.
Here are two of these methods:
private IQueryable<Retailer> MatchHighRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
var results = retailers.Where(r =>
(r.CompanyName != null && r.CompanyName.ToUpper().Contains(searchTerm))
|| (r.TradingName != null && r.TradingName.ToUpper().Contains(searchTerm))
);
return results;
}
private IQueryable<Retailer> MatchMediumRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
var results = retailers.Where(r =>
(r.Address.Street != null && r.Address.Street.ToUpper().Contains(searchTerm))
|| (r.Address.Complement != null && r.Address.Complement.ToUpper().Contains(searchTerm))
);
return results;
}
These methods are replicated ad nauseum throughout each provider class and I hope I could replace them for a single method that would receive the properties to be included in the query.
Something like:
public static IQueryable<T> Match<T>(string searchTerm, IQueryable<T> data, Expression<Func<T, string>> filterProperties)
{
var results = **build the query for each property in filterProperties**
return results;
}
But I really can't figure it out. I tried using reflection but it only worked with Linq to Objects and I need a solution for Linq to Entities.
So to solve this problem we need a few puzzle pieces first. The first puzzle piece is a method that can take an expression that computes a value, and then another expression that computes a new value taking the same type the first returns, and creates a new expression that represents the result of passing the result of the first function as the parameter to the second. This allows us to Compose expressions:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
This relies on the following tool to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
We'll also need a tool to help us OR two predicate expressions together:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(
expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
Now that we have this we can use Compose on each property selector to map it from the property results to whether or not that property value is non-null and contains the search term. We can then OR all of those predicates together to get a filter for your query:
public static IQueryable<T> Match<T>(
IQueryable<T> data,
string searchTerm,
IEnumerable<Expression<Func<T, string>>> filterProperties)
{
var predicates = filterProperties.Select(selector =>
selector.Compose(value =>
value != null && value.Contains(searchTerm)));
var filter = predicates.Aggregate(
PredicateBuilder.False<T>(),
(aggregate, next) => aggregate.Or(next));
return data.Where(filter);
}
You can do it with expression trees but it's not as simple as you might think.
public static IQueryable<T> Match<T>(this IQueryable<T> data, string searchTerm,
params Expression<Func<T, string>>[] filterProperties)
{
var parameter = Expression.Parameter(typeof (T), "source");
Expression body = null;
foreach (var prop in filterProperties)
{
// need to replace all the expressions with the one parameter (gist taken from Colin Meek blog see link on top of class)
//prop.body should be the member expression
var propValue =
prop.Body.ReplaceParameters(new Dictionary<ParameterExpression, ParameterExpression>()
{
{prop.Parameters[0], parameter}
});
// is null check
var isNull = Expression.NotEqual(propValue, Expression.Constant(null, typeof(string)));
// create a tuple so EF will parameterize the sql call
var searchTuple = Tuple.Create(searchTerm);
var matchTerm = Expression.Property(Expression.Constant(searchTuple), "Item1");
// call ToUpper
var toUpper = Expression.Call(propValue, "ToUpper", null);
// Call contains on the ToUpper
var contains = Expression.Call(toUpper, "Contains", null, matchTerm);
// And not null and contains
var and = Expression.AndAlso(isNull, contains);
// or in any additional properties
body = body == null ? and : Expression.OrElse(body, and);
}
if (body != null)
{
var where = Expression.Call(typeof (Queryable), "Where", new[] {typeof (T)}, data.Expression,
Expression.Lambda<Func<T, bool>>(body, parameter));
return data.Provider.CreateQuery<T>(where);
}
return data;
}
public static Expression ReplaceParameters(this Expression exp, IDictionary<ParameterExpression, ParameterExpression> map)
{
return new ParameterRebinder(map).Visit(exp);
}
Now you need to have a expressionvisitor to make all the expressions use one parameter
//http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx
public class ParameterRebinder : ExpressionVisitor
{
private readonly IDictionary<ParameterExpression, ParameterExpression> _map;
public ParameterRebinder(IDictionary<ParameterExpression, ParameterExpression> map)
{
_map = map;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (_map.ContainsKey(node))
{
return _map[node];
}
return base.VisitParameter(node);
}
}
Would use it like
var matches = retailers.Match("7", r => r.Address.Street, x => x.Address.Complement).ToList();
Warning - I checked this with linq to objects using the AsQueryable but didn't run it against EF.
You can use Linq.Dynamic to build the query.
public static IQueryable<T> Match<T>(
string searchTerm,
IQueryable<T> data,
params Expression<Func<T, string>>[] filterProperties) where T : class
{
var predicates = new List<string>();
foreach (var prop in filterProperties)
{
var lambda = prop.ToString();
var columnName = lambda.Substring(lambda.IndexOf('.') + 1);
var predicate = string.Format(
"({0} != null && {0}.ToUpper().Contains(#0))", columnName);
predicates.Add(predicate);
}
var filter = string.Join("||", predicates);
var results = data.Where(filter, searchTerm);
return results;
}
Usage.
var retailers = Match(
"asd", db.Retailers, r => r.CompanyName, r => r.TradingName);
var retailers = Match(
"asd", db.Retailers, r => r.Address.Street, r => r.Address.Complement);
Limitation.
The filter can only accept basic expression.
r => r.Name
r => r.PropA.Name
r => r.PropA.PropB.Name
Try to use Expressions like those all
http://www.codeproject.com/Articles/493917/Dynamic-Querying-with-LINQ-to-Entities-and-Express

Create linq query to search for contact the same way smartphone will do so

Let's say that in my database I have the table
**Table Contact**
Id, FirstName, LastName, Phone, Email, DateCreated
1 Tom Williams 3052548623 tom#gmail.com 2013-12-21 14:51:08
etc...
I would like to enable users to search for a contact entering a string. Let's say the user enters:
tom -> TRUE
tom wil -> TRUE
wil tom -> TRUE
tom XX -> FALSE
t w 3 # -> TRUE
wil 305 -> TRUE
(True means search found customer Tom, False means it did not find it)
I will be performing this type of search among different tables in my database. It will be nice if I dont have to build the query for a specific table.
The approach I am thinking on taking is to split the search string every time I find one or more spaces. Then I will be creating n number of searches and then performing an intersect?
You could do something like that, assuming you want only search in the string properties (so your samples will work if we considere Phone as a string property).
It's of course doable with numeric properties (but gets more complicated).
A method like that in an helper static class
public static Expression<Func<T, bool>> BuildPredicateForFilter<T>(string filterString)
{
//first, split search by space, removing white spaces, and putting this to upper case
var filters = filterString.Split(new []{" "}, StringSplitOptions.RemoveEmptyEntries).Select(m => m.ToUpper());
var parameter = Expression.Parameter(typeof (T), "m");
//get string.Contains() method
var containsMethod = typeof (string).GetMethod("Contains");
//get string.ToUpper() method
var toUpperMethod = typeof (string).GetMethod("ToUpper", new Type[]{});
//find all the string properties of your class
var properties = typeof(T).GetProperties().Where(m => m.PropertyType == typeof(string));
//for all the string properties, build a "m.<PropertyName>.ToUpper() expression
var members = properties.Select(p => Expression.Call(Expression.Property(parameter, p), toUpperMethod));
Expression orExpression = null;
//build the expression
foreach (var filter in filters)
{
Expression innerExpression = null;
foreach (var member in members)
{
innerExpression = innerExpression == null
? (Expression)Expression.Call(member, containsMethod, Expression.Constant(filter))
: Expression.OrElse(innerExpression, Expression.Call(member, containsMethod, Expression.Constant(filter)));
}
orExpression = orExpression == null
? innerExpression
: Expression.AndAlso(orExpression, innerExpression);
}
return Expression.Lambda<Func<T, bool>>(orExpression, new[]{parameter});
}
usage :
var result = <yourSource>.Where(Helper.BuildPredicateForFilter<TableName>("tom XX"));
for example, with "tom XX", the orExpression will look like
((((m.FirstName.ToUpper().Contains("TOM") OrElse
m.LastName.ToUpper().Contains("TOM")) OrElse
m.Phone.ToUpper().Contains("TOM"))
OrElse m.Email.ToUpper().Contains("TOM"))
AndAlso
(((m.FirstName.ToUpper().Contains("XX") OrElse
m.LastName.ToUpper().Contains("XX")) OrElse
m.Phone.ToUpper().Contains("XX")) OrElse
m.Email.ToUpper().Contains("XX")))
EDIT
or you could change the method to
public static IQueryable<T> FilterFor(this IQueryable<T> queryable, string filterString) {
//same
var predicate = Expression.Lambda<Func<T, bool>>(orExpression, new[]{parameter});
return queryable.Where(predicate);
}
then usage would simply be
<yourSource>.FilterFor("tom XX");
So what we're looking to do here is search through all of the fields in a type for a given value, doing a Contains search. We can write a method to do this.
First we'll need to use a PredicateBuilder, as we'll be dynamically generating a number of expressions that we want to OR together. Here is my definition of a PredicateBuilder capable of doing that:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}
This uses the following helper method/class to replace all instances of one Expression with another:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
Another tool that we'll use to solve this problem is a Compose method. It will take one expression, then another expression that takes as input the output of another, and produces a new expression that takes the input of the first and produces the output of the last.
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Thanks to all of these tools, what's left is actually quite straightforward. We'll accept a query, a string to search for, and a series of selectors, each selecting out a field to search through. Then we initialize a filter, go through every single selector, use Compose to turn each selector into a predicate that performs a Contains check on the relevant search text, and then ORs that to the existing filter.
public static IQueryable<T> AnyFieldContains<T>(
this IQueryable<T> query,
string searchText,
params Expression<Func<T, string>>[] fieldSelectors)
{
var filter = PredicateBuilder.False<T>();
foreach (var selector in fieldSelectors)
{
filter = filter.Or(selector.Compose(
value => value.Contains(searchText)));
}
return query.Where(filter);
}
Now that we have all of this we can split the input that you have, and for each of those expressions we can call this method. Then you simply need to supply selectors for the fields that need to be searched through:
IQueryable<Foo> query = db.Foo;
string searchText = "wil tom";
var searchExpressions = searchText.Split(' ');
foreach (var expression in searchExpressions)
{
query = query.AnyFieldContains(expression,
foo => foo.FirstName,
foo => foo.LastName,
foo => foo.Phone);
}
var result = query.Any();
If you're really sure that you want to search every field (and I'm not sure if you are, it's likely many tables will have fields that shouldn't be searched, or have fields that will need some sort of work on your end to transform them into an appropriate string worth searching), then you can use reflection to generate all of the selectors, rather than typing out explicitly what you want to have searched. We can simply create an additional overload such that if no selectors are provided it will use "everything":
public static IQueryable<T> AnyFieldContains<T>(
this IQueryable<T> query,
string searchText)
{
return AnyFieldContains(query, searchText,
typeof(T).GetProperties()
.Select(prop => CreateSelector<T>(prop))
.ToArray());
}
private static Expression<Func<T, string>> CreateSelector<T>(PropertyInfo prop)
{
var param = Expression.Parameter(typeof(T));
Expression body = Expression.Property(param, prop);
if (prop.PropertyType == typeof(decimal?))
body = Expression.Call(body, typeof(SqlFunctions)
.GetMethod("StringConvert", new[] { typeof(decimal?) }));
else if (prop.PropertyType == typeof(double?))
body = Expression.Call(body, typeof(SqlFunctions)
.GetMethod("StringConvert", new[] { typeof(double?) }));
return Expression.Lambda<Func<T, string>>(body, param);
}

Categories

Resources