C# predicate list passed to Linq Where clause - c#

I have a long Linq Where clause that I would like to populate with a predicate list.
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
filters.Add(p => p.Title != null && p.Title.ToLower().Contains(searchString));
filters.Add(p => p.Notes != null && p.Notes.ToLower().Contains(searchString));
filters.Add(GlobalSearchUser((List < User > users = new List<User>() { p.user1, p.user2, p.user3, p.user4 }), searchString));
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
However I'm getting this error:
cannot convert from 'System.Linq.Expressions.Expression<System.Func<project.Contracts.DTOs.Note,bool>>[]' to 'System.Func<project.Contracts.DTOs.Note,bool>'
Which is an error on the .where clause. Pulling out the .where compiles just fine.

I think great answer from Hogan can be simplified and shorten a bit by use of Any and All Linq methods.
To get items that fulfill all the conditions:
var resultAll = listOfItems.Where(p => filters.All(f => f(p)));
And to get the items that fulfill any condition:
var resultAny = listOfItems.Where(p => filters.Any(f => f(p)));

There are at least two errors in your code:
List<Expression<Func<Note, bool>>> filters = new List<Expression<Func<Note, bool>>>();
change it to
List<Func<Note, bool>> filters = new List<Func<Note, bool>>();
You don't need Expression trees here. You are using IEnumerable<>, not IQueryable<>
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(filters.ToArray()).Take(10).ToList();
There .Where() accepts a single predicate at a time. You could:
notes = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.Where(x => filters.All(x)).Take(10).ToList();
or various other solutions, like:
var notesEnu = dataAccess.GetList<Note>(pn => pn.ProjectVersionID == projectVersionID, filterExtensions.ToArray())
.AsEnumerable();
foreach (var filter in filters)
{
notesEmu = notesEmu.Where(filter);
}
notes = notesEnu.Take(10).ToList();
Because all the .Where() conditions are implicitly in &&.

You have to loop over your filters and run a test on each one.
You can do it with linq like this to return true if any of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == true) return(true); return(false)})
or like this to to return true if all of your filters are true:
.Where(p => { foreach(f in filters) if (f(p) == false) return(false); return(true)})

You can't just pass an array of predicates to the where method. You need to either iterate over the array and keep calling Where() for each expression in the array, or find a way to merge them all together into one expression and use that. You'll want to use LinqKit if you go the second route.

Related

Is there a way to create a filter expression in MongoDB C# drivers (same way as projection allows)?

I can create a projection fiel definition from expression like that:
var projection = Builders<Bar>.Projection.Expression(x => x.Foo)
I want to do exactly the same with the filter:
var filter = Builders<Bar>.Filter.Expression(x => x.Foo == "10")
But I cannot find a way to do this. The operations that I want to perform FindOneAndUpdateAsync supports an expression as it's input directly, but I want to combine a "hardcoded" filter with the one that is coming in as a parameter. This is how I do it now without an expression:
var filter = Filter.Eq(x => x.Id, id); // Here I would like to write Filter.Expression(x => x.Id == id)
if (additionalFilter != null)
{
filter &= additionalFilter;
}
P.S. I would rather avoid combining expressions directly "the C# way" as it is not very easy to understand.
Mongo net driver does not support Filter.Expression and if you want to project any value cast to as ToList
See the below eg:
var filter = Builders<Movie>.Filter.In("cast", new string[] { "Tom Hanks" });
var movies = await _moviesCollection.Find<Movie>(filter)
.Limit(2)
.ToListAsync();
// your test
var projection = Builders<Bar>.Projection.Expression(x => x.Foo);
var projection = Builders<Bar>.Filter.Where(x => x.Foo == "10");

Is there a way to have multiple likes generated dynamically from an array of string values in EF?

