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.
Related
We've just recently included the PredicateBuilder class in our project and encountered an issue. In the following code, why would the createDateTime condition not included in the generated sql? The other filters are generated properly except for the date. We've also referenced LinqKit, if that matters.
var predicate = PredicateBuilder.True<Booking>();
predicate = predicate.And(p => p.propertyId == propertyId).And(p => p.status == (int)status);
//if (dtFrom != null && dtFrom.HasValue)
//{
predicate.And(p => p.createDateTime >= dtFrom.Value);
//}
return this.ObjectContext.Bookings.AsExpandable().Where(predicate).OrderBy(p => p.createDateTime).Select(b => b.id).ToList();
You need to assign the predicate back to the variable.
Code as below:
predicate = predicate.And(p => p.createDateTime >= dtFrom.Value);
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));
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.
I wrote an extension method to get only approved absences out of a list of absences:
public static IQueryable<tblAbwesenheit> OnlyApprovedAbsences(this IQueryable<tblAbwesenheit> source)
{
return source.Where(a =>
(a.tblAbwesenheitsantraggenehmigungs.Any() && a.tblAbwesenheitsantraggenehmigungs.All(g => g.AbwesenheitsgenehmigungsstatusID == AbsenceStatusIds.Approved))
&& (!a.tblAbwesenheitsstornierunggenehmigungs.Any() || a.tblAbwesenheitsstornierunggenehmigungs.Any(g => g.AbwesenheitsgenehmigungsstatusID != AbsenceStatusIds.Approved)));
}
When I'm using this method with a "normal" Select, everything is fine:
context.tblAbwesenheits.OnlyApprovedAbsences().ToList()
However when I'm using it inside a Select statement, I get an error:
context.tblMitarbeiters.Select(m => new
{
Employee = m,
AbsencesForEmployee = m.tblAbwesenheits.OnlyApprovedAbsences()
})
.ToList();
LINQ to Entities does not recognize the method
'System.Linq.IQueryable1[Data.tblAbwesenheit]
OnlyApprovedAbsences(System.Linq.IQueryable1[Data.tblAbwesenheit])'
method, and this method cannot be translated into a store expression.
I have searched quite a lot, but could not find a way to teach Entity Framework to recognize my Method without expanding the query to
context.tblMitarbeiters.Select(m => new
{
Employee = m,
AbsencesForEmployee = m.tblAbwesenheits
.Where(a =>
(a.tblAbwesenheitsantraggenehmigungs.Any() && a.tblAbwesenheitsantraggenehmigungs.All(g => g.AbwesenheitsgenehmigungsstatusID == AbsenceStatusIds.Approved))
&& (!a.tblAbwesenheitsstornierunggenehmigungs.Any() || a.tblAbwesenheitsstornierunggenehmigungs.Any(g => g.AbwesenheitsgenehmigungsstatusID != AbsenceStatusIds.Approved)))
})
.ToList();
Is there a way to get EF to recognize my Method?
EF is trying to look for a SQL equivalent of your method and not finding one. It can find an equivalent of the expanded query, which is why that works.
You might be able to create an expression rather than a method
var OnlyApprovedAbsencesExpression = (a =>
(a.tblAbwesenheitsantraggenehmigungs.Any() && a.tblAbwesenheitsantraggenehmigungs.All(g => g.AbwesenheitsgenehmigungsstatusID == AbsenceStatusIds.Approved))
&& (!a.tblAbwesenheitsstornierunggenehmigungs.Any() || a.tblAbwesenheitsstornierunggenehmigungs.Any(g => g.AbwesenheitsgenehmigungsstatusID != AbsenceStatusIds.Approved)))
and then write something like
AbsencesForEmployee = m.tblAbwesenheits.Where(OnlyApprovedAbsencesExpression)
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();