Entity Framework group by and get max - c#

Lets say I've table with the structure as below:
MyRow:
Id Name Date
1 A 2015/01/01
2 B 2015/01/01
3 C 2015/01/02
4 A 2015/01/03
5 B 2015/01/03
6 A 2015/01/02
7 C 2015/01/01
Using EF I would like to get list of MyRow which would contain elements with distinct names and newest date so in this case it would be:
4 A 2015/01/03
5 B 2015/01/03
3 C 2015/01/02
I started with something like this:
var myRows = context.MyRows.GroupBy(mr => mr.Name).Select(..now wth with max..)

Order each group and take the last of each.
Or since EF doesn't (last time I checked) support Last(), order each group in reverse and take the first:
var myRows = context.MyRows
.GroupBy(mr => mr.Name)
.Select(grp => grp.OrderByDescending(mr => mr.Date).First());

var data = context.MyRows.Group(p => p.Name)
.Select(g => new {
Type = g.Key,
Date = g.OrderByDescending(p => p.Date)
.FirstOrDefault()
}

Related

EF Core header-detail query optimization

I have a header-detail relation in my SQL Server database. I have around 10k headers, each of them having 1-1k details. And the number of unique elements is about 1k.
Elements [id]
1
2
3
Headers [id]
1
2
3
Details [id, header_id, element_id]
1 1 1
2 1 2
3 1 3
4 2 1
5 3 1
It's very easy to query a list of headers with their details with such structure:
var searchHeaderIds = new List<int>{1,2,3};
var headers = context.Headers
.Where(h => searchHeaderIds.Contains(h.Id))
.Include(h => h.Details)
.ToList();
But what I want to query is a list of elements (1-200) where every element has a list of headers it belongs to (something like an inversion). I can write it in C# as below:
var searchElementIds = new List<int>{1,2,3};
var headers = context.Details
.Where(d => searchElementIds.Contains(d.element_id))
.GroupBy(d => d.element_id)
.Select(g => new {
id = g.Key,
header_ids = g.Select(x => x.header_id) })
.ToList();
But I wonder, what will be the fastest way to do it using the power of SQL/EF?
UPD: I'm ready to use extra data structures, preprocess the data in the database, or do anything else to improve performance.
what about:
var searchElementIds = new List<int>{1,2,3};
var headers = (
from header in context.Headers
join detail in context.Details on header.id equals detail.header_id
where searchElementIds.Contains(detail.element_id)
select header).Distinct();
If you want instances of the Element class:
var headers =
context.Details
.Where(d => searchElementIds.Contains(d.element_id))
.GroupBy(d => d.element_id)
.Select(g => new Element
{
id = g.Key,
header_ids = g.Select(x => x.header_id
})
.ToList();
Don't cal ToList() in the middle of your query.
This is most optimal query in your case. It is closer to original post, but reduced number of retrieved fields for intermediate result:
var headers = context.Details
.Where(d => searchElementIds.Contains(d.element_id))
.Select(d => new { d.element_id, d.header_id })
.ToList() // we need this, EF do not support retrieving grouping detals
.GroupBy(d => d.element_id)
.Select(g => new Element
{
id = g.Key,
header_ids = g.Select(x => x.header_id).ToList()
})
.ToList();

LINQ: Select a column based on another column value

After executing some LINQs, I have this data as IQueryable of an anonymous type:
Cat Type Count
----------------
A 1 10
B 1 15
C 1 25
D 1 5
A 2 15
C 2 30
D 2 20
B 2 10
Now, for every Cat I want to select Count based on Type values with LINQ.
Output should be like this:
Cat Type1Count Type2Count
---------------------------
A 10 15
B 15 10
C 25 30
D 5 20
You can try to use groupby then select to do pivot.
var query = myList
.GroupBy(c => c.Cat)
.Select(g => new {
Cat = g.Key,
Type1Count = g.Where(a => a.Type == 1).Sum(c => c.Count),
Type2Count = g.Where(a => a.Type == 2).Sum(c => c.Count)
});
c# online
Result
Cat:A Type1Count:10 Type2Count:15
Cat:B Type1Count:15 Type2Count:10
Cat:C Type1Count:25 Type2Count:30
Cat:D Type1Count:5 Type2Count:20
So basically you need to group by Cat and Type and get sum of Count:
myList.GroupBy(x => new { x.Cat, x.Type }).Select(x => new { Cat = x.Key.Cat, Type = x.Key.Type, CountForTypeAndCat = x.Sum(y=>y.Count) })
This will allow you go get information for any Type not only 1 and 2
foreach (var item in query.OrderBy(x=>x.Cat).ThenBy(x=>x.Type))
{
Console.WriteLine(string.Format("Cat:{0} Type:{1} CountForTypeAndCat:{2}", item.Cat, item.Type, item.CountForTypeAndCat));
}

Linq Group by Values in a list

So Currently I have this TrackingInfo class which contains an ID and a list of EmailActionIDs, which is an int.
I have a List Of this class which the data looks like:
ID, | EmailActions
_______________
A | 1, 3, 5
B | 3, 5, 6
C | 2, 4, 6
I'm trying to write a Linq Statement To convert this into A list of IDs grouped by each individual value in the list.
So the Results Set would look like:
ID | Values
_______________
1 | A
2 | C
3 | A, B
4 | C,
5 | A, B
6 | B, C
I can't figure out how I would write the group by can anyone give me some insight.
DistinctValues = new List<int> {1,2,3,4,5,6};
TrackingInfo.Groupby(t => DistinctValues.foreach(d =>
t.EmailActions.Contains(d))).Tolist()
This ofcourse isn't working any suggestions on how to do this using Linq
Its easy enough to get a distinct list of EmailActions
var distinctEmailActions = items.SelectMany(i => i.EmailActions).Distinct();
Then pivoting this is a little complex, but here it is:
var result = distinctEmailActions.Select(e => new {
ID=e,
Values = items.Where(i => i.EmailActions.Contains(e)).Select(i => i.ID)
});
Live example: http://rextester.com/CQFDY66608
What you're looking for is SelectMany, but it's easier to use query syntax here:
var result = from item in source
from action in item.EmailActions
group item.ID by action into g
select new { ID = g.Key, Values = g.ToList() }
You can do it by first generating a range using Enumerable.Range, and then matching EmailActions, like this:
var res = Enumerable.Range(1, 6)
.SelectMany(v => TrackingInfo.Where(info => info.EmailActions.Contains(v)).Select(info => new { Id, Value = v }))
.GroupBy(p => p.Value)
.Select(g => new {
Id = g.Key
, Values = g.Select(p => p.Id).ToList()
});
You can achieve this using SelectMany & GroupBy like this:-
var result = tracking.SelectMany(x => x.EmailActionIDs,
(trackObj, EmailIds) => new { trackObj, EmailIds })
.GroupBy(x => x.EmailIds)
.Select(x => new
{
ID = x.Key,
Values = String.Join(",", x.Select(z => z.trackObj.ID))
}).OrderBy(x => x.ID);
Working Fiddle.

How to use groupby in linq sql

I am trying to group a list and using ToDictionary to achieve this which works:
var levels = ids.GroupBy(f => f.Id).
ToDictionary(g => g.Key, g => g.First().Name);
The problem is: in the string "Name" the last char is a number i.e. 2 or 5 or 7 etc.
I do NOT want to select the first but I want to select "Name" with the MAX number. How can i achieve this. example of Name can be: "abd-hbb-les3" , "abd-hbb-les1" , "abd-hbb-les6"
You could do this in the following way:
var levels = ids.GroupBy(f => f.Id).ToDictionary(g => g.Key,
g => g.First( x=> x.Name.Last() == g.Max( y=> y.Name.Last())).Name);
assuming that it's really about the last letter so it's not possible to have a two (or more) digits at the end e.g.:
abd-hbb-les16 //will not work with the above code
For every group simply select the name with the maximum last character of the string. Like this:
var levels = ids.
GroupBy(f => f.Id).
ToDictionary(
g => g.Key,
g => g.First(i => i.Name.Last() == g.Max(j => j.Name.Last())).Name);

Remove Duplicate based on column value-linq

i have many to many relationship between employee and group. following linq statement
int[] GroupIDs = {6,7};
var result = from g in umGroups
join empGroup in umEmployeeGroups on g.GroupID equals empGroup.GroupID
where GroupIDs.Contains(g.GroupID)
select new { GrpId = g.GroupID,EmployeeID = empGroup.EmployeeID };
returns groupid and the employeeid. and result is
GrpId | EmployeeID
6 | 18
6 | 20
7 | 19
7 | 20
I need to remove the rows for which the employeeid is repeating e.g. any one of the row with employeeid= 20
Thanks
Okay, if you don't care which employee is removed, you could try something like:
var result = query.GroupBy(x => x.EmployeeId)
.Select(group => group.First());
You haven't specified whether this is in LINQ to SQL, LINQ to Objects or something else... I don't know what the SQL translation of this would be. If you're dealing with a relatively small amount of data you could always force this last bit to be in-process:
var result = query.AsEnumerable()
.GroupBy(x => x.EmployeeId)
.Select(group => group.First());
At that point you could actually use MoreLINQ which has a handy DistinctBy method:
var result = query.AsEnumerable()
.DistinctBy(x => x.EmployeeId);

Categories

Resources