I've set up a search textbox where the search will grab every word individually and search through a field using Contains.
Is there a way to search an array of string through Contains?
//Keep in mind that the array would be generated dynamically through textbox
string[] searchWords = { "hello", "world", "today" };
var articles = _swmDbContext.Articles
.Include(c => c.Category)
.Where(a => a.Title.Contains(searchWords));
searchWords obiviously does not work but trying to show what I want to achieve. searchWords[0] works because it is just one word.
I also tried below as suggested in other links but now the WHERE clause does not show up in query when i run debugger or profiler:
`var articles = _swmDbContext.Articles
.Include(c => c.Category)
.Where(a => searchWords.Any(w => a.Title.Contains(w)));
`
It seems like Entity Framework Core does not translate .Any and .All with .Contains in the above query to SQL statements. Instead it loads all otherwise matching data and does the search in memory.
If you want to find Articles which contain all search words in the Title you could dynamically add .Where conditions (I had a test database with Persons and a Comment field):
var query = (IQueryable<Person>)dbContext.Persons
.Include(p => p.TaxIdentificationNumber);
foreach (var searchWord in searchWords)
{
query = query.Where(p => p.Comment.Contains(searchWord));
}
var persons = query.ToList();
But if you want to find articles which contain any of the search words then you would need an OR in the .Where clause.
Written manually it would look like this:
.Where(p => p.Comment.Contains(searchWords[0]) || p.Comment.Contains(searchWords[1]))
But you can build the expression dynamically:
Expression<Func<Person, bool>> e1 = p => p.Comment.Contains(searchWords[0]);
Expression<Func<Person, bool>> e2 = p => p.Comment.Contains(searchWords[1]);
Expression<Func<Person, bool>> e3 = p => p.Comment.Contains(searchWords[2]);
var orExpression1 = Expression.OrElse(e1.Body, Expression.Invoke(e2, e1.Parameters[0]));
var orExpression2 = Expression.OrElse(orExpression1, Expression.Invoke(e3, e1.Parameters[0]));
var finalExpression = Expression.Lambda<Func<Person, bool>>(orExpression2, e1.Parameters);
and use it like this:
var persons = dbContext.Persons.Where(finalExpression).ToList();
as a function:
Expression<Func<Person, bool>> BuildOrSearchExpression(string[] searchWords)
{
// searchWords must not be null or empty
var expressions = searchWords.Select(s => (Expression<Func<Person, bool>>)(p => p.Comment.Contains(s))).ToList();
if (expressions.Count == 1) return expressions[0];
var orExpression = expressions.Skip(2).Aggregate(
Expression.OrElse(expressions[0].Body, Expression.Invoke(expressions[1], expressions[0].Parameters[0])),
(x, y) => Expression.OrElse(x, Expression.Invoke(y, expressions[0].Parameters[0])));
return Expression.Lambda<Func<Person, bool>>(orExpression, expressions[0].Parameters);
}
and use it
var persons = dbContext.Persons
.Include(p => p.TaxIdentificationNumber)
.Where(BuildOrSearchExpression(searchWords))
.ToList();
If you exchange the .OrElse with .AndAlso all search words must be found like with multiple .where clauses.
When I did some research I also stumbled upon the PredicatedBuilder http://www.albahari.com/nutshell/predicatebuilder.aspx and this SearchExtension https://stackoverflow.com/a/31682364/5550687. But I have not tried them and I don't know if they work with EF Core.

Get Method is null off IQueryable (Entity Framework)

I'm trying to pass lambda expressions and a type to my DAL. I have this statement:
(entities).GetType().GetMethod("Where")
"entities" is the Table of entities on the DataContext.
When I run the statement I get a null even though Linq.Table inherits IQueryable.
Anyone have an idea?
Here is the entire method:
public object GetResultSet(Dictionary<Type, Func<object, bool>> values)
{
using (ICSDataContext db = DataContextFactory.CreateDataContext<ICSDataContext>(DataContexts.ICS))
{
foreach (var entry in values)
{
var property = db.GetType().GetProperty(entry.Key.Name + "s");
IQueryable entities = (IQueryable)property.GetValue(db, null);
var whereMethod = (entities).GetType().GetMethod("Where")
.MakeGenericMethod(Type.GetType(entry.Key.AssemblyQualifiedName));
return whereMethod.Invoke(entities, new object[] { entry.Value });
}
}
return null;
}
Thanks
As an alternative you could do something like
db.Set<Type>()
which will return you the DBSet of the appropriate type, with Where accessible without reflection. Also you may want to use Expression> rather than Func, expressions work on queryables where as funcs work on enumerables. If you pass a func into a Where clause it pulls the entire dbset down and processes it in memory.
Typed expressions are also a little easier to work with (intellesence, type checking).
Expression<Func<User,bool>> filter = c=>c.FirstName == "Bob";
As another alternative you can look into System.Linq.Dynamic, ScottGu has a write up on it here. The article and the code are old, but it works with EF 6. It allows things like
.Where("CategoryId=2 and UnitPrice>3")
From answer by LukeH under here:
var where1 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Then this:
var gmi = where1.MakeGenericMethod(typeof(T));

How do I define a SELECT TOP using LINQ with a dynamic query?

