I have filters in my asp.net project and want to add expression to this list with condition:
Expression<Func<MyModel, bool>> func;
var list = new List<Expression<Func<MyModel, bool>>>();
I want to conditionally apply Where (OR between them). for example:
if(sth){
func = p => p.a <= b;
list.Add(func);
}
if (sth else){
func = p => p.c >= d;
list.Add(func);
}
var fq = Session.Query<MyModel>();
fq = list.Aggregate(fq, (current, expression) => current.Where(expression));
How can I do this?
You can build an extension method to merge two condition expressions using OR relationship like this:
public static class Extensions
{
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var parameter = one.Parameters[0];
var visitor = new ReplaceParameterVisitor(parameter);
another = (Expression<Func<T, bool>>)visitor.Visit(another);
var body = Expression.Or(one.Body, another.Body);
return Expression.Lambda<Func<T, bool>>(body, parameter);
}
}
class ReplaceParameterVisitor : ExpressionVisitor
{
public ParameterExpression NewParameter { get; private set; }
public ReplaceParameterVisitor(ParameterExpression newParameter)
{
this.NewParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return this.NewParameter;
}
}
Usage and test code:
Expression<Func<int, bool>> condition1 = x => x > 8;
Expression<Func<int, bool>> condition2 = y => y < 3;
var condition = condition1.Or(condition2);
var result = Enumerable
.Range(1, 10)
.Where(condition.Compile())
.ToList(); //1,2,9,10
Looks like you could do this easily with an extension method
public static class EnumerableExtensions
{
public static IEnumerable<T> ConditionalWhere<T>(this IEnumerable<T> list,
bool condition, Func<T,bool> predicate)
{
if(!condition)
return list;
return list.Where(predicate);
}
}
Usage would be:
var fq = Session.Query<MyModel>();
var result = fq.ConditionalWhere(sth, p => p.a <= b)
.ConditionalWhere(sth_else, p => p.c >= d);
I built a bit of code to showcase Predicate<> while trying to stick to your program structure:
using System;
using System.Collections.Generic;
using System.Linq;
namespace SOTests
{
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
private static int ControlId;
private static string ControlName;
static void Main(string[] args)
{
var idPred = new Predicate<MyModel>(m => m.Id > ControlId);
var namePred = new Predicate<MyModel>(m => m.Name == ControlName);
var list = new List<MyModel>();
if (true) // TODO: do id check?
{
list = list.Where(m => idPred.Invoke(m)).ToList();
}
if (true) // TODO: do name check?
{
list = list.Where(m => namePred.Invoke(m)).ToList();
}
//var fq = Session.Query<MyModel>();
//fq = list;
}
}
}
I commented out the Session bit not knowing what kind of storage abstraction it represents (and the code wouldn't compile).
The code should explain itself and it's not tested.
It can be much more elegant, but you should state more clearly what your requirements are for that.
Thanks all, I found the solution : OrElse
Expression<Func<MyModel, bool>> OrExpressionFunction(Expression<Func<MyModel, bool>> exp1, Expression<Func<MyModel, bool>> exp2)
{
ParameterExpression p = exp1.Parameters.Single();
return Expression.Lambda<Func<MyModel, bool>>(
Expression.OrElse(exp1.Body, exp2.Body), p);
}
then:
Expression<Func<MyModel, bool>> MyExp = null;
if(sth){
func = p => p.a <= b;
MyExp= OrExpressionFunction(func, MyExp);
}
if (sth else){
func = p => p.c >= d;
MyExp= OrExpressionFunction(func, MyExp);
}
list.Add(MyExp);
Related
i'm trying to refactor some code that is going to be similar in several areas. This piece of code is from a validator using fluentvalidation. I'm trying to transform the HaveUniqueNumber routine to be generic and pass in lambdas that will be used in the db query.
public class AddRequestValidator : AbstractValidator<AddRequest>
{
public AddRequestValidator()
{
RuleFor(x => x)
.Must(x => HaveUniqueNumber(x.myNumber, x.parentId))
.WithMessage("myNumber '{0}' already exists for parentId '{1}'.", x => x.myNumber, x=>x.parentId);
}
private bool HaveUniqueNumber(string number, int parentId)
{
myModel existingNumber = null;
using (var context = new myContextDb())
{
existingNumber = context.myModel.SingleOrDefault(a => a.MyNumber == number && a.ParentId == parentId);
}
return existingNumber == null;
}
}
i tried implementing something like :
public class AddRequestValidator : AbstractValidator<AddRequest>
{
public AddRequestValidator()
{
RuleFor(x => x)
.Must(x => HaveUniqueNumber<myModel>(an => an.myNumber == x.MyNumber, an => an.parentId == x.parentId))
.WithMessage("myNumber '{0}' already exists for parentId '{1}'.", x => x.myNumber, x=>x.parentId);
}
private bool HaveUniqueNumber<T>(Expression<Func<T, bool>> numberExpression, Expression<Func<T, bool>> parentExpression)
{
T existingNumber = default(T);
using (var context = new myContextDb())
{
existingNumber = context.T.SingleOrDefault(numberExpression && parentExpression);
}
return existingNumber == null;
}
}
how can i get an something similar to the original to work.
Needed to fix the following:
context.T to context.DbSet<T>()
added the ExpressionVisitor Class as referenced by the link posted by Alexei Levenkov. 457316
Added where T: class to HaveUniqueMethod
ended up with this refactored class:
private bool HaveUniqueNumber<T>(Expression<Func<T, bool>> numberExpression, Expression<Func<T, bool>> parentExpression) where T : class
{
T existingNumber = default(T);
using (var context = new myContextDb())
{
existingNumber = context.Set<T>().SingleOrDefault(numberExpression.AndAlso(parentExpression));
}
return existingNumber == null;
}
and added this extension method:
public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}
private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;
public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}
I want to declare and reuse Expression with filter by variable y. In a method I have something like following:
Expression<Func<Item, int, bool>> exFilter = (x, y) => x.Item.Id == y;
Further on, in a code I'm trying to use declared expression (exFilter)
return context.Item.Select(x => new { data = exFilter.Where(exFilter))
Q: How do I pass parameter to the exFilter? I want to do select filtered by every item in a list(x).
This is just a sample what I'm trying to figure out. The problem and query is much bigger and complicated.
You can use LinqKit to reuse the expression that you have. Here is an example:
var result =
context.Item //The DbSet
.AsExpandable() //This method is defined in LinqKit and allows for expression expansion
.Where(x => exFilter.Invoke(x, 2)) //LinqKit will know how to translate this into an expression
.ToList();
I am using the value 2 here as an example.
You can rewrite you code like this:
Expression<Func<Item, bool>> exFilter(int y){ return (x) => x.item.Id == y;}
And use it like this:
int paramY = 456;
return context.Item.Select(exFilter(paramY))
You can try something like this:
public class Item
{
public Item(String str, Int32 #int)
{
this.StrValue = str;
this.IntValue = #int;
}
public String StrValue { get; }
public Int32 IntValue { get; }
public override string ToString() =>
$"{this.IntValue} = '{this.StrValue}'";
}
public static class ExpressionExtensions
{
public static Expression<Func<TItem, TResult>> Curry<TItem, TCurry, TResult>(
this Expression<Func<TItem, TCurry, TResult>> function,
TCurry value)
{
if (function == null)
throw new ArgumentNullException(paramName: nameof(function));
var itemParameter = Expression.Parameter(typeof(TItem));
var valueConstant = Expression.Constant(value);
return Expression.Lambda<Func<TItem, TResult>>(
Expression.Invoke(
function,
new Expression[]
{
itemParameter,
valueConstant
}),
new[] { itemParameter });
}
}
...
var items = new[]
{
new Item("one", 1),
new Item("two", 2),
new Item("two again", 2),
};
Expression<Func<Item, Int32, Boolean>> predicate = (item, intValue) =>
item.IntValue == intValue;
var curriedPredicate = predicate.Curry(2);
var filtered = items
.AsQueryable<Item>()
.Where(curriedPredicate)
.ToArray();
foreach (var item in filtered)
{
Console.WriteLine(item);
}
I need to translate this simple SQL query into LINQ using EF entities:
SELECT * FROM Clientes WHERE ID_Cliente IN(SELECT ID_Objeto FROM
Direcciones where ID_TipoDireccion=IDTipoDireccion)
Seems very simple, but it seems to be a very hard to achieve. I try this:
public List<Clientes> EnumEntity(int IDTipoDireccion)
{
var dir = new DireccionesRepository(ref rep.Context);
var misClientes = rep.ListEntity();
var misDirColection = dir.ListEntity().ToList().Where(o => o.ID_TipoDireccion == IDTipoDireccion);
foreach (var item in misDirColection)
{
misClientes=misClientes.Where(p => p.ID_Cliente == item.ID_Objeto);
}
return misClientes.ToList();
}
The problem with above query is that use AND. I need it to use OR to include all clients that matches Direcciones object.
I try PredicateBuilder class but it doesn't support EF. I found this adaptation of PredicateBuilder that seems to resolve this problem:
internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor
{
public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
Expression newValue;
if (subst.TryGetValue(node, out newValue))
{
return newValue;
}
return node;
}
}
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>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
{
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
{
ParameterExpression p = a.Parameters[0];
SubstExpressionVisitor visitor = new SubstExpressionVisitor();
visitor.subst[b.Parameters[0]] = p;
Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}
But the problem is I don't know how to use it in my case.
Can anybody help me?
Thanks!
EDIT
After adapting the suggested code, this is the result in case someone else need it:
public List<EnumeradorWCFModel> EnumEntity(int IDTipoDireccion)
{
// SELECT * FROM Clientes WHERE ID_Cliente IN(SELECT ID_Objeto FROM Direcciones where ID_TipoDireccion=IDTipoDireccion)
// Get All directions of type IDTipoDireccion
var dir = new DireccionesRepository(ref rep.Context);
var misDirColection = dir.ListEntity().Where(x => x.ID_TipoDireccion == IDTipoDireccion)
.Select(x => x.ID_Objeto); // Do not iterate!
// Itinerate Clients
var misClientes = rep.ListEntity().Where(x => misDirColection.Contains(x.ID_Cliente)).ToList();
return misClientes.ToList();
}
Use Contains().
SELECT * FROM Clientes WHERE ID_Cliente IN(SELECT ID_Objeto FROM Direcciones where ID_TipoDireccion=IDTipoDireccion)
Can be directly translated to:
int IDTipoDireccion = ...
// First, create a query for your subselect.
var direcciones = dbContext.Direcciones
.Where(x => x.ID_TipoDireccion == IDTipoDireccion);
.Select(x => x.ID_Objeto); // Do not iterate!
// Then, use the first query in the WHERE of your second query.
var results = dbContext.Clientes
.Where(x => direcciones.Contains(x.ID_Cliente))
.ToList();
I have an entity class like
public class BookPage {
public int PageIndex { get; set; }
}
then I have an expression:
Expression<Func<int, bool>> pageIndexCondition = idx => idx == 1;
and the expression I want:
Expression<Func<BookPage, bool>> pageCondition = bookPage => bookPage.PageIndex == 1;
The question: How do I use pageIndexCondition to do LINQ-to-SQL query, or how can I convert pageIndexCondition into pageCondition?
Edit: Another solution that would be less elegant, but still meet my requirement is:
Expression<Func<T, bool>> GetPageIndexCondition(Expression<Func<T, int>> selector) {
return (T item) => selector(item) < 10; // This won't work because selector is Expression, so how to implement this correctly?
}
...
var pageCondition = GetPageIndexCondition(page => page.PageIndex);
I like doing these things, though as others have said, there's probably more efficient and better ways to do it:
void Main()
{
Expression<Func<int, bool>> pageIndexCondition = idx => idx == 1;
Expression<Func<BookPage, bool>> converted = ExpressionConverter.Convert(pageIndexCondition);
}
public class ExpressionConverter : ExpressionVisitor
{
public static Expression<Func<BookPage, bool>> Convert(Expression<Func<int, bool>> e)
{
var oldParameter = e.Parameters.First();
var newParameter = Expression.Parameter(typeof(BookPage), "bp");
Expression<Func<BookPage, int>> x = (BookPage bp) => bp.PageIndex;
var property = ((x.Body as MemberExpression).Member as PropertyInfo);
var memberAccess = Expression.Property(newParameter, property);
var converter = new ExpressionConverter(oldParameter, memberAccess);
return (Expression<Func<BookPage, bool>>)Expression.Lambda(converter.Visit(e.Body), newParameter);
}
private ParameterExpression pe;
private Expression replacement;
public ExpressionConverter(ParameterExpression pe, Expression replacement)
{
this.pe = pe;
this.replacement = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if(node == pe)
return replacement;
return base.VisitParameter(node);
}
}
var pages = new List<BookPage>
{
new BookPage { PageIndex = 1 },
new BookPage { PageIndex = 2 }
};
Expression<Func<BookPage, bool>> pageCondition = bookPage => bookPage.PageIndex == 1;
BookPage result = pages.AsQueryable().Single(pageCondition);
If you want a generic select by id you will have to do something like;
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null)
{
if (filter != null)
{
query = query.Where(filter);
}
}
This goes in your generic repository.
I'm building a LINQ query dynamically with this code.
It seems to work, but when i have more than one searchString in my search, (so when multiple expressions are added, i get the following error:
Variable 'p' of type referenced from scope, but it is not defined**
I guess i can only define /use p once. But, if so, i need to alter my code a bit. Can anyone point me in the right direction here?
if (searchStrings != null)
{
foreach (string searchString in searchStrings)
{
Expression<Func<Product, bool>> containsExpression = p => p.Name.Contains(searchString);
filterExpressions.Add(containsExpression);
}
}
Func<Expression, Expression, BinaryExpression>[] operators = new Func<Expression, Expression, BinaryExpression>[] { Expression.AndAlso };
Expression<Func<Product, bool>> filters = this.CombinePredicates<Product>(filterExpressions, operators);
IQueryable<Product> query = cachedProductList.AsQueryable().Where(filters);
query.Take(itemLimit).ToList(); << **error when the query executes**
public Expression<Func<T, bool>> CombinePredicates<T>(IList<Expression<Func<T, bool>>> predicateExpressions, Func<Expression, Expression, BinaryExpression> logicalFunction)
{
Expression<Func<T, bool>> filter = null;
if (predicateExpressions.Count > 0)
{
Expression<Func<T, bool>> firstPredicate = predicateExpressions[0];
Expression body = firstPredicate.Body;
for (int i = 1; i < predicateExpressions.Count; i++)
{
body = logicalFunction(body, predicateExpressions[i].Body);
}
filter = Expression.Lambda<Func<T, bool>>(body, firstPredicate.Parameters);
}
return filter;
}
Simplifying, here are several lines which you are trying to do (I use string instead Product etc, but idea is the same):
Expression<Func<string, bool>> c1 = x => x.Contains("111");
Expression<Func<string, bool>> c2 = y => y.Contains("222");
var sum = Expression.AndAlso(c1.Body, c2.Body);
var sumExpr = Expression.Lambda(sum, c1.Parameters);
sumExpr.Compile(); // exception here
Please notice how I expanded your foreach into two expressions with x and y - this is exactly how it looks like for compiler, that are different parameters.
In other words, you are trying to do something like this:
x => x.Contains("...") && y.Contains("...");
and compiler wondering what is that 'y' variable??
To fix it, we need to use exactly the same parameter (not just name, but also reference) for all expressions. We can fix this simplified code like this:
Expression<Func<string, bool>> c1 = x => x.Contains("111");
Expression<Func<string, bool>> c2 = y => y.Contains("222");
var sum = Expression.AndAlso(c1.Body, Expression.Invoke(c2, c1.Parameters[0])); // here is the magic
var sumExpr = Expression.Lambda(sum, c1.Parameters);
sumExpr.Compile(); //ok
So, fixing you original code would be like:
internal static class Program
{
public class Product
{
public string Name;
}
private static void Main(string[] args)
{
var searchStrings = new[] { "111", "222" };
var cachedProductList = new List<Product>
{
new Product{Name = "111 should not match"},
new Product{Name = "222 should not match"},
new Product{Name = "111 222 should match"},
};
var filterExpressions = new List<Expression<Func<Product, bool>>>();
foreach (string searchString in searchStrings)
{
Expression<Func<Product, bool>> containsExpression = x => x.Name.Contains(searchString); // NOT GOOD
filterExpressions.Add(containsExpression);
}
var filters = CombinePredicates<Product>(filterExpressions, Expression.AndAlso);
var query = cachedProductList.AsQueryable().Where(filters);
var list = query.Take(10).ToList();
foreach (var product in list)
{
Console.WriteLine(product.Name);
}
}
public static Expression<Func<T, bool>> CombinePredicates<T>(IList<Expression<Func<T, bool>>> predicateExpressions, Func<Expression, Expression, BinaryExpression> logicalFunction)
{
Expression<Func<T, bool>> filter = null;
if (predicateExpressions.Count > 0)
{
var firstPredicate = predicateExpressions[0];
Expression body = firstPredicate.Body;
for (int i = 1; i < predicateExpressions.Count; i++)
{
body = logicalFunction(body, Expression.Invoke(predicateExpressions[i], firstPredicate.Parameters));
}
filter = Expression.Lambda<Func<T, bool>>(body, firstPredicate.Parameters);
}
return filter;
}
}
But notice the output:
222 should not match
111 222 should match
Not something you may expect.. This is result of using searchString in foreach, which should be rewritten in the following way:
...
foreach (string searchString in searchStrings)
{
var name = searchString;
Expression<Func<Product, bool>> containsExpression = x => x.Name.Contains(name);
filterExpressions.Add(containsExpression);
}
...
And here is output:
111 222 should match
IMHO, no need to make the list:
var filterExpressions = new List<Expression<Func<Product, bool>>>()
You may easily live with the following in Visitor class:
public class FilterConverter : IFilterConverterVisitor<Filter> {
private LambdaExpression ConditionClausePredicate { get; set; }
private ParameterExpression Parameter { get; set; }
public void Visit(Filter filter) {
if (filter == null) {
return;
}
if (this.Parameter == null) {
this.Parameter = Expression.Parameter(filter.BaseType, "x");
}
ConditionClausePredicate = And(filter);
}
public Delegate GetConditionClause() {
if (ConditionClausePredicate != null) {
return ConditionClausePredicate.Compile();
}
return null;
}
private LambdaExpression And(Filter filter) {
if (filter.BaseType == null || string.IsNullOrWhiteSpace(filter.FlattenPropertyName)) {
//Something is wrong, passing by current filter
return ConditionClausePredicate;
}
var conditionType = filter.GetCondition();
var propertyExpression = filter.BaseType.GetFlattenPropertyExpression(filter.FlattenPropertyName, this.Parameter);
switch (conditionType) {
case FilterCondition.Equal: {
var matchValue = TypeDescriptor.GetConverter(propertyExpression.ReturnType).ConvertFromString(filter.Match);
var propertyValue = Expression.Constant(matchValue, propertyExpression.ReturnType);
var equalExpression = Expression.Equal(propertyExpression.Body, propertyValue);
if (ConditionClausePredicate == null) {
ConditionClausePredicate = Expression.Lambda(equalExpression, this.Parameter);
} else {
ConditionClausePredicate = Expression.Lambda(Expression.And(ConditionClausePredicate.Body, equalExpression), this.Parameter);
}
break;
}
// and so on...
}
}
The code is not optimal, I know, I'm a beginner and a lot of everything to be implemented... But this stuff does work. The idea is to have the only ParameterExpression per Visitor class, then to construct expressions using this parameter. After, just concatenate all expressions per one LambdaExpression clause and compile to delegate, when needed.