I'm trying to sort some data using Linq using two sorting methods but it doesn't work.
So I have a list that contains an Id and a Result.
I would like to sort in the following order:
Sort the list by the lowest ID wherever the Result = 0
Then sort the list by ascending Result
But the list can only include Results that are not null
I tried the code below but it appears I can't put a .Where in between the .OrderBy and the .ThenBy.
var selectedResults = Results
.OrderBy(s => s.id)
.Where(s => s.result == 0)
.ThenBy(s => s.result)
.Where(s => s.result != null)
.ToList();
Any suggestions?
You don't want to filter by s.result == 0 but just by s.result == null.
var selectedResults = Results
.Where(s => s.result != null)
.OrderBy(s => s.result == 0 ? s.id : s.result);
If you want to force that the result==0 items come first add this conditional order:
var selectedResults = Results
.Where(s => s.result != null)
.OrderByDescending(s => s.result == 0) // forces 0 results first even if there are negative
.ThenBy(s => s.result == 0 ? s.id : s.result);
Put ThenBy after OrderBy because it only works with IOrderedQueryable<T>, when you call Where it changes the return type back to IQueryable<T> so the ThenBy no longer be called. And as the comment points out, it is cheaper to filter the result set before ordering.
var selectedResults = Results
.Where(s => s.result == 0 && s.result != null)
.OrderBy(s => s.id)
.ThenBy(s => s.result)
.ToList();
If I understands you right,
var selectedResults = Results
.OrderBy(item => item.Result != 0) // items with Result == 0 first
.ThenBy(item => item.Result == 0 // if items.Result == 0 then by Id
? item.id
: int.MaxValue) // I've assumed id is int
.ThenBy(item => item.Result); // finally by Result
So we'll have something like this
Result | Id
-----------
0 | 1 <- Result == 0 on the top; tie breaks by Id (1, 2, 4)
0 | 2
0 | 4
-1 | 0 <- Result != 0 on the bottom, tie breaks by Result (-1, 7, 8, 9)
7 | 15
8 | 3
9 | 98
You always want to sort on the smallest dataset you can, so I would put the Where clauses at the beginning like so:
var selectedResults = Results
.Where(s => s.result == 0 && s.result != null)
.OrderBy(s => s.id)
.ThenBy(s => s.result)
.ToList();
Related
I have a pretty complicated linq statement that gets a list of people (using Entity Framework) and I want to add an OrderBy clause to the end, depending on which column the user has clicked on for sorting. I DON'T want to get all the people and then sort as there are potentially alot of people and we also do paging, so getting the data and then sorting/paging is not an option. It must therefore be done using LINQ to EF.
I have managed to get the search criteria that filters based on the status of the user's current vaccination status, but I am unable to "convert" that to an OrderBy statement
The data I am getting relates to COVID vaccinations and whether the person's vaccination status is Full, Partial, Not Disclosed or None.
The Entity Framework LINQ statement with the Where clause looks like this and It is an IQueryable<Person>, not a List<Person>:
people.Where(p => p.Encounters.Where(e =>
e.EncounterItems.Any(ei => ei.PersonAssessments.Any(pa =>
pa.Assessment.Questions.Any(q => q.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase) || q.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)))))
.OrderByDescending(e => e.servicedt ?? e.planneddt).FirstOrDefault()
.EncounterItems.Where(ei =>
ei.PersonAssessments.Any(pa => pa.Answers.Any(a => a.adate.HasValue && DbFunctions.AddMonths(a.adate, procedureCycleDays) < DateTime.Today &&
(a.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase) || (a.Question.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)
&& (!pa.Answers.Any(aa => aa.adate.HasValue && aa.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)))
))))).FirstOrDefault()
!= null)
From the above it will filter the people where their vaccination status is "Overdue". i.e they have done either Partial or Full Vaccination but the cycle for this vaccination has been exceeded. There are 2 questions with questioncode's "qIDateP" (partial) and "qIDateF" (full).
I know the below OrderBy is completly wrong, but I want to do something like this so that all the people with overdue vaccination status's are at the top. I will then add several other OrderBy clauses such as "Current" using the same clause, just chainging the date expression e.g. DbFunctions.AddMonths(a.adate, procedureCycleDays) >= DateTime.Today
people.OrderBy(p => p.Encounters.Where(e =>
e.EncounterItems.Any(ei => ei.PersonAssessments.Any(pa =>
pa.Assessment.Questions.Any(q => q.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase) || q.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)))))
.OrderByDescending(e => e.servicedt ?? e.planneddt).FirstOrDefault()
.EncounterItems.Where(ei =>
ei.PersonAssessments.Any(pa => pa.Answers.Any(a => a.adate.HasValue && DbFunctions.AddMonths(a.adate, procedureCycleDays) < DateTime.Today &&
(a.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase) || (a.Question.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)
&& (!pa.Answers.Any(aa => aa.adate.HasValue && aa.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)))
))))).FirstOrDefault()
!= null)
The Relationships for the EF Models is as follows:
Person => Encounter => EncounterItem => PersonAssessment => Answer
A person can answer multiple Assessments over their life and can change their mind as to whether they want to disclose their vaccination status or not.
NOTE: We are using the latest Entity Framework 6.4.4
I hope someone can help me with the OrderBy clause as Im at a complete loss as to how to achieve this.
------UPDATE 1-------
I have used this so far.
people.OrderBy(p => p.Encounters.Where(
e => e.EncounterItems.Any(
ei => ei.PersonAssessments.Any(
pa => pa.Assessment.Questions.Any(
q => q.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)
|| q.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase))))).OrderByDescending(e => e.servicedt ?? e.planneddt).FirstOrDefault() // you have 1 Encounters item
.EncounterItems.DefaultIfEmpty().FirstOrDefault(
ei => ei.PersonAssessments.Any(
pa => pa.Answers.Any(
a => a.adate.HasValue
&& DbFunctions.AddMonths(a.adate, procedureCycleDays) < DateTime.Today
&& (a.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)
|| (a.Question.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)
&& (!pa.Answers.Any(aa => aa.adate.HasValue && aa.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)))))))).Encounter.planneddt)
The issue is that all the "Overdue" records are at the bottom, not at the top. If I use OrderByDescending it seems correct. How can I now put all those records at the top with OrderBy instead of OrderByDescending.
------ UPDATE 2 Final Solution ------
After a couple of changes based on Margus answer below I have the final updated OrderBy. I had to OrderBydescending for some reason in order to get the records that I wanted at the top.
people.OrderByDescending(p => p.Encounters.Where(
e => e.EncounterItems.Any(
ei => ei.PersonAssessments.Any(
pa => pa.Assessment.Questions.Any(
q => q.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)
|| q.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase))))).OrderByDescending(e => e.servicedt ?? e.planneddt).FirstOrDefault() // you have 1 Encounters item.EncounterItems.DefaultIfEmpty().FirstOrDefault(
ei => ei.PersonAssessments.Any(
pa => pa.Answers.Any(
a => a.adate.HasValue
&& DbFunctions.AddMonths(a.adate, procedureCycleDays) < DateTime.Today
&& (a.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)
|| (a.Question.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)
&& (!pa.Answers.Any(aa => aa.adate.HasValue && aa.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)))))))).Encounter.planneddt)
Now Im concerned about the performance lol... But that will be another stackoverflow search :)
Well as far as I can tell you want to use orderBy and then simply fetch the first element, while you could simply fetch the first element with the same predicate dropping O(nlogn) complexity
var result = people.Where(
p => p.Encounters.Where(
e => e.EncounterItems.Any(
ei => ei.PersonAssessments.Any(
pa => pa.Assessment.Questions.Any(
q => q.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)
|| q.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)))))
.FirstOrDefault(e => e.servicedt ?? e.planneddt) // you have 1 Encounters item
.EncounterItems.FirstOrDefault(
ei => ei.PersonAssessments.Any(
pa => pa.Answers.Any(
a => a.adate.HasValue
&& DbFunctions.AddMonths(a.adate, procedureCycleDays) < DateTime.Today
&& (a.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)
|| (a.Question.questioncode.Equals("qIDateP", StringComparison.InvariantCultureIgnoreCase)
&& (!pa.Answers.Any(aa => aa.adate.HasValue && aa.Question.questioncode.Equals("qIDateF", StringComparison.InvariantCultureIgnoreCase)))))))));
I have a database and excel file
And I want to export strings to excel
How to export strings with 1 id I know
The code you can see below
_Hours = Rep.Where(o => o.Projects.ProjectGroupID == 4).Where(o => o.Projects.ProjectType == 1).Sum(o => (decimal?)o.TaskEfforts) ?? 0m,
But how I can choose several id's?
This does not work
_Hours = Rep.Where(o => o.Projects.ProjectGroupID == 4).Where(o => o.ProjectDescriptionID == 10).Where(o => o.ProjectDescriptionID == 17).Where(o => o.ProjectDescriptionID == 18).Where(o => o.ProjectDescriptionID == 19).Where(o => o.ProjectDescriptionID == 21).Where(o => o.ProjectDescriptionID == 24).Where(o => o.ProjectDescriptionID == 26).Where(o => o.Projects.ProjectType == 1).Sum(o => (decimal?)o.TaskEfforts) ?? 0m,
I know, that it is an error, but how can I choose some ID's?
Thanks for answers.
The reason it didn't work is because each where clause is working on a subset of data that the previous one put out.
Use a list for your LINQ query's boolean logic.
List<int> ids = new List<int>{ 10, 17, 13, 7 };
_Hours = Rep.Where(o => ids.Contains(o.Projects.ProjectGroupID)).Where(o => o.Projects.ProjectType == 1).Sum(o => (decimal?)o.TaskEfforts) ?? default(int);
By manipulating the boolean this way, you can essentially convert a list of ints into a list of objects.
For your second where clause, I see you're needing a different attribute to restrict the list even further. This would work, since each where clause operates on the results of the first, but for the reader's sake it should be a &&.
_Hours = Rep.Where(o => ids.Contains(o.Projects.ProjectGroupID) && o.Projects.ProjectType == 1).ToList();
You're on the right track! As mentioned in the comments, you can use the || (OR) operator to select the o object if the id matches. In this case, you only need one Where. Here is your example written out:
_Hours = Rep.Where(o => o.Projects.ProjectGroupID == 4 || o.ProjectDescriptionID == 10 || o.ProjectDescriptionID == 17 || o.ProjectDescriptionID == 18 || o.ProjectDescriptionID == 19 || o.ProjectDescriptionID == 21 || o.ProjectDescriptionID == 24 || o.ProjectDescriptionID == 26).Where(o => o.Projects.ProjectType == 1).Sum(o => (decimal?)o.TaskEfforts) ?? 0m,
Edit: Didn't realize ProjectType property check at end of ORs. Made it it's own WHERE selector
i am very new with C# and MVC.
My Problem:
I have a list OF IDs
int[] mylist = {10, 23}
I try to query some data from DB
var result = db.tableName.Where(o => mylist.Any(y => y == o.item_ID && o.readed)).ToList();
This is what I get with the query:
item_ID Product_ID readed
277 1232 1
277 1233 1
277 1235 1
280 1235 1
What I need is:
item_ID Product_ID readed
277 1235 1
280 1235 1
If I change "any" to "all" i don't get any results, but I have definitely one item where the condition fits.
I think its more like make a query with id 277, then a query with 280 and then merge the list and return only where where "Product_ID" match.
Any ideas?
I assume that what you need is this:
var temp = db.tableName.Where(o => mylist.Any(y => y == o.item_ID && o.readed))
.ToList();
// Find the Product_id which appeared more than one time
// By the way, this assumes that there is at least one product_Id whihc has appeared more than one time
var neededProductID = temp.GroupBy(x => x.Product_ID)
.Where(x => x.Count() > 1)
.First()
.Key;
// Filter the result by neededProductID
var result = temp.Where(x => x.Product_ID == neededProductID).ToList();
Also, if there could be more tha one Product_ID which has apperaed more than one time, then you can consider this:
var neededProductID = temp.GroupBy(x => x.Product_ID)
.Where(x => x.Count() > 1)
.Select(x => x.Key)
.ToList();
var result = temp.Where(x => neededProductID.Any(y => y == x.Product_ID)).ToList();
By the way, you don't need All(). It tells you if all the elements in a collection match a certain condition.
You can use the following
var result = db.tableName.Where(o => mylist.conains(o.item_ID)
&& o.readed).ToList();
I know this is asked many times and I've searched most of the solutions online, but nothing seems to make it for me. I have a table with this structure:
ID | ScheduleId | Filename | Description
1 | 10 | | ....
2 | 10 | test.txt | .....
I want to get the last non-empty Filename by passing the ScheduleId(e.g. to get "test.txt" in this case).
I've tried many things and nothing seems to get me the Filename. Here is the last one:
var tempFileName = objContext.SchedulesAndFiles
.Where(x => x.ScheduleId == scheduleId)
.OrderByDescending(x => x.ScheduleId)
.Take(1).Select(x => x.Filename);
This doesn't work as well, although I understand why it doesn't:
var tempFileName = from e in objContext.SchedulesAndFiles
where e.ScheduleId == scheduleId
orderby e.ScheduleId descending
select e.Filename;
Calling .Last() or .LastOrDefault() throws an exception(The query operator 'LastOrDefault' is not supported.)
if have to include that you want only non-empty filenames. You may also use ToList() to finalize the query, then FirstOrDefault() should work as expected, try
var tempFileName = objContext.SchedulesAndFiles
.Where(x
=> x.ScheduleId == scheduleId
&& x.Filename != null
&& x.Filename != "")
.OrderByDescending(x => x.ScheduleId)
.Take(1)
.Select(x => x.Filename)
.ToList()
.FirstOrDefault();
You should sort your records based on the ID instead of ScheduleId and also filter the records that has the empty Filename:
objContext.SchedulesAndFiles
.Where(x => x.ScheduleId == scheduleId && x.Filename != "")
.OrderByDescending(x => x.ID)
.First().Filename;
One option is to call ToList() or AsEnumerable() before trying to use LastOrDefault().
var tempFileName = objContext.SchedulesAndFiles
.Where(x => x.ScheduleId == scheduleId
&& x.Filename != null && x.Filename != '')
.ToList().LastOrDefault();
if(tempFileName != null)
{
// Do something
}
You can try this query. I think you have to issue the last or default before selecting the file name
var tempFileName = objContext.SchedulesAndFiles
.Where(x => x.ScheduleId == scheduleId && ! string.IsNullOrEmpty(e.FileName))
.FirstOrDefault().Select(x => x.Filename);
The final attempt:
var tempFileName = objContext.SchedulesAndFiles
.Where(x
=> x.ScheduleId == scheduleId
&& x.Filename != null
&& x.Filename != "")
.OrderByDescending(x => x.ID)
.First()
.Select(x => x.Filename);
For every item with this scheduleId, this gets the all of the items which have a non-empty fileName, sorted descending by ID (assuming that higher IDs are inserted after lower IDs), getting the First() (should be supported) and getting its FileName.
Note that you could run into a NullPointerException on First() if there is no satisfying fileName.
Also, you may need to normalize/trim to not find spaces/tabs and such things.
I have a List that contains Supplier data and I would like to search it by using SupplierID, non-active supplier and only 1 latest result.
So I've got:
List<Supplier> filteredList = this.toList();
filteredList.OrderByDescending(m => m.ModifiedDatetime).FirstOrDefault();
filteredList.Where(f => (f.Active == false && f.FieldId == SupplierFieldID))
.ToList<Supplier>();
But I can't make this work; please help.
You need to chain your LINQ expressions, like this:
var filteredList = unfilteredData
.Where(f => f.Active == false && f.FieldId == SupplierFieldID)
.OrderByDescending(m => m.ModifiedDatetime)
.FirstOrDefault();
You do not need a ToList(), because you need a single item, not a list; this is what FirstOrDefault() does. If you need the last item, you need to order by the reverse of your original ordering condition. For example, if you would like the entry with the latest modified date, you need to order by descending (as you did).
You can do this in one statement, chaining together the LINQ operators:
var filteredList = myList.Where(f => f.Active == false && f.FieldId == SupplierFieldID)
.OrderByDescending(m => m.ModifiedDatetime)
.Take(1);
or as #Preston Guillot suggested, the even shorter form:
var filteredList = unfilteredData
.OrderByDescending(m => m.ModifiedDatetime)
.FirstOrDefault(f => f.Active == false && f.FieldId == SupplierFieldID);