Sorting a dictionary via multiple parameters - c#

I have a dictionary IDictionary answersDictionary<Country, List<Answer>>, where Country contains information on country and List<Answer> is a list of Answer objects, which have a property State that can be final, draft, comment.
I need to sort the dictionary in a way, that its sorted by:
Number of draft answers for country
Number of comment answers for country
By Country.Name
And, at the end get only the keys of the dictionary.
I have done the following:
IDictionary<Country, List<Answer>> answersDictionary = Database.Answer
.GroupBy(a => a.Country).ToDictionary(d => d.Key, d => d.ToList());
answersDictionary.OrderByDescending(d => d.Value.Where(a => a.State == AnswerState.Draft).Count())
.ThenByDescending(d => d.Value.Where(a => a.State == AnswerState.Comment).Count())
.ThenBy(d => d.Key.Name);
And at the end List<Country> finalResult=answersDictionary.Keys;
But, the results are not correct, as they are returned sorted only by Draft and in ascending order. Any suggestions, what's the issue?

You have to assign your second query to a variable. Try this:
IDictionary<Country, List<Answer>> answersDictionary = Database.Answer
.GroupBy(a => a.Country).ToDictionary(d => d.Key, d => d.ToList());
var answerDict = answersDictionary.OrderByDescending(d => d.Value.Count(a => a.State == AnswerState.Draft))
.ThenByDescending(d => d.Value.Count(a => a.State == AnswerState.Comment))
.ThenBy(d => d.Key.Name)
.ToDictionary(d => d.Key, d => d.Value);
List<Country> finalResult = answerDict.Keys.ToList();

Related

c# linq filter group by max composite key

I have a linq query:
var reports = await dbContex.ShoppingListPatientReports
.Where(report => patientIds.Contains(report.PatientId))
.GroupBy(report => new { report.PatientId, RecentDate = DbFunctions.TruncateTime(report.DateCreated) })
.OrderByDescending(g => g.Key)
.ToListAsync();
it returns groups sorted in descending order by the composite key (PatientId, RecentDate).
Keys:
10004, 2021-02-03
10004, 2021-01-01
10004, 2021-02-02
10002, 2021-01-05
10002, 2021-01-06
can I somehow take only the groups with the largest key (PatientId, RecentDate)
i.e groups with most recent dates (In this example, the result should be two groups):
10004, 2021-02-03
10002, 2021-01-06
You need to add a Where after your GroupBy:
.Where(grp =>
grp.OrderByDecending(x => x.Key.RecentDate).First().PatientId == grp.Key.PatientId)
Full query:
var reports = await dbContex.ShoppingListPatientReports
.Where(report => patientIds.Contains(report.PatientId))
.GroupBy(report => new
{
report.PatientId,
RecentDate = DbFunctions.TruncateTime(report.DateCreated)
})
.Where(grp =>
grp.OrderByDecending(x => x.Key.RecentDate).First().PatientId == grp.Key.PatientId)
.OrderByDescending(g => g.Key)
.ToListAsync();
Maybe It will be useful for you. It works with list of elements, but I've never tried it with EF.
var reports = new List<ShopingPatientReport>() { ... }
.Where(report => patientIds.Contains(report.PatientId))
.GroupBy(report => report.PatientId)
.Select(report => report.GroupBy(x => DbFunctions.TruncateTime(report.DateCreated))
.OrderByDescending(f => f.Key).First()).OrderByDescending(g => g.Key)
.ToList()
Here is a query that works for me:
await dbContex.ShoppingListPatientReports
.Where(report => patientIds.Contains(report.PatientId))
.GroupBy(report => report.PatientId)
.Select(patientShoppingList =>
new
{
patientId = patientShoppingList.Key,
//Grouping reports by created date and take group wiht the most recent date
reports = patientShoppingList.GroupBy(report => DbFunctions.TruncateTime(report.DateCreated))
.OrderByDescending(group => group.Key)
.FirstOrDefault()
})
.SelectMany(g => g.reports.Select(r => r))
.ToListAsync();

LINQ to SQL - order by, group by and order by each group with skip and take

