Group inside group linq - c#

I have a datatable like this:
I want to group this table For FIELD A and FIELD B,
and the third field of my group should be lists of FIELD C, but it must be grouped by ID field.
At the end, the result should be like this:
First Field | Second Field | Third Field
------------+--------------+----------------
5 | XXXX |(8) (2,6,3) (9)
5 | KKKK |(8,3)
The third field must be a list of lists.
How can i do this with LINQ?
I tried this so far:
var trytogroup = (from p in datatable.AsEnumerable()
group p by new
{
ID = p["Id"].ToLong(),
FieldA = p["FieldA"].ToLong(),
FieldB = p["FieldB"].ToString()
} into g
select new
{
FirstField = g.Key.FieldA,
SecondField = g.Key.FieldB,
ThirdField = datatable.AsEnumerable().Where(p => p["FieldA"].ToLong() == g.Key.FieldA && p["FieldB"].ToString() == g.Key.FieldB).Select(p => p["FieldC"].ToLong()).GroupBy(x => x["Id"].ToLong()).Distinct().ToList()
});

What's wrong with your query:
You don't need to group by three fields on first place. Grouping by ID should be done within group which you have by FieldA and FieldB
When getting ThirdField you don't need to query datatable again - you already have all required data. You just need to add grouping by ID
Correct query:
from r in datatable.AsEnumerable()
group r by new {
FieldA = r.Field<long>("FieldA"),
FieldB = r.Field<string>("FieldB")
} into g
select new
{
First = g.Key.FieldA,
Second = g.Key.FieldB,
Third = g.GroupBy(r => r.Field<long>("ID"))
.Select(idGroup => idGroup.Select(i => i.Field<long>("FieldC")).ToList())
}

If you prefere lambdas, your query could look like:
dataSource
.GroupBy(item => new { item.FieldA, item.FieldB })
.Select(group => new
{
First = group.Key.FieldA,
Second = group.Key.FieldB,
Third = group.GroupBy(q => q.Id).Select(q => q.Select(e => e.FieldC).ToArray()).ToArray()
}).ToArray();

