LinqKit Predicates - predicates

I am trying to figure out how the Predicates work. I have a piece of code where one parameter will always be supplied but there could be up to 5 different parameters.
If I try this way
var predicate = PredicateBuilder.False<Data.AccountAllocation>();
if (startDate.HasValue)
predicate = predicate.And(p => p.DateEntered >= startDate);
if (endDate.HasValue)
predicate = predicate.And(p => p.DateEntered <= endDate);
if (allocationTypeId.HasValue)
predicate = predicate.And(p => p.AllocationTypeID == allocationTypeId);
if (allocationStatusID.HasValue)
predicate = predicate.And(p => p.AllocationStatusTypeID == allocationStatusID);
var accountAllocation = await db.AccountAllocations.AsExpandable().Where(predicate).ToListAsync();
return accountAllocation;
It returns nothing yet if I write it this way
var predicate = PredicateBuilder.False<Data.AccountAllocation>();
if (accountId > 0)
predicate = predicate.Or(p => p.AccountID == accountId);
if (startDate.HasValue)
predicate = predicate.And(p => p.DateEntered >= startDate);
if (endDate.HasValue)
predicate = predicate.And(p => p.DateEntered <= endDate);
if (allocationTypeId.HasValue)
predicate = predicate.And(p => p.AllocationTypeID == allocationTypeId);
if (allocationStatusID.HasValue)
predicate = predicate.And(p => p.AllocationStatusTypeID == allocationStatusID);
var accountAllocation = await db.AccountAllocations.AsExpandable().Where(predicate).ToListAsync();
return accountAllocation;
It works properly.
If I change the first Predicate, the account, from .Or to .And it does not work.
.Or always seems to run but if I put .Or for all of them the date returned is not correct since it needs to be an .And
I am trying to figure out how to get this to work because there will be a time where all the parameters are optional. and I will not be able to use a .Or, what is the secret to getting the .And to work regardless oh how many of the parameters get added.

If you only evaluate And conditions you must start with a True predicate, basically because false && bool1 && bool2 ... always evaluates to false:
var predicate = PredicateBuilder.True<Data.AccountAllocation>();
But when there is an Or predicate in the chain of predicates the expression becomes true if the Or predicate evaluates to true.
You probably start with a False predicate because you don't want to return any data of not a single parameter is entered. You can achieve this by checking the predicate at the end:
var predicate = PredicateBuilder.True<Data.AccountAllocation>();
var initString = predicate.ToString();
if (startDate.HasValue)
predicate = predicate.And(p => p.DateEntered >= startDate);
...
if (predicate.ToString() == initString)
predicate = predicate.And(p => false);

Related

linq query with dynamic predicates in where clause joined by OR

