I'm using PredicateBuilder to create a dynamic Where clause to query data from a DataTable. I have a Dictionary that contains my column names and values I need to search for. I'm simply iterating over the dictionary, if the key matches a column name, then add that key and value to the predicate. Everything seems to work fine until the actual query is run against the datatable, I get zero records back:( But if I replace the dynamic predicate with something like p => p["Year"] == "2010", I get records back. Here's the code:
var objectList = table.AsEnumerable();
Func<DataRow, bool> predicate = GetPredicate(parms, table.Columns);
var list1 = objectList.Where(predicate).ToList();
private static Func<DataRow, bool> GetPredicate(Dictionary <string, string> parms, DataColumnCollection dataColumnCollection)
{
var predicate = PredicateBuilder.False<DataRow>();
foreach (var parm in parms)
{
if (dataColumnCollection.Contains(parm.Key))
{
var copy = parm;
predicate = predicate.And(p => p[copy.Key] == copy.Value);
}
}
return predicate.Compile();
}
Any help would be greatly appreciated:)
You're starting with false and then adding and clauses.
false && ... always returns false, so your Where clause will never match anything.
Try starting with:
var predicate = PredicateBuilder.True<DataRow>();
ALSO:
You are closing over a loop variable, which means all your predicates will use the last parm in parms.
You can fix this by creating a local copy in your loop:
foreach( var parm in parms )
{
if (dataColumnCollection.Contains(parm.Key))
{
var copy = parm;
predicate = predicate.And( p => p[ copy.Key ] == copy.Value );
}
UPDATE:
DataRow[ key ] is of type object, so in p[ copy.Key ] == copy.Value, the equality operator is object reference equality, not string equality.
You can fix this by specifying String.Equals:
predicate = predicate.And( p => String.Equals( p[ copy.Key ], copy.Value ) );
Interestingly, this example shows that you can have multiple instances of string with the same contents.
Related
My plan is to create a query, but the parameters based on a Dictionary.
The Dictionary contains string key and bool value.
Can be 2 or 3 or more items in the dictionary.
Dictionary<string, bool> items = new Dictionary<string, bool>();
items.Add("CostFree", true);
items.Add("Visible", true);
items.Add("Closed", true);
This is the dictionary I am sending and based on this I want to create dynamically a query like
.Where(e => e.CostFree == true || Visible == true || Closed == true)
but the dictionary can contain 2, 3 or four items.
How can I solve this ?
Thanks in advance
LINQ expressions can be built easily via static methods exposed on System.Linq.Expressions.Expression class.
Here is a sample with your needs assuming the entity you are building the expression against named SomeClass
[TestMethod]
public void MyTestMethod()
{
var testData = new List<SomeClass>()
{
new SomeClass() {Id=1, CostFree = false, Closed='N', Visible=false},
new SomeClass() {Id=2, CostFree = true, Closed='N', Visible=false}, // expect only this one matching
};
var items = new Dictionary<string, object>();
items.Add("CostFree", true);
items.Add("Visible", true);
items.Add("Closed", 'Y');
// this one will be the "e" in "e => e.CostFree == true || Visible == true || Closed == 'Y'"
var paramExpression = Expression.Parameter(typeof(SomeClass));
// lets construct the body ("e.CostFree == true || Visible == true || Closed == 'Y'") part step-by-step
// the parts consists of binary "equals" expressions combined via logical "or" expression
var bodyExpression = (Expression)null;
foreach(var kvp in items)
{
// get the named property ("CostFree", ...) reference of paramExpression. this is the left hand side of "equals"
var propertyExpression = Expression.PropertyOrField(paramExpression, kvp.Key);
// get the constant with appropriate value to place on right hand side of "equals"
var constantExpression = Expression.Constant(kvp.Value, kvp.Value.GetType());
// combine them into "equals"
var binaryEqualsExpression = Expression.Equal(propertyExpression, constantExpression);
if (bodyExpression == null)
{
bodyExpression = binaryEqualsExpression;
}
else
{
// combine each "equals" parts with logical "or"
bodyExpression = Expression.OrElse(bodyExpression, binaryEqualsExpression);
}
}
// now construct the whole lambda...
var lambdaExpression = Expression.Lambda<Func<SomeClass, bool>>(bodyExpression, paramExpression);
// ...and make it useable in .Where()
var compiledExpression = lambdaExpression.Compile();
// lets execute in on our test data
var r = testData.Where(compiledExpression);
// only #2 should match
Assert.AreEqual(2, r.Single().Id);
}
Update:
I changed the solution:
items values are of type object
constantExpression honors the value's type.
This way the dictionary can contain other name-value pairs and the solution
still works. The rule of dictionary contents: keys must match SomeClass property names and values must match the given property's type.
The easy (but inelegant) way of doing this is to chain a series of Union statements. You can use a lookup dictionary with a key matching your strings and a value containing an appropriate predicate.
Here is an example using an extension method:
static public IQueryable<Foo> WithFlags(this IQueryable<Foo> source, string[] flags)
{
var map = new Dictionary<string, Expression<Func<Foo, bool>>>()
{
{ "Closed", x => x.Closed },
{ "CostFree", x => x.CostFree },
{ "Visible", x => x.Visible }
};
//Start with a query that returns nothing
var query = source.Where(x => false);
//For each flag supplied by the caller, add an additional set
foreach (var flag in flags)
{
query = query.Union(query.Where(map[flag]));
}
return query;
}
To use:
var results = DbContext.Foo.WithFlags( new string[] { "Closed", "Visible" }).ToList();
The more elegant way to do it is to build a predicate expression containing Or logic. This would be a little involved. I recommend finding a third party toolkit. See this answer.
I am trying to implement the following algorithm using LINQ-to-SQL:
Given a list of strings L, return every row R in the DB for which every string in L is a substring of one of the column values in R.
The question is how do I do this iteratively for every string in L? I don't know how I can slickly put it all into one Linq-To-SQL statement. Note that I have no problem writing code along the lines of:
field1.contains(...) || field2.contains(...) || ...
as there are not that many columns.
E.g., if the input is
["Charlie", "Doctor", "Kor"]
we would output all rows that have a field with "Charlie" as a substring, a field with "Doctor" as a substring, and a field with "Kor" as a substring.
One approach I thought of was to make separate SQL queries for each input value and to take the intersection of all of those.
Another approach is to pick just one of the strings from the input, make a SQL query on that, convert it to a list, and filter out the rest of the strings one at a time using just LINQ in C#.
Any thoughts on an optimal way to do this?
I would try All extension method (EF6 supports it, not sure about LINQ to SQL):
List<string> values = new List<string> { "Charlie", "Doctor", "Kor" };
var query = db.Table
.Where(r => values.All(v => r.Field1.Contains(v) || r.Field2.Contains(v) || ...));
Update: Well, the assumption was wrong - as mentioned in the comments, unfortunately LINQ to SQL does not support the above construct (shame on them).
As usual in such cases, I would build dynamically a corresponding predicate expression.
In this particular case we need something like this (for N fields and M values):
r => (r.Field1.Contains(value1) || r.Field2.Contains(value1) ... || r.FieldN.Contains(value1))
&& (r.Field1.Contains(value2) || r.Field2.Contains(value2) ... || r.FieldN.Contains(value2))
...
&& (r.Field1.Contains(valueM) || r.Field2.Contains(valueM) ... || r.FieldN.Contains(valueM));
And here is a custom extension method which does that:
public static class QueryableExtensions
{
public static IQueryable<T> WhereContainsAll<T>(
this IQueryable<T> source,
IEnumerable<string> values,
params Expression<Func<T, string>>[] members)
{
var parameter = Expression.Parameter(typeof(T), "r");
var body = values
.Select(value => members
.Select(member => (Expression)Expression.Call(
Expression.MakeMemberAccess(parameter, ((MemberExpression)member.Body).Member),
"Contains", Type.EmptyTypes, Expression.Constant(value)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso);
var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
return source.Where(predicate);
}
}
and the sample usage would be
List<string> values = new List<string> { "Charlie", "Doctor", "Kor" };
var query = db.Table.WhereContainsAll(values,
r => r.Field1, r => r.Field2, r => r.Field3, ...);
which should lead to a single SQL query which IMO should be optimal because the heavy work will be done by the database engine. Of course the query most likely will cause full table scan, but the same will happen even with single Contains (SQL LIKE) criteria.
Try this (I made an example using Lists):
var dbValues = new List<string> {"hello", "how", "are", "you"};
var substrings = new List<string> {"ello", "re"};
var result = dbValues.Where(i => substrings.Any(l => i.Contains(l))).ToList();
Result will contain {"hello","are"}
Example with database:
using (var db = new MyDatabase())
{
var substrings = new List<string> { "ello", "re" };
var result = db.MyTable.Where(i => substrings.Any(l => i.Value.Contains(l))).ToList();
}
I am confused if the parameter fruit (which I know is an input parameter) is returned if the condition is true for predicate. As the following piece of code signifies:
List<string> fruits = new List<string> {
"apple",
"passionfruit",
"banana",
"mango",
"orange",
"blueberry",
"grape",
"strawberry"
};
IEnumerable<string> query = fruits.Where(fruit => fruit.Length < 8);
// query contains: {apple,banana,mango,orange,grape}
IEnumerable<string> query2 = query.Where(fruit => fruits.Contains("apple"));
foreach (string fruity in query2)
{
Console.WriteLine(fruity);
}
// finally returns: {apple,banana,mango,orange,grape}
Therefore it seems as if input is returned if condition is true.
Kindly guide me if I'm wrong
Where returns a filtered sequence of the input for which the predicate returned true. It is applied to each element in turn, and that item is either yielded or discarded. Basically:
public static IEnumerable<T>(this IEnumerable<T> source, Func<T,bool> predicate)
{
foreach(var el in source) {
if(predicate(el) {
yield return el;
}
}
}
Look at the names:
IEnumerable<string> query2 = query.Where(fruit => fruits.Contains("apple"));
That says, for every fruit, see if the entire set (fruits, note the final s) returns an apple. The list fruits does contain apple, so that is true for every fruit.
You possibly meant:
IEnumerable<string> query2 = query.Where(fruit => fruit.Contains("apple"));
LINQ Where returns an IEnumerably set with all items that the predicate returns true for.
In your second query you're doing fruits.Contains("apple"), which is basically always true, or always false. Perhaps you meant to do the following:
IEnumerable<string> query2 = query.Where(fruit => fruit == "apple");
//returns: {apple}
You can think of .Where lambda as SQL query:
SELECT * FROM IEnumerable WHERE Predicate = TRUE;
For me this is preferable way due to SQL background :)
I have created a dynamic search screen in ASP.NET MVC. I retrieved the field names from the entity through reflection so that I could allow the user to choose which fields they wanted to search on instead of displaying all fields in the view.
When the search result is Posted back to the controller, I receive a FormCollection containing the FieldName and the Value. I don't know how many fields are being searched on, and the FormCollection only contains fields that were chosen by the user.
I want to be able to now take that field name and apply that to my LINQ statement when I query the database for example:
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
List<People> searchResults = new List<People>();
foreach (string key in fieldValueDictionary.Keys)
{
searchResults.Add(entities.People.Where(p => p.<use the key string as the fieldName> == fieldValueDictionary[key]));
}
return searchResults;
}
Where I have "use the key string as the fieldName" it would be like p => p.FirstName == fieldValueDictionary[key] where key = "FirstName". I've tried and failed to use Lambda Expression Trees, and have had a little success with Dynamic LINQ. The only other alternative is to do something like:
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
IQueryable<People> results = entities.People;
foreach (string key in fieldValueDictionary.Keys)
{
switch (k)
{
case "FirstName": results = results.Where(entities.People.Where(p => p.FirstName == k);
case "LastName": results = results.Where(entities.People.Where(p => p.LastName == k);
// Repeat for all 26 fields in table
}
}
return results.ToList<People>();
}
UPDATE: I've done research into Lambda Expression Trees through the following posts:
dynamically create lambdas expressions + linq + OrderByDescending
Parameter problem with Expression.Lambda()
LINQ: Passing lambda expression as parameter to be executed and returned by method
I've gotten as far as getting a lambda to output the following: "p => p.FirstName", but I can't get this to work in a where. Any Suggestions? My code is below:
MemberInfo member = typeof(People).GetProperty("FirstName");
ParameterExpression cParam = Expression.Parameter(typeof(People), "p");
Expression body = Expression.MakeMemberAccess(cParam, member);
var lambda = Expression.Lambda(body, cParam);
After a lot more trial and error and searching I accidentally found another SO post that covers the same issue:
InvalidOperationException: No method 'Where' on type 'System.Linq.Queryable' is compatible with the supplied arguments
Here is my modified code that works:
IQueryable query = entities.People;
Type[] exprArgTypes = { query.ElementType };
string propToWhere = "FirstName";
ParameterExpression p = Expression.Parameter(typeof(People), "p");
MemberExpression member = Expression.PropertyOrField(p, propToWhere);
LambdaExpression lambda = Expression.Lambda<Func<People, bool>>(Expression.Equal(member, Expression.Constant("Scott")), p);
MethodCallExpression methodCall = Expression.Call(typeof(Queryable), "Where", exprArgTypes, query.Expression, lambda);
IQueryable q = query.Provider.CreateQuery(methodCall);
With some hopefully pretty easy modifications, I should be able to get this to work with any type.
Thanks again for your answers Ani & John Bowen
Have you tried getting the value from PropertyInfo?
entities.People.Where(p => (p.GetType().GetProperty(key).GetValue(p, null) as string) == fieldValueDictionary[key])
public List<People> SearchPeople(Dictionary<string, string> fieldValueDictionary)
{
return !fieldValueDictionary.Any()
? entities.People
: entities.People.Where(p => fieldValueDictionary.All(kvp => PropertyStringEquals(p, kvp.Key, kvp.Value)))
.ToList();
}
private bool PropertyStringEquals(object obj, string propertyName, string comparison)
{
var val = obj.GetType().GetProperty(propertyName).GetValue(obj, null);
return val == null ? comparison == null : val.ToString() == comparison; ;
}
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);