I have a request query using linq. The query has multiple where clause say return list of items matching name and city.
Below is the piece of code I used for multiple where clause, but it returns empty set of items.
wherefield contains list of field names like name;city
wherefieldValue contains list of field values like james;delhi
var where = FilterLinq<T>.GetWherePredicate(wherefield, wherefieldvalue).Compile();
items = items.Where(where).OrderByDescending(a => a.GetType().GetProperty(field).GetValue(a, null)).Skip
public class FilterLinq<T>
{
public static Expression<Func<T, Boolean>> GetWherePredicate(string whereFieldList, string whereFieldValues)
{
//the 'IN' parameter for expression ie T=> condition
ParameterExpression pe = Expression.Parameter(typeof(T), typeof(T).Name);
//combine them with and 1=1 Like no expression
Expression combined = null;
if (whereFieldList != null)
{
string[] field = whereFieldList.Split(';');
string[] fieldValue = whereFieldValues.Split(';');
for (int i = 0; i < field.Count(); i++)
{
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, field[i]);
//the name constant to match
Expression columnValue = Expression.Constant(fieldValue[i]);
//the first expression: PatientantLastName = ?
Expression e1 = Expression.Equal(columnNameProperty, columnValue);
if (combined == null)
{
combined = e1;
}
else
{
combined = Expression.And(combined, e1);
}
}
}
//create and return the predicate
return Expression.Lambda<Func<T, Boolean>>(combined, new ParameterExpression[] { pe });
}
}
Your example works. Sure it works only for string properties, but i assume, that is your use case.
Perhaps it doesn't do what you want to achieve, becasue you combine your clause with and, but actually you do want to do it with Or, jus change this code line :
combined = Expression.And(combined, e1);
To
combined = Expression.Or(combined, e1);
Related
I'm using .Net Core and expression trees.
I have a Product class, where LstProp property contains list of values separated by ';', like "val1;val2;val3;":
public class Product
{
// actually contains list of values separated by ';'
public string LstProp{get; set;}
}
And I want to filter Products by this property and contains any condition using expression trees.
I've try this, but it doesn't work.
var value="val1;val2"
var productItem = Expression.Parameter(typeof(Product), "product");
var prop = Expression.Property(productItem, "LstProp");
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var values = value.ToString().Split(';');
Expression predicate = null;
foreach (var val in values)
{
var listPropExpression = Expression.Constant(val);
var containsExpresion=Expression.Call(listPropExpression, method, property);
predicate = predicate != null ? Expression.Or(predicate, containsExpresion) : containsExpresion;
}
So I'm trying to combine call of Contains function for each value in the list, but getting error about "No conversion between BinaryExpression and MethodCallExpression".
How could I combine multiple method calls with expression trees?
I have found solution.
I got two problems:
Incorrect order of parameters.
Should be Expression.Call(property, method, listPropExpression); instead Expression.Call(listPropExpression, method, property);
Main problem was solved with simple cast:
predicate = predicate != null ? (Expression) Expression.Or(predicate, containsExpresion) : containsExpresion;
As a Result I get expression like product=>product.LstProp.Contains("val1") Or product.LstProp.Contains("val2")
I have the search function. and I want to select all value in my Book table which contain _searchdata but I dont know how to express at "where" with the short code instead of listing all items of Table like this:
(I just get some items for example, it contains about 100 items like Booktitle, Author, Genre... i dont want to specify it because it's so long)
public void SearchAny(string _searchdata)
{
var searchAnyInDB = from Book x in BookDB.Books
where (x.BookTitle.Contains(_searchdata)
|| x.Author.Contains(_searchdata)
|| x.Genre.Contains(_searchdata))
select x;
DataSearch.Clear();
DataSearch = new ObservableCollection<Book>(searchAnyInDB);
}
Because LINQ to Entities and LINQ to SQL both use Expression<Func<TSource, bool>> as IQueryable.Where extension method parameter, you can use reflection to create that Expression during compile type and generate all there || instead of typing them into your source code.
Would be something like:
var searchAnyInDB = from Book x in BookDB.Books
where (GetWhereExpression<Book>(_searchdata))
select x;
And GetWhereExpression<TSource> method:
static Expression<Func<TSource, bool>> GetWhereExpression<TSource>(string value)
{
var param = Expression.Parameter(typeof(TSource));
var val = Expression.Constant(value);
var expression = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
foreach(var prop in typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if(prop.PropertyType == typeof(string))
{
expression = Expression.OrElse(expression,
Expression.Call(
Expression.Property(param, prop),
"Contains",
null,
val
)
);
}
}
return Expression.Lambda<Func<TSource, bool>>(expression, param);
}
You still can do better then that, ex. remembering the expression for type to prevent using reflection every time you need to execute the query with different search texts, etc. But it should give you an idea where to go.
I want to create a dynamic linq expression for sql IN clause in EF 6.0 with code first approch. Note that i am new to Expressions. What i want to achive is
select * from Courses where CourseId in (1, 2, 3, 4)
//CourseId is integer
The normal linq query looks like this. But i want to query it dynamically
string[] ids = new string[]{"1", "2", "3", "4"};
var courselist = DBEntities.Courses.Where(c => ids.Contains(SqlFunctions.StringConvert((decimal?)c.CourseId)))
There are two ways to make dynamic expression.
1) one ways is to loop through ids and make expressions
The below code will create the following expression in debug view
{f => ((StringConvert(Convert(f.CourseId)).Equals("23") Or StringConvert(Convert(f.CourseId)).Equals("2")) Or StringConvert(Convert(f.CourseId)).Equals("1"))}
Dynamic Expression is
var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId");
MethodInfo mi = null;
MethodCallExpression mce = null;
if (property.Type == typeof(int))
{
var castProperty = Expression.Convert(property, typeof(double?));
var t = Expression.Parameter(typeof(SqlFunctions), "SqlFunctions");
mi = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(double?) });
mce = Expression.Call(null,mi, castProperty);
}
mi = typeof(string).GetMethod("Equals", new Type[]{ typeof(string)});
BinaryExpression bex = null;
if (values.Length <= 1)
{
return Expression.Lambda<Func<T, bool>>(Expression.Call(mce, mi, Expression.Constant(values[0]), param));
}
//var exp1 = Expression.Call(mce, mi, Expression.Constant(values[0]));
for (int i = 0; i < values.Length; i++)
{
if (bex == null)
{
bex = Expression.Or(Expression.Call(mce, mi, Expression.Constant(values[i])), Expression.Call(mce, mi, Expression.Constant(values[i + 1])));
i++;
}
else
bex = Expression.Or(bex, Expression.Call(mce, mi, Expression.Constant(values[i])));
}//End of for loop
return Expression.Lambda<Func<T, bool>>(bex, param);
2) The 2nd way that i tried (debug view)
{f => val.Contains("23")} //val is parameter of values above
The dynamic expression for above that i tried is
var param = Expression.Parameters(typeof(Course), "f")
MemberExpression property = Expression.PropertyOrField(param, "CourseId");
var micontain = typeof(Enumerable).GetMethods().Where(m => m.Name == "Contains" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(string));
var mc = Expression.Call(micontain, Expression.Parameter(values.GetType(), "val"), Expression.Constant("2"));//NOTE: I haven't use CourseId for now as i am getting conversion error
return Expression.Lambda<Func<T, bool>>(mc, param);
I get the following errors
LINQ to Entities does not recognize the method 'System.String StringConvert(System.Nullable`1[System.Double])' method, and this
method cannot be translated into a store expression when i use the
first methodology. I know i can't use ToString with EF thats why I used SqlFunctions but it is not working for me.
The parameter 'val' was not bound in the specified LINQ to Entities query expression using 2nd methodology
I am trying this from last 4 days. I googled it but didn't find any suitable solution. Please help me.
After a lot of struggle I found solution to my question.
I want to achieve this sql query
select * from Courses where CourseId in (1, 2, 3, 4)
Using Linq to Entities, but I want to pass in(1,2,3,4) list dynamically to linq query. I created an Extension class for that purpose.
public static class LinqExtensions
{
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> In<T, TValue>(this Expression<Func<T, bool>> predicate,string propertyName, List<TValue> values)
{
var param = predicate.Parameters.Single();
MemberExpression property = Expression.PropertyOrField(param, propertyName);
var micontain = typeof(List<TValue>).GetMethod("Contains");
var mc = Expression.Call(Expression.Constant(values), micontain, property);
return Expression.Lambda<Func<T, bool>>(mc, param);
}
}
Use of LinqExtensions
var pred = LinqExtensions.False<Course>(); //You can chain In function like LinqExtensions.False<Course>().In<Course, int>("CourseId", inList);
var inList= new List<int>(){1, 2, 3}; //Keep in mind the list must be of same type of the Property that will be compared with. In my case CourseId is integer so the in List have integer values
pred =pred.In<Course, int>("CourseId", inList); //TValue is int. As CourseId is of type int.
var data = MyEntities.Courses.Where(pred);
I hope this might be beneficial for some one
have you seen the type of
var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId)))
above statement would not return actual list of courses. The query is not executed yet. It just returns IQuereable. The query is executed when you actually call .ToList() method on it
so, your solution is..
Create array of IDs using for loop and then simply run the below query
var courselist = DBEntities.Courses.Where(c => ids.Contains(c.CourseId))).ToList()
So, I'm getting the error that's in the title. I'll jump straight into the relevant code. First, the manual creation of the where statement (which does work) and then the dynamic creation which does not work. Both create the same queryExpression.
First, the queryExpression that is generated with both methods:
{Table(Products).Select
(s => new SalesData() {
ProductID = s.ProductID,
StoreName = s.StoreName,
Count = s.Count
}).Where(s => (s.Count > 500))}
Now, the manual method that does work:
IQueryable<SalesData> data = ( from s in sales.Products
select new SalesData
{
ProductID = s.ProductID,
StoreName = s.StoreName,
Count = s.Count
} ).AsQueryable<SalesData>();
data = data.Where(s => s.Count > 500);
this.dataGridView1.DataSource = null;
this.dataGridView1.DataSource = (from d in data
select new
{
d.ProductID,
d.StoreName,
d.Count
} );
This works as expected; my DataGridView is populated with the data.
Now, to create the where clause dynamically:
IQueryable<SalesData> data = ( from s in sales.Products
select new SalesData
{
ProductID = s.ProductID,
StoreName = s.StoreName,
Count = s.Count
} ).AsQueryable<SalesData>();
if (this.filter.Predicate != null)
{
Expression<Func<SalesData, bool>> lambda =
Expression.Lambda<Func<SalesData, bool>>(this.filter.Predicate,
new ParameterExpression[] { this.filter.PE });
data = data.Where(lambda);
}
this.dataGridView1.DataSource = null;
this.dataGridView1.DataSource = (from d in data <---- fails here
select new
{
d.ProductID,
d.StoreName,
d.Count
} );
The only thing that is different is that in the second snippet I'm creating the lambda expression dynamically. I've noted where it fails in the second snippet.
Using the debugger I can see the queryExpression for data and it is the same with both methods, so I don't think my problem is with my actual expression creation. If that code is needed I can post it here.
My question is, what am I doing wrong and how do I fix it?
Edit:
Here is the function that gives filter.Predicate it's value:
public static Expression GetPredicate(List<FilterItem> itemList, ParameterExpression pe)
{
List<Expression> expressions = new List<Expression>();
List<string> combiners = new List<string>();
foreach (FilterItem item in itemList)
{
Expression left = Expression.PropertyOrField(pe, item.Field);
Expression right = Expression.Constant(Convert.ToInt32(item.Value), typeof(int));
expressions.Add(Expression.GreaterThan(left, right));
combiners.Add(item.Combiner);
}
int expressionCount = expressions.Count();
Expression predicateBody = expressions[0];
if (expressionCount > 1)
{
for (int x = 1; x <= expressionCount; x++)
{
switch (combiners[x - 1])
{
case "AND":
predicateBody = Expression.And(predicateBody, expressions[x]);
break;
case "OR":
predicateBody = Expression.Or(predicateBody, expressions[x]);
break;
default:
break;
}
}
}
return predicateBody;
}
Inside filter I have:
this.Predicate = BuildPredicate.GetPredicate(this.filterList, this.PE);
this.PE is:
public ParameterExpression PE
{
get
{
return Expression.Parameter(typeof(SalesData), "s");
}
}
From the father of C#, Anders Hejlsberg:
Parameters are referenced in expressions through their object identity, not by comparing their names. In fact, from an expression tree's point of view, the name of a parameter is purely informational. The reason for this design is the same reason that types are referenced through their System.Type objects and not their names--expression trees are fully bound and are not in the business of implementing name lookup rules (which may differ from language to language).
From MSDN Forums
The short and long of it is that filter.Predicate has a value of {(s.Count > 500)} but this doesn't mean that s has meaning here by sharing a name with the initial LINQ expression.
I was able to solve my problem thanks to the dialog on this page and using Predicate Builder.
A better method for what I'm trying to do can be found at Scott Gu's site. This is better for my purposes because I need to determine the field and the value dynamically, plus allow for grouping (easier with this method).
I've been looking on google but not finding anything that does the trick for me.
as you know SQL has a "where x in (1,2,3)" clause which allows you to check against multiple values.
I'm using linq but I can't seem to find a piece of syntax that does the same as the above statement.
I have a collection of category id's (List) against which I would like to check
I found something that uses the .contains method but it doesn't even build.
You have to use the Contains method on your id list:
var query = from t in db.Table
where idList.Contains(t.Id)
select t;
The syntax is below:
IEnumerable<int> categoryIds = yourListOfIds;
var categories = _dataContext.Categories.Where(c => categoryIds.Contains(c.CategoryId));
The key thing to note is that you do the contains on your list of ids - not on the object you would apply the in to if you were writing sql.
Here's an article illustrating the approach. You should indeed use the Contains method over your collection which will be translated into IN clause.
Here is my realization of WhereIn() Method, to filter IQueryable collection by a set of selected entities:
public static IQueryable<T> WhereIn<T,TProp>(this IQueryable<T> source, Expression<Func<T,TProp>> memberExpr, IEnumerable<TProp> values) where T : class
{
Expression predicate = null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
bool IsFirst = true;
MemberExpression me = (MemberExpression) memberExpr.Body;
foreach (TProp val in values)
{
ConstantExpression ce = Expression.Constant(val);
Expression comparison = Expression.Equal(me, ce);
if (IsFirst)
{
predicate = comparison;
IsFirst = false;
}
else
{
predicate = Expression.Or(predicate, comparison);
}
}
return predicate != null
? source.Where(Expression.Lambda<Func<T, bool>>(predicate, param)).AsQueryable<T>()
: source;
}
And calling of this method looks like:
IQueryable<Product> q = context.Products.ToList();
var SelectedProducts = new List<Product>
{
new Product{Id=23},
new Product{Id=56}
};
...
// Collecting set of product id's
var selectedProductsIds = SelectedProducts.Select(p => p.Id).ToList();
// Filtering products
q = q.WhereIn(c => c.Product.Id, selectedProductsIds);