Just few small notes. .GroupBy uses Lookup to get the Groupings, so some overhead can be avoided by replacing .GroupBy( with .ToLookup( when deffered execution is not needed.
The elements in each Grouping are stored in array, so I don't see much use in converting them .ToList (but you can save a bit of space if you convert them .ToArray).
DataTable.AsEnumerable uses .Rows.Cast<TRow>(), but also seems to do some extra stuff when there is any DataView sorting or filtering that are usually not needed.
var groups = datatable.Rows.Cast<DataRow>()
.ToLookup(r => Tuple.Create(
r["FieldA"] as long?,
r["FieldB"]?.ToString()
))
.Select(g => Tuple.Create(
g.Key.Item1,
g.Key.Item2,
g.ToLookup(r => r["ID"] as long?, r => r["FieldC"] as long?)
)).ToList();
As usual, premature optimization is the root of all evil but I thought the information might be useful.

Related

Return all fields, Group by And Aggregate functions in lambda

I have such a table as shown below
I need to group each record by LifePluseCaseId column,
and each group must be selected by Min distance and Min Duration
And return All of the fields.
I have tried this:
var query = db.ApplicantCenterDistance.GroupBy(s => s.LifeplusCaseId)
.Select(s => new {
Id = s.Key,
MinDistance = s.Min(m => m.Distance),
Duration = s.Min(m => m.Duration)
}).ToList();
But i don't know how to get all fields in Select statement and what the role of key is.
Is s.Key equals to s.LifeplusCaseId?
From the above queries, each group will have multiple ApplicantCenterDistances record (in theory) because... well it's a group. If you really want to get all the items in each group as well, you can do like this (pseudo-code):
var productByCategory = await db.Products
.GroupBy(q => q.CategoryId)
.Select(q => new {
CategoryId = q.Key,
// Here q is also acting as a list of products with the same `CategoryId`
Products = q,
// Or if you only want some specific fields
ProductCustoms = q.Select(p => new {
Name = p.Name,
Color = p.Color,
// All fields you want
})
}).ToListAsync(); // Do whatever you want with the result
Now it makes much more sense right? The productByCategory is a list of groups, each group has the key (CategoryId), and has a list of products that has that matching CategoryId.

Selecting fields in grouping

I have data like the following inside my DataTable:
id vrn seenDate
--- ---- --------
1 ABC 2017-01-01 20:00:05
2 ABC 2017-01-01 18:00:09
3 CCC 2016-05-05 00:00:00
I am trying to modify the data to only show vrn values with the most recent date. This is what I have done so far:
myDataTable.AsEnumerable().GroupBy(x => x.Field<string>("vrn")).Select(x => new { vrn = x.Key, seenDate = x.Max(y => y.Field<DateTime>("seenDate")) });
I need to modify the above to also select the id field (i.e. I do not want to group on this field, but I want to have it included in the resulting data set).
I cannot put in x.Field<int>("id") in the Select() part, as the Field clause does not exist.
You need an equivalent of MaxBy method from MoreLINQ.
In standard LINQ it can be emulated with OrderByDescending + First calls:
var result = myDataTable.AsEnumerable()
.GroupBy(x => x.Field<string>("vrn"))
.Select(g => g.OrderByDescending(x => x.Field<DateTime>("seenDate")).First())
.Select(x => new
{
vrn = x.Field<string>("vrn"),
id = x.Field<int>("id"),
seenDate = x.Field<DateTime>("seenDate"),
});
You can use select new like this to select anything you want from your data
var query = from pro in db.Projects
select new { pro.ProjectName, pro.ProjectId };
If you may have few ids in the same vrn with same max date then the following would work:
IEnumerable<DataRow> rows = myDataTable.AsEnumerable()
.GroupBy(x => x.Field<string>("vrn"))
.Select(x => new
{
Grouping = x,
MaxSeenDate = x.Max(y => y.Field<DateTime>("seenDate"))
})
.SelectMany(arg =>
arg.Grouping.Where(y => y.Field<DateTime>("seenDate") == arg.MaxSeenDate));
It will retrun an IEnumerable of the original DataRow so you have all your fields there.
Or you can add another select to have only the fields you need.

How to c# List<> order by and Group by according to parameters?

I have a class and its List
abc cs = new abc();
List<abc> Lst_CS = new List<abc>();
and I set some value by HidenField in foreach loop
foreach (blah blah)
{
cs = new abc{
No = VKNT,
GuidID=hdnGuidID.Value.ToString(),
RecID=hdnRecID.Value.ToString(),
Date=HdnDate.Value.ToString()
};
Lst_CS.Add(cs);
}
and finally I get a List_CS and I order by Lst_CS according to Date like this;
IEnumerable<abc> query = Lst_CS.OrderBy(l => l.Date).ToList();
but in extra, I want to group by according to No.
Briefly, I want to order by Date and then group by No on Lst_CS How can I do ?
Thanks for your answer
Well you just just do the ordering then the grouping like so:
Lst_CS.OrderBy(l => l.Date)
.GroupBy(l => l.No)
.ToList();
Each list of items in each group will be ordered by date. The groupings will be in the order that they are found when the entire list is ordered by date.
Also your ForEach can be done in one Linq statement, then combined with the ordering and grouping:
var query = blah.Select(b => new abc{
No = VKNT,
GuidID=hdnGuidID.Value.ToString(),
RecID=hdnRecID.Value.ToString(),
Date=HdnDate.Value.ToString()
})
.OrderBy(l => l.Date)
.GroupBy(l => l.No)
.ToList();

Use Linq to return first result for each category

I have a class (ApplicationHistory) with 3 properties:
ApplicantId, ProviderId, ApplicationDate
I return the data from the database into a list, however this contains duplicate ApplicantId/ProviderId keys.
I want to supress the list so that the list only contains the the earliest Application Date for each ApplicantId/ProviderId.
The example below is where I'm currently at, but I'm not sure how to ensure the earliest date is returned.
var supressed = history
.GroupBy(x => new
{
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.First();
All advice appreciated.
Recall that each group formed by the GroupBy call is an IGrouping<ApplicationHistory>, which implements IEnumerable<ApplicationHistory>. Read more about IGrouping here. You can order those and pick the first one:
var oldestPerGroup = history
.GroupBy(x => new
{
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.Select(g => g.OrderBy(x => x.ApplicationDate).FirstOrDefault());
You are selecting first group. Instead select first item from each group:
var supressed = history
.GroupBy(x => new {
ApplicantId = x.ApplicantId,
ProviderId = x.ProviderId
})
.Select(g => g.OrderBy(x => x.ApplicationDate).First());
Or query syntax (btw you don't need to specify names for anonymous object properties in this case):
var supressed = from h in history
group h by new {
h.ApplicantId,
h.ProviderId
} into g
select g.OrderBy(x => x.ApplicationDate).First();

Using LINQ on ObservableCollection with GroupBy and Sum aggregate

I have the following block of code which works fine;
var boughtItemsToday = (from DBControl.MoneySpent
bought in BoughtItemDB.BoughtItems
select bought);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(boughtItemsToday);
It returns data from my MoneySpent table which includes ItemCategory, ItemAmount, ItemDateTime.
I want to change it to group by ItemCategory and ItemAmount so I can see where I am spending most of my money, so I created a GroupBy query, and ended up with this;
var finalQuery = boughtItemsToday.AsQueryable().GroupBy(category => category.ItemCategory);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
Which gives me 2 errors;
Error 1 The best overloaded method match for 'System.Collections.ObjectModel.ObservableCollection.ObservableCollection(System.Collections.Generic.List)' has some invalid arguments
Error 2 Argument 1: cannot convert from 'System.Linq.IQueryable>' to 'System.Collections.Generic.List'
And this is where I'm stuck! How can I use the GroupBy and Sum aggregate function to get a list of my categories and the associated spend in 1 LINQ query?!
Any help/suggestions gratefully received.
Mark
.GroupBy(category => category.ItemCategory); returns an enumerable of IGrouping objects, where the key of each IGrouping is a distinct ItemCategory value, and the value is a list of MoneySpent objects. So, you won't be able to simply drop these groupings into an ObservableCollection as you're currently doing.
Instead, you probably want to Select each grouped result into a new MoneySpent object:
var finalQuery = boughtItemsToday
.GroupBy(category => category.ItemCategory)
.Select(grouping => new MoneySpent { ItemCategory = grouping.Key, ItemAmount = grouping.Sum(moneySpent => moneySpent.ItemAmount);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
You can project each group to an anyonymous (or better yet create a new type for this) class with the properties you want:
var finalQuery = boughtItemsToday.GroupBy(category => category.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});
The AsQueryable() should not be needed at all since boughtItemsToday is an IQuerable anyway. You can also just combine the queries:
var finalQuery = BoughtItemDB.BoughtItems
.GroupBy(item => item.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});

Categories