This is an extension of already answered question by Jon Skeet that you can find here.
The desired result is following:
A 100
A 80
B 80
B 50
B 40
C 70
C 30
considering you have following class:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
to get to the result (in ideal scenario) can be done with Jon Skeet's answer:
var query = grades.GroupBy(student => student.Name)
.Select(group =>
new { Name = group.Key,
Students = group.OrderByDescending(x => x.Grade) })
.OrderBy(group => group.Students.FirstOrDefault().Grade);
However in my case I have to support paging in my query as well. This means performing SelectMany() and then do Skip() and Take(). But to do Skip() you have to apply OrderBy(). This is where my ordering breaks again as I need to preserve the order I get after SelectMany().
How to achieve this?
var query = grades.GroupBy(student => student.Name)
.Select(group =>
new { Name = group.Key,
Students = group.OrderByDescending(x => x.Grade) })
.OrderBy(group => group.Students.FirstOrDefault().Grade).SelectMany(s => s.Students).OrderBy(something magical that doesn't break ordering).Skip(s => skip).Take(t => take);
I know I could manually sort again the records when my query is materialised but I would like to avoid this and do all of it in one SQL query that is translated from LINQ.
You can take another approach using Max instead of ordering each group and taking the first value. After that you can order by max grade, name (in case two students have the same max grade) and grade:
var query = c.Customers
.GroupBy(s => s.Name, (k, g) => g
.Select(s => new { MaxGrade = g.Max(s2 => s2.Grade), Student = s }))
.SelectMany(s => s)
.OrderBy(s => s.MaxGrade)
.ThenBy(s => s.Student.Name)
.ThenByDescending(s => s.Student.Grade)
.Select(s => s.Student)
.Skip(toSkip)
.Take(toTake)
.ToList();
All these methods are supported by EF6 so you should get your desired result.
Just re-index your list results and remove the index before returning.
var query = grades.GroupBy(student => student.Name)
.Select(group =>
new { Name = group.Key,
Students = group.OrderByDescending(x => x.Grade)
})
.OrderBy(group => group.Students.FirstOrDefault().Grade)
.SelectMany(s => s.Students)
.Select((obj,index) => new {obj,index})
.OrderBy(newindex => newindex.index)
.Skip(s => skip).Take(t => take)
.Select(final=> final.obj);

How to include two object inside the same select on Linq MVC5

I do have a complex query to select a full object called Performance
The Performance relationship with others objects is:
Performance has a list of Index
Index has a list of SubIndex
SubIndex has a list of Indicator
Indicator has a list of Item
Item relationship with Spot and Measurement:
Item has one Spot and one Measurement
The query below returns exactly what I want, but I would like to include the Spot and Measurement to the Item object.
return _context.Performance.Include(i => i.Indexes
.Select(s => s.SubIndexes
.Select(d => d.Indicators
.Select(t => t.Items))))
.SingleOrDefault(p => p.Id == id);
I have tried the query below and it is returning the Measurement object. How to include the Spot object?
return _context.Performance.Include(i => i.Indexes
.Select(s => s.SubIndexes
.Select(d => d.Indicators
.Select(t => t.Items.Select(tm => tm.Measurement)))))
.SingleOrDefault(p => p.Id == id);
You could add second Include too;
return _context.Performance.Include(i => i.Indexes
.Select(s => s.SubIndexes
.Select(d => d.Indicators
.Select(t => t.Items.Select(tm => tm.Measurement)))))
.Include(i => i.Indexes
.Select(s => s.SubIndexes
.Select(d => d.Indicators
.Select(t => t.Items.Select(tm => tm.Spot)))))
.SingleOrDefault(p => p.Id == id);

How to order groupBy items

Having this Linq query which returns grouping of 4 DateTime:
IEnumerable<IGrouping<DateTime, QuoteSnapshotModel>> lista = (from q in quoteModeList
where q.QuoteTradeType == "Q"
select q).GroupBy(n => n.ExceriseDate);
How can I order the groups by DateTime and get only the first group?
meaning **List<QuoteSnapshotModel>**
Also, how can I get only the second List<QuoteSnapshotModel> (according to DateTime)
Try this:
for this you have to create a list<QuoteSnapshotModel> within class QuoteSnapshotModel.
int record = 1;
List<QuoteSnapshotModel> result =
quoteModeList
.Where(x => x.QuoteTradeType == "Q")
.GroupBy(x => x.ExceriseDate,
(a, b) => new QuoteSnapshotModel
{
ExceriseDate = a,
ListQuoteSnapshotModel = b.ToList()
})
.OrderByDescending(t => t.ExceriseDate)
.Skip(record - 1).Take(1).ToList();
Update
You can use only the first group
List<QuoteSnapshotModel> list =quoteModeList.Where(x=>x.QuoteTradeType =="Q")
.GroupBy(x => x.ExceriseDate)
.OrderBy(x=>x.Key)
.FirstOrDefault().Select(x=>x.QuoteTradeType).ToList();
You can use only the second group
List<QuoteSnapshotModel> list =quoteModeList.Where(x=>x.QuoteTradeType =="Q")
.GroupBy(x => x.ExceriseDate)
.OrderBy(x=>x.Key)
.Skip(1).First().Select(x=>x.QuoteTradeType).ToList()
How can I order the groups by DateTime
.OrderBy(g => g.Key) or .OrderBy(g => g.First().ExerciseDate)
and get only the first group?
.First() (or .FirstOrDefault() if it's possible there are 0 groups)
how can I get only the second List (according to DateTime)
.Skip(1).Take(1) or .ElementAt(1) or .Skip(1).First()
Putting it all together:
IEnumerable<IGrouping<DateTime, QuoteSnapshotModel>> lista = (
from q in quoteModeList
where q.QuoteTradeType == "Q"
select q
).GroupBy(n => n.ExerciseDate);
IList<QuoteSnapshotModel> firstQuote = lista.OrderBy(x => x.Key).Select(x => x.ToList()).FirstOrDefault();
IList<QuoteSnapshotModel> secondQuote = lista.OrderBy(x => x.Key).Skip(1).Select(x => x.ToList()).FirstOrDefault();

Query entity and return back a dictionary

I'm trying to query a view (entity) from the database and return back a dictionary. There are duplicates in the view so I tried groupby and I can't figure it out.
var queryresults = db.MyView.Where(x => x.year == myYear)
.GroupBy(g => new { g.myCode, g.myCodeName})
.ToDictionary(d => d.myCode, d => d.myCodeName);
You should group by dictionary key property if you want to avoid duplicate keys error. Then you can select code name of first item in each group as dictionary entry value:
var queryresults =
db.MyView.Where(x => x.year == myYear)
.GroupBy(x => x.myCode)
.ToDictionary(g => g.Key, g => g.First().myCodeName);

Categories