you can easily create dynamic queries in c# if you add more restrictions to the current query.
var list = new List<Item>();
var q = list.AsQueryable();
q = q.Where(x => x.Size == 3);
q = q.Where(x => x.Color == "blue");
In this case, every new predicate is added performing an AND operation with the previous. The previous result is equivalent to:
q = list.Where(x => x.Size == 3 && x.Color == "blue");
Is it possible to achieve the same result but with OR instead of AND?
q = list.Where(x => x.Size == 3 || x.Color == "blue");
The idea is to have a variable number of expressions that are joined with OR operator.
Expected result would need to be written in some how similar to the following pseudo code:
var conditions = new List<Func<Item, bool>>();
And later iterate conditions to build something like:
foreach(var condition in conditions)
finalExpression += finalExpression || condition;
Another possible solution to this, especially when someone doesn't want to use an external library is using expression trees.
Add a following expression extension:
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);
return Expression.Lambda<Func<T, bool>>(
Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
As an example imagine you have a Container entity, which has InnerContainer nested entity with two properties Name and Id. Then you can use it in the following way:
Expression<Func<Container, bool>> whereQuery = c => c.InnerContainer.Name == "Name1";
whereQuery = whereQuery.Or(c => c.InnerContainer.Name == "Name2");
whereQuery = whereQuery.Or(c => c.InnerContainer.Id == 0);
var result = query
.Where(whereQuery)
.ToList();
Materializing such query will result in the following SQL:
SELECT [x].[Id], [x].[InnerContainerId]
FROM [Containers] AS [x]
LEFT JOIN [InnerContainer] AS [x.InnerContainer] ON [x].[InnerContainerId] = [x.InnerContainer].[Id]
WHERE [x.InnerContainer].[Name] IN (N'Name1', N'Name2') OR ([x].[InnerContainerId] = 0)
This way you can hold lambdas in a collection and loop through them.
Thanks to Raphaƫl Althaus that gave the following link:
http://www.albahari.com/nutshell/predicatebuilder.aspx
Predicate builder is the solution. You can use it installing LinqKit from Nuget. In that url you can find also the implementation of this class.
Note: in order to make this work with LinqToSql or LinqToEntities the IQueriable Object must be transformed using "AsExpandable()" method, for memory objects it's not required

Linq PredicateBuilder, grouping and operator precedence

Here is an example of the problem:
var source = new LambdasTestEntity[] {
new LambdasTestEntity {Id = 1},
new LambdasTestEntity {Id = 2},
new LambdasTestEntity {Id = 3},
new LambdasTestEntity {Id = 4},
};
Expression<Func<LambdasTestEntity, bool>> expression1 = x => x.Id == 1;
Expression<Func<LambdasTestEntity, bool>> expression2 = x => x.Id == 3;
Expression<Func<LambdasTestEntity, bool>> expression3 = x => x.Id > 2;
// try to chain them together in a following rule
// Id == 1 || Id == 3 && Id > 2
// as && has higher precedence, we expect getting two entities
// with Id=1 and Id=3
// see how default LINQ works first
Expression<Func<LambdasTestEntity, bool>> expressionFull = x => x.Id == 1 || x.Id == 3 && x.Id > 2;
var filteredDefault = source.AsQueryable<LambdasTestEntity>()
.Where(expressionFull).ToList();
Assert.AreEqual(2, filteredDefault.Count); // <-this passes
// now create a chain with predicate builder
var totalLambda = expression1.Or(expression2).And(expression3);
var filteredChained = source.AsQueryable<LambdasTestEntity>()
.Where(totalLambda).ToList();
Assert.AreEqual(2, filteredChained.Count);
// <- this fails, because PredicateBuilder has regrouped the first expression,
// so it now looks like this: (Id == 1 || Id == 3) && Id > 2
When I look in Watches for both expressions, I see the following:
expressionFull as it is coming from Linq:
(x.Id == 1) OrElse ((x.Id == 3) AndAlso (x.Id > 2))
totalLambda for PredicateBuilder:
((x.Id == 1) OrElse Invoke(x => (x.Id == 3), x)) AndAlso Invoke(x => (x.Id > 2), x)
I find it is a bit unsafe to use the PredicateBuilder if it behaves differently from default Linq Expression builder.
Now some questions:
1) Why is Linq creating those groups? Even if I create an Or expression
x => x.Id == 1 || x.Id == 3 || x.Id > 2
I stil get the first two criteria grouped like this:
((x.Id == 1) OrElse (x.Id == 3)) OrElse (x.Id > 2)
Why it is not just
(x.Id == 1) OrElse (x.Id == 3) OrElse (x.Id > 2)
?
2) Why PredicateBuilder is adding those Invokes? I don't see Invokes in the default Linq expression result, so they seem useless...
3) Is there any other way to construct the expression "offline" and then pass to the default Linq Expression builder? Something like this:
ex = x => x.Id == 1;
ex = ex || x.Id == 3;
ex = ex && x.Id > 2;
and then Linq Expression builder then parses it and creates the same expression as it does for x => x.Id == 1 || x.Id == 3 && x.Id > 2 (giving && higher precedence)?
Or maybe I could tweak the PredicateBuilder to do the same?
Expanding on my comment above:
Because it has no concept of operator precedence here. You're
literally building the expression tree yourself, and "piping" the
results of one method to the next determines order. Thus, the order of
the resulting expression is going to be exactly what you specified.
The complete source for PredicateBuilder is posted here and shows just how simple it is. But it also shows you the source of your problem above. In case you don't want to visit Albahari's site, here's the complete source:
public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}
The main thing to notice here is that it builds the expression one node at a time, then pipes this node as being the left expression (leaf) of the subsequent node. The Expression.Invoke call is simply to pipe the parameters from your existing node into the right leaf (the next expression), and the rest is pretty self explanatory.
Edit: I had to do something similar to this (but didn't use PredicateBuilder, built the trees myself using Expression calls). The main thing to keep in mind is that you just have to process the And/AndAlso nodes first, and then handle the Or/OrElse nodes, that way you build the tree with the proper precedence. Unfortunately, building ExpressionTrees by hand are very much a step-by-step process, so you have to make sure you break each of the steps down into the correct order to get the results you want/need.

Cannot implicitly convert type 'DateTime' to 'bool' linq query

I have this LINQ query:
var resourcePlanningInWeek = resourcePlanning.Where(rp => rp.PlanDate >= dateFrom && rp.PlanDate <= dateTo);
var holidays = new HolidayManager().GetByPeriod(dateFrom, dateTo);
var resourcePlanningExcludedHolidays= resourcePlanningInWeek.Where(rpiw => ( holidays.Where(h => h.HolidayDate = rpiw.PlanDate).Count = 0))
When executed, I get following error:
Cannot implicitly convert type 'DateTime' to 'bool'
Somewone know why?
You need to use == instead of = when you want to make a comparison. That's relevant at two places in the last line.
The call to Count is missing the parentheses.
var resourcePlanningExcludedHolidays = resourcePlanningInWeek
.Where(rpiw => holidays.Where(h => h.HolidayDate == rpiw.PlanDate)
.Count() == 0);
However, there is a better way of writing this:
var resourcePlanningExcludedHolidays = resourcePlanningInWeek
.Where(rpiw => !holidays.Any(h => h.HolidayDate == rpiw.PlanDate));
This is better, because:
It is shorter and more readable
It stops enumerating holidays as soon as the condition is true the first time. Count() always enumerates the complete list.
An even better approach would be to use a HashSet<DateTime>:
var holidays
= new HashSet<DateTime>(new HolidayManager().GetByPeriod(dateFrom, dateTo)
.Select(x => x.HolidayDate));
var resourcePlanningExcludedHolidays
= resourcePlanningInWeek.Where(rpiw => !holidays.Contains(rpiw.PlanDate));
You are missing an equal symbol at:
h.HolidayDate = rpiw.PlanDate
It should be:
var resourcePlanningExcludedHolidays=
resourcePlanningInWeek.Where(rpiw => ( holidays.Where(h => h.HolidayDate == rpiw.PlanDate).Count = 0))

Entity Framework/ Linq - groupby and having clause

Given the query below
public TrainingListViewModel(List<int> employeeIdList)
{
this.EmployeeOtherLeaveItemList =
CacheObjects.AllEmployeeOtherLeaves
.Where(x => x.OtherLeaveDate >= Utility.GetToday() &&
x.CancelDate.HasValue == false &&
x.OtherLeaveId == Constants.TrainingId)
.OrderBy(x => x.OtherLeaveDate)
.Select(x => new EmployeeOtherLeaveItem
{
EmployeeOtherLeave = x,
SelectedFlag = false
}).ToList();
}
I want to put in the employeeIdList into the query.
I want to retrieve all of the x.OtherLeaveDate values where the same x.OtherLeaveDate exists for each join where x.EmployeeId = (int employeeId in employeeIdList)
For example if there are EmployeeIds 1, 2, 3 in employeeIdList and in the CacheObjects.AllEmployeeOtherLeaves collection there is a date 1/1/2001 for all 3 employees, then retreive that date.
If I read you well it should be something like
var grp = this.EmployeeOtherLeaveItemList =
CacheObjects.AllEmployeeOtherLeaves
.Where(x => x.OtherLeaveDate >= Utility.GetToday()
&& x.CancelDate.HasValue == false
&& x.OtherLeaveId == Constants.TrainingId
&& employeeIdList.Contains(x.EmployeeId)) // courtesy #IronMan84
.GroupBy(x => x.OtherLeaveDate);
if (grp.Count() == 1)
{
var result = g.First().Select(x => new EmployeeOtherLeaveItem
{
EmployeeOtherLeave = x,
SelectedFlag = false
})
}
First the data is grouped by OtherLeaveDate. If the grouping results in exactly one group, the first (and only) IGrouping instance is taken (which is a list of Leave objects) and its content is projected to EmployeeOtherLeaveItems.
To the where statement add "&& employeeIdList.Contains(x.EmployeeId)"
I need to thank #IronMan84 and #GertArnold for helping me along, and I will have to admonish myself for not being clearer in the question. This is the answer I came up with. No doubt it can be improved but given no one has responded to say why I will now tick this answer.
var numberOfEmployees = employeeIdList.Count;
var grp = CacheObjects.AllEmployeeOtherLeaves.Where(
x =>
x.OtherLeaveDate >= Utility.GetToday()
&& x.CancelDate.HasValue == false
&& x.OtherLeaveId == Constants.TrainingId
&& employeeIdList.Contains(x.EmployeeId))
.GroupBy(x => x.OtherLeaveDate)
.Select(x => new { NumberOf = x.Count(), Item = x });
var list =
grp.Where(item => item.NumberOf == numberOfEmployees).Select(item => item.Item.Key).ToList();

If condition in LINQ Where clause

With Linq, can I use a conditional statement inside of a Where extension method?
var query = someList.Where(a => (someCondition)? a == "something" : true);
so, if 'someCondition' is false, 'Where' will be skipped.
Yes you can like:
var query = someList.Where(a => a == "something");
if (condition)
{
query = query.Where(b => b == "something else");
}
var result = query.ToList();
Because Where is producing an IQueryable, the execution is deferred until the ToList in my example so you can chain Wheres together as much as you want and then just execute it after you have passed all your conditions.
Make use of WhereIf extenstion method avaialbe in linq
Example
if (SearchControlMain.PostingID.HasValue)
query = query.Where(q => q.PostingID == SearchControlMain.PostingID);
instead of above go for the below
query = query.WhereIf(SearchControlMain.CategoryID.HasValue, q => q.CategoryID == SearchControlMain.CategoryID);
LINQ WhereIf Extension Method
LINQ to SQL Where Clause Optional Criteria
Not sure if this is appropriate but it is quite useful, you can use ifs quite handily with conditional where clauses:
var r = (from p in productinfo.tblproduct
where p.Accountid == accountid
select p);
if (uuf1 != null)
r = r.Where(p => p.UnitUserField1 == uuf1);
if (uuf2!= null)
r = r.Where(p => p.UnitUserField2 == uuf2);
So the where clause will be amended according to what is in UUF1 or UUF2 i.e. you might have only UUF1 with info, in which case it will take that and ignore the UUF2 where clause, you might have both in which it will take both or you might not have anything in UUF1 or 2 and your where clause will just take the accountid as the where clause.
In my case there were two "conditional" where depending on search keys, so I did:
var query = db.Package.Include("SomeThing")
.Where(item => searchString1 == null || searchString1 == "" || item.Contains(searchString1))
.Where(item => searchString2 == null || searchString2 == "" || item.Contains(searchString2));
...
from item in items
where condition1
&& (condition2 ? true : condition3)
select item
This is how can you can do it with the noob Linq syntax.
This applies the condition3 only if condition2 is false.
If condition2 is true, you are essentially doing && true which has no effect on the where clause.
So it is essentially doing this:
if(condition2)
{
from item in items
where condition1
select item
else
{
from item in items
where condition1
&& condition3
select item
}
I had a scenario like this where I had to check for null within the list itself. This is what I did.
items = from p in items
where p.property1 != null //Add other if conditions
select p;
// Use items the way you would use inside the if condition
But as Kelsey pointed out this would work too -
items = items.Where(a => a.property1 != null);
I'm not sure what the question is, but a possible answer could be:
Yes,
list.Where(item => { if (Foo(item)) return true; else return false; });
It would be a complicated way of saying something simple, though.
In my case, I wanted to keep the elements which met my criteria and log the ones that didn't without iterating multiple times.
var merchantsWithLocations = allMerchants.Where(m =>
{
if (m.Locations?.Any() != true)
{
_logger.Log("Merchant {merchantId} has no locations", m.Id);
return false;
}
return true;
};
Any time you want to do a side-effect per element (such as logging), breaking out the lambda into a statement body makes it easy to reason about.

Resources