PredicateBuilder with DateTime - c#

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);

Related

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));

Filtering query by child entity element

I used entity framework with my example. I wanted to filter child entity but I am getting exception 'The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.'
public List<Notification> GetNotificationBySentDate(DateTime? dateTime)
{
if (dateTime == null)
{
return
_dbContext.Notifications.Include(x => x.Attachments.Select(a=>a.Clean==true))
.Where(x =>
x.Sent == null &&
x.FaultCount <= _appSettingsHelper.NotificationsFaultCountLimit &&
DbFunctions.AddSeconds(x.CreatedDate, x.DelaySeconds) < DateTime.UtcNow)
.OrderBy(a => DbFunctions.AddSeconds(a.CreatedDate, a.DelaySeconds))
.Take(_appSettingsHelper.NotificationsBySentStateSelectTop).ToList();
}
return _dbContext.Notifications.Include(x => x.Attachments).Where(x => x.Sent >= dateTime)
.OrderBy(a => a.CreatedDate)
.Take(_appSettingsHelper.NotificationsBySentStateSelectTop).ToList();
}
Any help would be much appreciated. Thanks.
It seems that the problem is with Select statement under the Include. You can try to remove it and add an additional clause to Where, something like && x.Attachements.Clean == true. Thus your code will be
_dbContext.Notifications.Include(x => x.Attachments)
.Where(x =>
x.Sent == null &&
x.FaultCount <= _appSettingsHelper.NotificationsFaultCountLimit &&
DbFunctions.AddSeconds(x.CreatedDate, x.DelaySeconds) < DateTime.UtcNow &&
x.Attachments.Clean == true)
.OrderBy(a => DbFunctions.AddSeconds(a.CreatedDate, a.DelaySeconds))
.Take(_appSettingsHelper.NotificationsBySentStateSelectTop).ToList();

C# predicate list passed to Linq Where clause

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.

LINQ to Entities does not recognize the method 'System.Linq.IQueryable in nested select

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)

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