I want to pass dynamic lambda expressions to the function below, but I'm not sure how to define the .Take() or .OrderByDescending() on the expression object.
If I want to call the function below, then I want to be able to do this:
dbprovider.Query = (x => x.ConfigurationReference == "172.16.59.175")
.Take(100)
.OrderByDescending(x.Date)
FindEntities(db, dbprovider.Query)
But I can't (this syntax is invalid). Any ideas?
public static List<T> FindEntities<T>(TrackingDataContext dataContext, System.Linq.Expressions.Expression<Func<T, bool>> find) where T : class
{
try
{
var val = dataContext.GetTable<T>().Where(find).ToList<T>();
return val;
}
catch (Exception ex)
{
throw ex;
}
}
The parameter is of type:
System.Linq.Expressions.Expression<Func<T, bool>> find
That means it can take a predicate (the "where" clause), and only a predicate. Thus the only bit you can pass in there is the filter:
x => x.ConfigurationReference == "172.16.59.175"
To do what you want, you would need to add the rest of the code in FindEntities, so that it becomes:
var val = dataContext.GetTable<T>().Where(find)
.OrderByDescending(x => x.Date).Take(100).ToList<T>();
(note also that the Take should really be after the OrderByDescending)
One way you could do that would be:
public static List<T> FindEntities<T>(TrackingDataContext dataContext,
System.Linq.Expressions.Expression<Func<T, bool>> find,
Func<IQueryable<T>, IQueryable<T>> additonalProcessing = null
) where T : class
{
var query = dataContext.GetTable<T>().Where(find);
if(additonalProcessing != null) query = additonalProcessing(query);
return query.ToList<T>();
}
and call:
var data = FindEntities(db, x => x.ConfigurationReference == "172.16.58.175",
q => q.OrderByDescending(x => x.Date).Take(100));
However, frankly I'm not sure what the point of this would be... the caller could do all of that themselves locally more conveniently, without using FindEntities at all. Just:
var data = db.GetTable<T>()
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or even:
var data = db.SomeTable
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or just:
var data = (from row in db.SomeTable
where row.ConfigurationReference == "172.16.58.175"
orderby row.Date descending
select row).Take(100).ToList();

Combine some lambda expressions using Or operand

I want to make a dynamic expression which will be generated out of an unknown number of Or conditions:
IQueryable<Entity> entities = Repository<Entity>.Load();
Expression<Func<Entity, bool>> wholeFilter;
foreach(condition in Conditions)
{
Expression<Func<Entity, bool>> filter = e => e.something1 == condition.First && e.something2 == condition.Second
wholeFilter = wholeFilter.Or(filter)
}
return entities.Where(wholeFilter);
I tried to implement the Or extension but I couldn't come up with a logical way.
Moreover I was playing around the PredicateBuilder and wrote something like:
var predicate = PredicateBuilder.False<Entity>();
and the code below in my loop:
predicate = predicate.Or(filter);
However I realized that it just And the conditions together not Or them....
Any idea how to generate the combined expression?
regards
EDIT :
the original code that I wrote :
public IQueryable<IEntity> Find(IEntityType entityType, List<AdaptiveObjectModelSpecification> queryObjecs)
{
if (entityType == null)
return null;
var predicate = PredicateBuilder.False<IEntity>();
var entities = Repository<IEntity>.Find(p => p.EntityType == entityType);
if (queryObjecs != null)
{
foreach (var queryObject in queryObjecs)
{
if (entityType.PropertyTypes.Count(p => p.PropertyName == queryObject.PropertyType.PropertyName) == 0)
throw new MissingFieldException(String.Format("Column {0} not found.", queryObject.PropertyType.PropertyName));
predicate = predicate.Or(e => e.Properties.Any(p => p.Value == queryObject.SearchValue && p.PropertyType.PropertyName == queryObject.PropertyType.PropertyName));
}
entities = entities.Where(predicate);
}
return entities;
}
generated entities.Expression.ToString() when queryObjects has two elements =
{value(NHibernate.Linq.NhQueryable`1[Azarakhsh.Domain.Core.AdaptiveObjectModel.Interface.IEntity])
.Where(p => (p.EntityType == value(Azarakhsh.Domain.Core.AdaptiveObjectModel.Service.AdaptiveObjectModelRepositoryService+<>c__DisplayClassd).entityType))
// The line below is generated by PredicateBuilder
.Where(
Param_0 => ((False
OrElse
Invoke(e => e.Properties.Any(p => ((p.Value == value(Azarakhsh.Domain.Core.AdaptiveObjectModel.Service.AdaptiveObjectModelRepositoryService+<>c__DisplayClass10).queryObject.SearchValue) AndAlso (p.PropertyType.PropertyName == value(Azarakhsh.Domain.Core.AdaptiveObjectModel.Service.AdaptiveObjectModelRepositoryService+<>c__DisplayClass10).queryObject.PropertyType.PropertyName))), Param_0))
OrElse
Invoke(e => e.Properties.Any(p => ((p.Value == value(Azarakhsh.Domain.Core.AdaptiveObjectModel.Service.AdaptiveObjectModelRepositoryService+<>c__DisplayClass10).queryObject.SearchValue) AndAlso (p.PropertyType.PropertyName == value(Azarakhsh.Domain.Core.AdaptiveObjectModel.Service.AdaptiveObjectModelRepositoryService+<>c__DisplayClass10).queryObject.PropertyType.PropertyName))), Param_0)))}
Using PredicateBuilder.Or should certainly be "and"-ing the results together.
The code you've currently got in the main snippet of code won't compile because you're using wholeFilter without assigning it an initial value - and additionally, I don't know of any instance or extension method called Or on Expression<TDelegate>.
I would stick to PredicateBuilder, and work out why that isn't working - because it certainly should. Please post some code using PredicateBuilder, along with the SQL it generates.

Categories

Resources