I have a DateRange class that I'd like to apply to an IQueryable as a where predicate, automatically using the begin and end dates and automatically using an open or closed interval.
public class DateRange
{
public DateTime? BeginDate { get; set; }
public DateTime? EndDate { get; set; }
public bool BeginInclusive { get; set; }
public bool EndInclusive { get; set; }
public DateRange()
{
BeginInclusive = true;
EndInclusive = false;
}
public IQueryable<T> Apply<T>( IQueryable<T> source, Expression<Func<T,DateTime>> dateField )
{
var result = source;
if (BeginDate.HasValue)
{
if (BeginInclusive)
result = result.Where( x => dateField >= BeginDate ); //does not compile
else
result = result.Where( x => dateField > BeginDate ); //does not compile
}
if (EndDate.HasValue)
{
if (EndInclusive)
result = result.Where( x => dateField <= EndDate ); //does not compile
else
result = result.Where( x => dateField < EndDate ); //does not compile
}
return result;
}
}
And I want to call it like this, DateField is any DateTime property of T.
DateRange d;
IQueryable<T> q;
q = d.Apply( q, x => x.DateField );
So I want to pass a member expression to the Apply method, and have it apply an appropriate where clause to the result set, but I cannot figure out how to get the dateField member expression embedded in the where predicate's expression. See lines "do not compile" in class above. I need to transform dateField somehow or build the predicate expression some other way, but I have no idea how to do so.
What you're looking to do here is to compose expressions; you're trying to apply one expression to the result of another. You can actually write a method to do that:
public static Expression<Func<TSource, TResult>> Compose<TSource, TIntermediate, TResult>(
this Expression<Func<TSource, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TSource));
var intermediateValue = first.Body.ReplaceParameter(first.Parameters[0], param);
var body = second.Body.ReplaceParameter(second.Parameters[0], intermediateValue);
return Expression.Lambda<Func<TSource, TResult>>(body, param);
}
It uses the following method to replace the parameter of an expression with an expression.
public static Expression ReplaceParameter(this Expression expression,
ParameterExpression toReplace,
Expression newExpression)
{
return new ParameterReplaceVisitor(toReplace, newExpression)
.Visit(expression);
}
public class ParameterReplaceVisitor : ExpressionVisitor
{
private ParameterExpression from;
private Expression to;
public ParameterReplaceVisitor(ParameterExpression from, Expression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : node;
}
}
This allows you to write your code as:
public IQueryable<T> Apply<T>(IQueryable<T> source,
Expression<Func<T, DateTime>> dateField)
{
var result = source;
if (BeginDate.HasValue)
{
if (BeginInclusive)
result = result.Where(dateField.Compose(date => date >= BeginDate));
else
result = result.Where(dateField.Compose(date => date > BeginDate));
}
if (EndDate.HasValue)
{
if (EndInclusive)
result = result.Where(dateField.Compose(date => date <= EndDate));
else
result = result.Where(dateField.Compose(date => date < EndDate));
}
return result;
}
You'll have to hand-craft dateField >= BeginDate using Expression class methods.
(...)
if (BeginInclusive)
{
var greaterOrEqual =
Expression.Lambda<Func<T, bool>>(
Expression.GreaterThanOrEqual(
dateField.Body,
Expression.Constant(BeginDate)),
dateField.Parameters);
result = result.Where(greaterOrEqual);
}
(...)
Similarly for the other cases.
Here's the updated Apply method created after figuring this out.
public IQueryable<T> Apply<T>( IQueryable<T> source, Expression<Func<T,DateTime>> dateField )
{
Expression predicate;
if (BeginDate.HasValue)
{
if (BeginInclusive)
predicate = Expression.GreaterThanOrEqual( dateField.Body, Expression.Constant( BeginDate, typeof(DateTime) ) );
else
predicate = Expression.GreaterThan( dateField.Body, Expression.Constant( BeginDate, typeof(DateTime) ) );
source = source.Where( Expression.Lambda<Func<T, bool>>( predicate ) );
}
if (EndDate.HasValue)
{
if (EndInclusive)
predicate = Expression.LessThanOrEqual( dateField.Body, Expression.Constant( EndDate, typeof(DateTime) ) );
else
predicate = Expression.LessThan( dateField.Body, Expression.Constant( EndDate, typeof(DateTime) ) );
source = source.Where( Expression.Lambda<Func<T, bool>>( predicate ) );
}
return source;
}
Next, I'll transform it into an extension method, so it can be used like:
DateRange range;
IQueryable<T> q;
q = q.WhereInDateRange( range, x => x.DateField );
Related
I'm creating some extension methods on IQueryable to make wildcard filtering easier. But I'm stumbling into a lot of exceptions when I try to filter a sub list. My example:
public class User {
public string FirstName { get; set; }
public string LastName { get; set; }
public IReadOnlyList<Address> Addresses { get; set; }
...
}
public class Address {
public string Street { get; set; }
...
}
My wildcard implementation speaks for itself, the method expects a list of values and supports StartsWith, EndsWith and Contains. I have a Filter method that looks like this:
public static IQueryable<T> Filter<T>(this IQueryable<T> query, Expression<Func<T, string>> property,
IList<string> values)
{
if (values == null || values.Count == 0)
return query;
Expression<Func<T, bool>> condition;
if (values.Count == 1)
condition = GetBooleanExpressionFromString(property, values.First()).Expand();
else
condition = GetBooleanExpressionFromStringList(property, values).Expand();
return query.Where(condition);
}
And the expressions builders look like:
private static Expression<Func<T, bool>> GetBooleanExpressionFromStringList<T>(
Expression<Func<T, string>> expression,
IList<string> values)
{
var predicate = PredicateBuilder.New<T>();
foreach (var value in values)
{
var parsedValue = value.Replace("*", string.Empty);
if (value.StartsWith("*") && value.EndsWith("*"))
predicate.Or(x => expression.Invoke(x).Contains(parsedValue));
else if (value.StartsWith("*"))
predicate.Or(x => expression.Invoke(x).EndsWith(parsedValue));
else if (value.EndsWith("*"))
predicate.Or(x => expression.Invoke(x).StartsWith(parsedValue));
else predicate.Or(x => expression.Invoke(x) == parsedValue);
}
return predicate;
}
public static Expression<Func<T, bool>> GetBooleanExpressionFromString<T>(
Expression<Func<T, string>> expression,
string value)
{
var parsedValue = value.Replace("*", string.Empty);
if (value.StartsWith("*") && value.EndsWith("*"))
return x => expression.Invoke(x).Contains(parsedValue);
if (value.StartsWith("*"))
return x => expression.Invoke(x).EndsWith(parsedValue);
if (value.EndsWith("*"))
return x => expression.Invoke(x).StartsWith(parsedValue);
return x => expression.Invoke(x) == parsedValue;
}
At the end I can use the Filter method like this:
var query = Session.Query<User>();
query = query.Filter(x => x.FirstName, new [] { "Foo*", "*Bar", "*test*" });
return query;
Now I want to make the same extension for the Street property of the Address list. In normal Linq it would compile to query.Where(x => x.Addresses.Any(y => y.Street.*wildcardstuff*)) and the Filter method would be called like query.Filter(x => x.Addresses, x => x.Street, values). But I'm keep getting NotSupported exceptions. The last thing I tried is this post from EF but its not that typed like i want it to be.
The last implementation I tried is this one:
public static IQueryable<T> FilterList<T, U>(this IQueryable<T> query, Expression<Func<T, IEnumerable<U>>> innerList, Expression<Func<U, string>> property,
IList<string> values)
{
if (values == null || values.Count == 0)
return query;
Expression<Func<U, bool>> condition;
if (values.Count == 1)
condition = GetBooleanExpressionFromString(property, values.First()).Expand();
else
condition = GetBooleanExpressionFromStringList(property, values).Expand();
return query.Where(i => innerList.Invoke(i).Any(j => condition.Invoke(j)));
}
but got this exception: System.NotSupportedException: Cannot parse expression 'x => x.Addresses' as it has an unsupported type. Only query sources (that is, expressions that implement IEnumerable) and query operators can be parsed.
I tried to make an extension on string with the wildcard logic but this throws also a NotSupported exception, anyone has an idea?
I would make it more universal. Which may simplify creating such extensions.
query = query.FilterByWildcard(x => x.FirstName, new [] { "Foo*", "*Bar", "*test*" });
And realization:
public static class QueryExtensions
{
public static IQueryable<T> FilterByWildcard<T>(this IQueryable<T> query, Expression<Func<T, string>> prop, IEnumerable<string> items)
{
return query.FilterBy(items, prop, s =>
{
var pattern = s.Trim('*');
if (s.StartsWith("*"))
if (s.EndsWith("*"))
return e => e.Contains(pattern);
else
return e => e.StartsWith(pattern);
else if (s.EndsWith("*"))
return e => e.EndsWith(pattern);
else
return e => e == s;
});
}
public static IQueryable<T> FilterBy<T, TProp, TItem>(this IQueryable<T> query,
IEnumerable<TItem> items,
Expression<Func<T, TProp>> prop,
Func<TItem, Expression<Func<TProp, bool>>> operationSelector, bool isOr = true)
{
var param = prop.Parameters[0];
Expression predicate = null;
foreach (var item in items)
{
var operation = operationSelector(item);
var body = ExpressionReplacer.GetBody(operation, prop.Body);
if (predicate == null)
{
predicate = body;
}
else
{
predicate = Expression.MakeBinary(isOr ? ExpressionType.OrElse : ExpressionType.AndAlso, predicate,
body);
}
}
if (predicate == null)
return query.Where(e => 1 == 2);
var lambda = Expression.Lambda<Func<T, bool>>(predicate, param);
return query.Where(lambda);
}
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(lambda.Parameters.Zip(toReplace)
.ToDictionary(e => (Expression)e.First, e => e.Second)).Visit(lambda.Body);
}
}
}
This answer is fixing my own method but Svyatoslav Danyliv's answer is more futureproof.
public static IQueryable<T> FilterList<T, U>(this IQueryable<T> query, Expression<Func<T, IEnumerable<U>>> innerList, Expression<Func<U, string>> property,
IList<string> values)
{
if (values == null || values.Count == 0)
return query;
Expression<Func<U, bool>> condition;
if (values.Count == 1)
condition = GetBooleanExpressionFromString(property, values.First());
else
condition = GetBooleanExpressionFromStringList(property, values);
Expression<Func<T, bool>> finalCondition = t => innerList.Invoke(t).Any(j => condition.Invoke(j));
return query.Where(finalCondition.Expand());
}
And then use it like this.
var query = Session.Query<User>();
query = query.FilterList(x => x.Addresses, y => y.Street, new [] { "Foo*", "*Bar", "*test*" });
return query;
Which is shorter and cleaner but restricted.
I had another question similar answered here on NULL values being last on an order by.
Keep NULL rows last on Dynamic Linq Order By
I would also like to see if I can do the same thing with a Date column with the following criteria.
All items with all end dates on the current date and up at the top, sorted by the most recent upcoming event
Followed by all past events using the end date and comparing to the current date with the most recent end date passed and down. I do something similar in pure SQL at the moment.
(CASE
WHEN ev.EndDate >= GETDATE() THEN 1
ELSE 2
END) ASC,
(CASE
WHEN ev.EndDate >= GETDATE() THEN ev.EndDate
ELSE ev.StartDate
END) ASC,
Example: Current Date 3/24/2017
EndDate
3/25/2017
4/15/2017
7/29/2017
3/23/2017
2/22/2016
Current Code
public static class OrderByHelper
{
public static IOrderedQueryable<T> ThenBy<T>(this IEnumerable<T> source, string orderBy)
{
return source.AsQueryable().ThenBy(orderBy);
}
public static IOrderedQueryable<T> ThenBy<T>(this IQueryable<T> source, string orderBy)
{
return OrderBy(source, orderBy, false);
}
public static IOrderedQueryable<T> OrderBy<T>(this IEnumerable<T> source, string orderBy)
{
return source.AsQueryable().OrderBy(orderBy);
}
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string orderBy)
{
return OrderBy(source, orderBy, true);
}
private static IOrderedQueryable<T> OrderBy<T>(IQueryable<T> source, string orderBy, bool initial)
{
if (string.IsNullOrWhiteSpace(orderBy))
orderBy = "ID DESC";
var parameter = Expression.Parameter(typeof(T), "x");
var expression = source.Expression;
foreach (var item in ParseOrderBy(orderBy, initial))
{
var order = item.PropertyName.Split('.')
.Aggregate((Expression)parameter, Expression.PropertyOrField);
if (!order.Type.IsValueType || Nullable.GetUnderlyingType(order.Type) != null)
{
var preOrder = Expression.Condition(
Expression.Equal(order, Expression.Constant(null, order.Type)),
Expression.Constant(1), Expression.Constant(0));
expression = CallOrderBy(expression, Expression.Lambda(preOrder, parameter), item.Direction, initial);
initial = false;
}
expression = CallOrderBy(expression, Expression.Lambda(order, parameter), item.Direction, initial);
initial = false;
}
return (IOrderedQueryable<T>)source.Provider.CreateQuery(expression);
}
private static Expression CallOrderBy(Expression source, LambdaExpression selector, SortDirection direction, bool initial)
{
return Expression.Call(
typeof(Queryable), GetMethodName(direction, initial),
new Type[] { selector.Parameters[0].Type, selector.Body.Type },
source, Expression.Quote(selector));
}
private static string GetMethodName(SortDirection direction, bool initial)
{
return direction == SortDirection.Ascending ?
(initial ? "OrderBy" : "ThenBy") :
(initial ? "OrderByDescending" : "ThenByDescending");
}
private static IEnumerable<OrderByInfo> ParseOrderBy(string orderBy, bool initial)
{
if (String.IsNullOrEmpty(orderBy))
yield break;
string[] items = orderBy.Split(',');
foreach (string item in items)
{
string[] pair = item.Trim().Split(' ');
if (pair.Length > 2)
throw new ArgumentException(String.Format("Invalid OrderBy string '{0}'. Order By Format: Property, Property2 ASC, Property2 DESC", item));
string prop = pair[0].Trim();
if (String.IsNullOrEmpty(prop))
throw new ArgumentException("Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC");
SortDirection dir = SortDirection.Ascending;
if (pair.Length == 2)
dir = ("desc".Equals(pair[1].Trim(), StringComparison.OrdinalIgnoreCase) ? SortDirection.Descending : SortDirection.Ascending);
yield return new OrderByInfo() { PropertyName = prop, Direction = dir, Initial = initial };
initial = false;
}
}
private class OrderByInfo
{
public string PropertyName { get; set; }
public SortDirection Direction { get; set; }
public bool Initial { get; set; }
}
private enum SortDirection
{
Ascending = 0,
Descending = 1
}
}
The way I understand, you have a DateTime property (ley call it Date), and instead of regular sort
.OrderBy(x => x.Date)
having something like
var baseDate = DateTime.Today;
you want to sort the future values first in ascending order followed by the past values in descending order.
It can be achieved in the following generic way (works in LINQ to Objects as well as EF):
.OrderBy(x => x.Date >= baseDate ? x.Date : DateTime.MaxValue)
.ThenByDescending(x => x.Date >= baseDate ? DateTime.MinValue : x.Date)
To implement that dynamically, you could insert the following inside the implementation method body loop:
if (order.Type == typeof(DateTime)) // && some other special condition
{
var condition = Expression.GreaterThanOrEqual(
order, Expression.Constant(DateTime.Today));
var order1 = Expression.Condition(condition,
order, Expression.Constant(DateTime.MaxValue));
var order2 = Expression.Condition(condition,
Expression.Constant(DateTime.MinValue), order);
expression = CallOrderBy(expression,
Expression.Lambda(order1, parameter), SortDirection.Ascending, initial);
expression = CallOrderBy(expression,
Expression.Lambda(order2, parameter), SortDirection.Descending, false);
initial = false;
continue;
}
All is said in the title, more precisely I am searching a way to convert an
Expression<Func<TDerived, out bool>> to Expression<Func<TBase, out bool>>,
with TDerived deriving from TBase.
How can I achieve this?
Given an Expression replacer like this one:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
// A simple expression visitor to replace some nodes of an expression
// with some other nodes. Can be used with anything, not only with
// ParameterExpression
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
{
// Note that we should really clone from and to... But we will
// ignore this!
Replaces = replaces;
}
public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
{
Replaces = new Dictionary<Expression, Expression>();
using (var enu1 = from.GetEnumerator())
using (var enu2 = to.GetEnumerator())
{
while (true)
{
bool res1 = enu1.MoveNext();
bool res2 = enu2.MoveNext();
if (!res1 || !res2)
{
if (!res1 && !res2)
{
break;
}
if (!res1)
{
throw new ArgumentException("from shorter");
}
throw new ArgumentException("to shorter");
}
Replaces.Add(enu1.Current, enu2.Current);
}
}
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
now we can, given
public class Base
{
public int ValueBase { get; set; }
}
public class Derived : Base
{
public int ValueDerived { get; set; }
}
and a
Expression<Func<Derived, bool>> exp = x => x.ValueBase == 0;
then
ParameterExpression parOld = exp.Parameters[0];
ParameterExpression parNew = Expression.Parameter(typeof(Base));
// Replace the parOld with the parNew
Expression body2 = new SimpleExpressionReplacer(parOld, parNew).Visit(exp.Body);
// Note that we have to rebuild the Expression.Lambda<>
Expression<Func<Base, bool>> expNew = Expression.Lambda<Func<Base, bool>>(body2, parNew);
This will produce a
Expression<Func<Base, bool>> exp = x => x.ValueBase == 0;
Note that if you want instead to do:
Expression<Func<Derived, bool>> exp = x => x.ValueDerived == 0;
to
Expression<Func<Base, bool>> exp = x => ((Derived)x).ValueDerived == 0;
then you need something like:
ParameterExpression parOld = exp.Parameters[0];
ParameterExpression parNew = Expression.Parameter(typeof(Base));
UnaryExpression convert = Expression.Convert(parNew, typeof(Derived));
Expression body2 = new SimpleExpressionReplacer(parOld, convert).Visit(exp.Body);
Expression<Func<Base, bool>> expNew = Expression.Lambda<Func<Base, bool>>(body2, parNew);
You need to wrap the inner expression. Something like
var argument = Expression.Parameter(typeof(TDerived));
Expression.Lambda<Func<TDerived, bool>>
(
Expression.Invoke(innerExpression, argument),
argument
);
Of course, depending on the direction, you might need an explicit cast on the argument to innerExpression - this is quite simple, just use Expression.Cast.
EDIT:
To accomodate for your edit, the inverted variant:
var argument = Expression.Parameter(typeof(TBase));
Expression.Lambda<Func<TBase, bool>>
(
Expression.Invoke(innerExpression, Expression.Convert(argument, typeof(TDerived))),
argument
);
Note that this will obviously only work if the runtime type of the parameter is derived from TDerived.
I have created a generic search extension method for IQueryable that enables you to search for a single property to see if a search term is contained within it.
http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable
I now want to enable the user to select multiple properties to search within each, matching if any property contains the text.
The code:
The user enters the following code to perform this search:
string searchTerm = "Essex";
context.Clubs.Search(searchTerm, club => club.Name, club => club.County)
//Note: If possible I would rather something closer to the following syntax...
context.Clubs.Search(club => new[]{ club.Name, club.County}, searchTerm);
// ... or, even better, something similar to this...
context.Clubs.Search(club => new { club.Name, club.County}, searchTerm);
This will return any golf club with 'Essex' in the Name or as the County.
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
//Build parameters
var parameters = stringProperties.SelectMany(prop => prop.Parameters);
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
//Build or expression for each property
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
var methodCallExpression = Expression.Call(typeof(Queryable),
"Where",
new Type[] { source.ElementType },
source.Expression,
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters));
return source.Provider.CreateQuery<TSource>(methodCallExpression);
}
The error
If I change the number of parameters supplied to 1:
Expression.Lambda<Func<TSource, bool>>(orExpression, parameters.First()));
I get a new error:
UPDATE
I have written a post on the work discussed in this question. Check it out on GitHub too.
Here we go; you were pretty close - as I noted in comments, the key piece here is to use ExpressionVisitor to re-write the trees in terms of the single parameter you want to keep:
using System;
using System.Linq;
using System.Linq.Expressions;
static class Program
{
static void Main()
{
var data = new[] { new Foo { A = "x1", B = "y1", C = "y1" }, new Foo { A = "y2", B = "y2", C = "y2" },
new Foo { A = "y3", B = "y3", C = "x3" } }.AsQueryable();
var result = data.Search("x", x => x.A, x => x.B, x => x.C);
foreach (var row in result)
{
Console.WriteLine("{0}, {1}, {2}", row.A, row.B, row.C);
}
}
class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
public class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(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 Swap(Expression body, Expression from, Expression to)
{
return new SwapVisitor(from, to).Visit(body);
}
}
public static IQueryable<TSource> Search<TSource>(this IQueryable<TSource> source, string searchTerm, params Expression<Func<TSource, string>>[] stringProperties)
{
if (String.IsNullOrEmpty(searchTerm))
{
return source;
}
if (stringProperties.Length == 0) return source.Where(x => false);
// The lamda I would like to reproduce:
// source.Where(x => x.[property1].Contains(searchTerm)
// || x.[property2].Contains(searchTerm)
// || x.[property3].Contains(searchTerm)...)
//Create expression to represent x.[property1].Contains(searchTerm)
var searchTermExpression = Expression.Constant(searchTerm);
var param = stringProperties[0].Parameters.Single();
Expression orExpression = null;
//Build a contains expression for each property
foreach (var stringProperty in stringProperties)
{
// re-write the property using the param we want to keep
var body = SwapVisitor.Swap(stringProperty.Body, stringProperty.Parameters.Single(), param);
var checkContainsExpression = Expression.Call(
body, typeof(string).GetMethod("Contains"), searchTermExpression);
if (orExpression == null)
{
orExpression = checkContainsExpression;
}
else
{ // compose
orExpression = Expression.OrElse(orExpression, checkContainsExpression);
}
}
var lambda = Expression.Lambda<Func<TSource, bool>>(orExpression, param);
return source.Where(lambda);
}
}
I have this sql statement
SELECT userID from users WHERE
(name='name1' AND username='username1') OR
(name='name2' AND username='username2') OR
(name='name3' AND username='username3') OR
..........
(name='nameN' AND username='usernameN')
How can I implement this statement with entity framework using LINQ?
You can use a beautiful thing called PredicateBuilder. Use it like this
var pr = PredicateBuilder.False<User>();
foreach (var name in names)
{
pr = pr.Or(x => x.Name == name && x.Username == name);
}
return query.AsExpandable().Where(pr);
Expression<Func<User, bool>> whereExpression = null;
foreach (var name in names)
{
Expression<Func<User, bool>> e1 = u => u.Name == name;
Expression<Func<User, bool>> andExpression = e1.And(u => u.Username == name);
whereExpression = whereExpression == null
? andExpression
: whereExpression.Or(andExpression);
}
return query.Where(whereExpression);
This helper may help you.
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression)
{
if (leftExpression == null) return rightExpression;
if (rightExpression == null) return leftExpression;
var paramExpr = Expression.Parameter(typeof(T));
var exprBody = Expression.And(leftExpression.Body, rightExpression.Body);
exprBody = (BinaryExpression)new ParameterReplacer(paramExpr)
.Visit(exprBody);
return Expression.Lambda<Func<T, bool>>(exprBody, paramExpr);
}
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> leftExpression,
Expression<Func<T, bool>> rightExpression)
{
if (leftExpression == null) return rightExpression;
if (rightExpression == null) return leftExpression;
var paramExpr = Expression.Parameter(typeof(T));
var exprBody = Expression.Or(leftExpression.Body, rightExpression.Body);
exprBody = (BinaryExpression)new ParameterReplacer(paramExpr)
.Visit(exprBody);
return Expression.Lambda<Func<T, bool>>(exprBody, paramExpr);
}
}
and:
class ParameterReplacer : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(_parameter);
}
internal ParameterReplacer(ParameterExpression parameter)
{
_parameter = parameter;
}
}
NOTE: this is modified from something I have so it might not work out of the box. But it would be a good starting point.
public static IQueryable<TEntity> Where<TEntity>(
this IQueryable<TEntity> source,
IEnumerable<WhereSpecifier> orClauses) where TEntity : class
{
if (!orClauses.Any()) return source.Where(t => false);
Type type = typeof (TEntity);
ParameterExpression parameter = null;
Expression predicate = Expression.Constant(false, typeof (bool));
ParameterExpression whereEnt = Expression.Parameter(type, "WhereEnt");
foreach (WhereSpecifier orClause in orClauses)
{
Expression selector;
if (orClause.Selector != null) {
selector = orClause.Selector;
parameter = orClause.Parameter;
}
else
{
parameter = whereEnt;
Type selectorResultType;
selector = GenerateSelector<TEntity>(parameter, orClause.Column,
out selectorResultType);
}
Expression clause = selector.CallMethod(orClause.Method,
MakeConstant(selector.Type, orClause.Value), orClause.Modifiers);
predicate = Expression.Or(predicate, clause);
}
var lambda = Expression.Lambda(predicate, whereEnt);
var resultExp = Expression.Call(typeof (Queryable), "Where", new[] {type},
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<TEntity>(resultExp);
}
GenerateSelector:
public static Expression GenerateSelector<TEntity>(
ParameterExpression parameter, string propertyName,
out Type resultType) where TEntity : class
{
// create the selector part, but support child properties
PropertyInfo property;
Expression propertyAccess;
if (propertyName.Contains('.'))
{
// support to be sorted on child fields.
String[] childProperties = propertyName.Split('.');
property = typeof (TEntity).GetProperty(childProperties[0]);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
for (int i = 1; i < childProperties.Length; i++)
{
property = property.PropertyType.GetProperty(childProperties[i]);
propertyAccess = Expression
.MakeMemberAccess(propertyAccess, property);
}
}
else
{
property = typeof (TEntity).GetProperty(propertyName);
propertyAccess = Expression.MakeMemberAccess(parameter, property);
}
resultType = property.PropertyType;
return propertyAccess;
}
WHereSpecifier:
public class WhereSpecifier
{
public WhereSpecifier(string column, CheckMethod method, string value,
CheckMethodModifiers modifiers)
{
Modifiers = modifiers;
Value = value;
Column = column;
Method = method;
}
public WhereSpecifier(string column, CheckMethod method, string value)
: this(column, method, value, CheckMethodModifiers.None)
{
}
public Expression Selector { get; set; }
public ParameterExpression Parameter { get; set; }
public string Column { get; set; }
public CheckMethod Method { get; set; }
public CheckMethodModifiers Modifiers { get; set; }
public string Value { get; set; }
}
Usage:
var column = typeof(TEntity).Name + "ID";
var where = from id in SelectedIds
select new WhereSpecifier(column, CheckMethod.Equal, id.ToString());
return GetTable().Where(where);
I tried #Egor Pavlikhin solution but i got "The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.".
According to this you can use PredicateExtensions :
var predicate = PredicateExtensions.Begin<User>();
foreach (var name in names)
{
pr = pr.Or(x => x.Name == name);
}
return _context.Users.Where(predicate);
Don't forget that entity framework also understands entity sql, so you can do this part of the query in a string. Building a string up is pretty convenient when you have dynamic stuff you need to do.
I had to construct the predicate for the 'Where' clause dynamically based on User Interface selections. 'System.Dynamic.Linq' allows to predicates from strings.
foreach (var name in names)
{
query = query.Where("Name=#0 And UserName=#1", name, name);
}
return query;
'System.Dynamic.Linq' is available as a nuget package. Check out Scott Guthrie's introduction to the topic here.
i found this way it is too simple :
var query = context.InvoiceHeader.Where( i => i.DateInvoice >= model.datedu && i.DateInvoice <= model.dateau).AsQueryable();
if(model.name != null)
{
query = query.Where(i => i.InvoiceNum.Equals(model.name));
}
if (model.status != 0 )
{
query = query.Where(i => i.REF_InvoiceStatusRecId == model.status);
}
if (model.paiements != 0)
{
query = query.Where(i => i.REF_PaymentTermRecId == model.paiements);
}
query = query.AsQueryable().OrderByDescending(x => x.RecId);