I have a Linq expression that is used in a few places. I went down the expression route as there wasn't a logical way to accomplish some searching logic without enumerating a very large table otherwise.
private Expression<Func<Property, bool>> PropertyIsCompliant()
{
return (p) => p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr)
.All(cr => cr.Outcome == CalculationOutcome.Success);
}
My models are as such:
A Property has many CalculationSets
Each CalculationSet is also assigned to a Document
Each CalculationSet has a number of CalculationResults
Each CalculationResult has an Outcome
I'm trying to create an expression that will tell me if all the outcomes from the most recent calculationsets grouped by document ordered by most recent (ie the most recent distinct results) are Successful.
I can the SelectMany clause returns all the CalculationResults from the correct CalculationSets.
I just cant figure out how to return true ONLY if the collection isn't empty AND they are all Outcome.Success.
I understand the All operator automatically returns true on an empty collection. I just can't think of a way around it!
So your real condition is that there are not any unsuccessful outcomes. In that case use Any and reverse the condition:
//V-- notice the ! inverse operator here
return (p) => !(p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr)
.Any(cr => cr.Outcome != CalculationOutcome.Success));
var countsBySuccess =
...
.GroupBy(cr => cr.Outcome == CalculationOutcome.Success) //group on success
.Select(g => new { IsSuccessful = g.Key, Count = g.Count() });
You can now examine the two result rows to make sure that the unsuccessful count is zero and the successful count is non-zero.
Regarding performance, this will need to materialize the entire result set server-side and aggregate it. But it does so only once.
If you must use the calculation result as part of a bigger query, you must use another trick:
!countsBySuccess.Any(g =>
g.IsSuccessful && Count == 0 ||
!g.IsSuccessful && Count != 0)
This boolean expression determines whether the condition you are looking for holds with one scan of the data.
It is important to only scan the data once. Do not simply write:
myItems.All(cr => cr.Outcome == CalculationOutcome.Success) && myItems.Any()
Because that does two scans. SQL Server does not optimize this out.
I think you're answering your question - if you know that All returns TRUE for empty then you have two checks to make. Excuse my C# (I'm not sure on the var query assignment, hopefully you get the idea) but you could do something like this:
private Expression<Func<Property, bool>> PropertyIsCompliant()
{
var query = (p) => p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr);
return (query.Count > 0) & query.All(cr => cr.Outcome == CalculationOutcome.Success);
}
I didn't realise it was possible to use "&&" in expressions. So I've managed to combine 2 separate expressions that give the answer I need. The "&&" only returns true when both expressions evaluate "true"
return (p) =>
p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr).Any()
&&
p.CalculationSets.OfType<SingleDocumentCalculationSet>()
.GroupBy(cs => cs.SourceDocument)
.Select(g => g.OrderByDescending(d => d.DateTime).FirstOrDefault().CalculationResults)
.SelectMany(cr => cr)
.All(cr => cr.Outcome == CalculationOutcome.Success);
Related
Earlier I put a question on Stackoverflow about how to remove duplicate records in a list of objects, based on a particular property within each object.
I got the answer I was looking for (see below), a query which returns a distinct list of objects using MainHeadingID as the property to remove duplicates.
public IList<tblcours> GetAllCoursesByOrgID(int id)
{
return _UoW.tblcoursRepo.All.
Where(c => c.tblCourseCategoryLinks.Any(cl => cl.tblUnitCategory.tblUnit.ParentID == id))
.GroupBy(c => c.MainHeadingID)
.Select(g => g.FirstOrDefault())
.ToList();
}
However, now I need more help! Is there anyway of amending the query above so that, it only removes duplicate values when MainHeadingID is not equal to 180. I tried amending GroupBy line to
.GroupBy(c => c.MainHeadingID != 180)
However, this didn't work.
Any help would be much appreciated with this.
Thanks.
Following works for LINQ to SQL:
return _UoW.tblcoursRepo.All
.Where(c => c.tblCourseCategoryLinks.Any(cl => cl.tblUnitCategory.tblUnit.ParentID == id))
.GroupBy(c => c.MainHeadingID)
//.SelectMany(g => g.Key == 180 ? g : g.Take(1))
.SelectMany(g => g.Take(g.Key == 180 ? Int32.MaxValue : 1))
.ToList();
Comments: SelectMany in query above selects all items from group where MainHeadingID equals to 180, but it takes only one item form other groups (i.e. distinct result). Linq to SQL cannot translate commented out part, but thanks to #usr there is way around.
Linq to Entities cannot translate even simplified query. I think only option for you in this case is simple concating result of two queries:
Expression<Func<tblcours, bool>> predicate = x =>
x.tblCourseCategoryLinks.Any(cl => cl.tblUnitCategory.tblUnit.ParentID == id)
int headingId = 180;
return _UoW.tblcoursRepo.All
.Where(c => c.MainHeadingID != headingId)
.Where(predicate)
.GroupBy(c => c.MainHeadingID)
.Select(g => g.FirstOrDefault())
.Concat(_UoW.tblcoursRepo.All
.Where(c => c.MainHeadingID == headingId)
.Where(predicate))
.ToList();
lazyberezovsky's answer fails due to an EF bug (which is not surprising given the quality of EF's LINQ support). It can be made to work with a hack:
.SelectMany(g => g.Key == 180 ? g.Take(int.MaxValue) : g.Take(1))
or
.SelectMany(g => g.Take(g.Key == 180 ? int.MaxValue : 1))
Note that performance will not be particularly good due to the way this is translated to SQL.
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))
I have the following linq expression pulling all data from my database:
var items = response.Select(a => a.SessionLocationID).ToArray();
mdl = _meetingRepository.Select<SessionLocation>()
.OrderBy(a => a.SessionDT).ThenBy(a => a.SessionEndTime);
Now I want to group by the field ActualRoom and only the ones with ActualRoom count > 3
Is that possible?
You can use GroupBy, just keep in mind that you are losing the ordering you already did so I would start off before you do the sorting:
var groups = _meetingRepository.Select<SessionLocation>()
.GroupBy(x => x.ActualRoom)
.Where(g => g.Count() > 3)
To have sorted groups - assuming preserving the count as a separate property is not neccessary you can just project to an IEnumerable of IEnumerable<SessionLocation>:
var groups = _meetingRepository.Select<SessionLocation>()
.GroupBy(x => x.ActualRoom)
.Where(g => g.Count() > 3)
.Select(g => g.OrderBy(x => x.SessionDT).ThenBy(x => x.SessionEndTime));
I am using "Linq" to filter list of objects and to sort them, like
myList.Where(x => x.Item!= "SF" && x.AdSize == minadSize)
.OrderBy(x => x.ManufacturingDate)
.OrderBy(x=>x.ExpiryDate);
I doubt whether i am doing it right or not that is if i want to "sorting" on multiple fields then is it necessary to use multiple Order By clause cant it be done with single "OrderBy"
Don't use multiple OrderBy calls - use OrderBy followed by ThenBy:
var query = myList.Where(x => x.Item!= "SF" && x.AdSize == minadSize)
.OrderBy(x => x.ManufacturingDate)
.ThenBy(x => x.ExpiryDate); // Could add more ThenBy calls
If you use OrderBy twice, it will reorder the already-ordered-by-date list by expiry-date, whereas I assume you only want to order by expiry date for items with an equal manufacturing date, which is what the above does.
Obviously there's a ThenByDescending method too. For example:
var query = people.OrderBy(x => x.LastName)
.ThenBy(x => x.FirstName)
.ThenByDescending(x => x.Age)
.ThenBy(x => x.SocialSecurity);
Hi I am getting this error.
The expression of type 'System.Collections.Generic.IEnumerable`1[System.String]' is not a sequence.
this is my code
_session.All<Sentence>()
.Select(T => new { Sentence = T, Descriptions = T.Sentence.Split(' ') })
.Where(S => S.Descriptions .Intersect(words).Any())
.Select(R => R.Sentence)
.Distinct();
words is a list of string.
what does not a sequence mean and how do i fix it.
EDIT: Okay, now that the question's been corrected...
I suspect this is a Subsonic restriction. You might try this instead:
.Where(S => S.Descriptions.Any(x => words.Contains(x)))