How do you create nested groups with linq via lambda/method expressions - c#

Is it possible? For example, how would you rewrite the working code below with a lambda expression?
public void QueryNestedGroups()
{
var queryNestedGroups =
from student in students
group student by student.Year into newGroup1
from newGroup2 in
(from student in newGroup1
group student by student.LastName)
group newGroup2 by newGroup1.Key;
}
// Three nested foreach loops are required to iterate
// over all elements of a grouped group. Hover the mouse
// cursor over the iteration variables to see their actual type.
foreach (var outerGroup in queryNestedGroups)
{
Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key);
foreach (var innerGroup in outerGroup)
{
Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key);
foreach (var innerGroupElement in innerGroup)
{
Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName);
}
}
}

var names = myClass1List
.SelectMany(c1 => c1.Class2List.Where(c2 => c2.Name == "something"))
.SelectMany(c2 => c2.Class3List.Select(c3 => c3.Name));
var names = myClass1List
.SelectMany(c1 => c1.Class2List
.Where(c2 => c2.Name == "something")
.SelectMany(c2 => c2.Class3List
.Select(c3 => c3.Name)));
See more: LINQ on List with lot of nested List

I think you mean method syntax:
var queryNestedGroups = students.GroupBy(x=>x.Year, (key,g1)=>
g1.GroupBy(x=>x.LastName, (key2,g2)=>
g2.GroupBy(x=>x.Year)));//Because the group1.Key is exactly Year;
If you don't want to use Year hard-codedly. Try this instead:
var queryNestedGroups = students.GroupBy(x=>x.Year, (key,g1)=>
g1.Select(x=>new{key,x})
.GroupBy(x=>x.x.LastName, (key2,g2)=>
g2.GroupBy(x=>x.key, x=>x.x)));

It's too late to post on this question but since there's no answer been posted I'm leaving a post here for those landing here seeking similar problem. I had this same problem converting similar linq query to lambda expression.
Here's how you can do it:
var res1 = students.GroupBy(th=>th.Year)
.SelectMany (grp1 => grp1.GroupBy (th => th.LastName), (grp1, grp2) => new {grp1 = grp1, grp2 = grp2})
.GroupBy (temp0 => temp0.grp1.Key, temp0 => temp0.grp2);
see the original post which helped figuring out the solution.

Related

Select and group by string occurrences using LINQ?

I have some data that I would like to count and group by. It looks like this:
Cat Cat Dog Dog Dog
My goal is to get a list of objects that have the name of the animal and the number of times it appears in the data set
Object 1
Name: Cat
NumberAppearances: 2
Object 2
Name: Dog
NumberAppearances: 3
I am trying to do this with a LINQ query and this is what I have but the count is wrong. I think it's counting the length instead of the number of times it appears. How would I modify this?
animalData.Select(x => x.AnimalType).Distinct().ToList().ForEach(a =>
AnimalObject animal = new AnimalObject();
animal.Name = a.Name;
animal.Number = a.Distinct().Count();
animalList.Add(animal);
});
This is all you need to accomplish your task:
var result = animalData.GroupBy(x => x.AnimalType).Select(g => new AnimalObject
{
Name = g.Key,
Number = g.Count()
}).ToList();
foreach (var e in result)
Console.WriteLine($"Name: {e.Name} \n NumberAppearances: {e.Number}");
note -
as your LINQ query is currently written, you need not call distinct nor ToList as the former will result
in the incorrect outcome and the latter is unnecessary.
stick to good naming conventions, you don't need the Object suffix after the type name so just Animal will suffice.
You can use this one without creating a new object, with the help of Dictionary:
var animalCounts = animalList.GroupBy(a => a.AnimalType).ToDictionary(
grp => grp.Key, grp => grp.ToList().Count());
foreach(var animalCount in animalCounts) {
Console.WriteLine($"Name: {animalCount.Key}{Environment.NewLine}NumberOfApperances: {animalCount.Value}");
}
Group the data by AnimalType, then transform it to AnimalObject.
var result = animalData.GroupBy(x => x.AnimalType)
.Select(g => new AnimalObject { Name = g.Key, Number = g.Count() })
.ToList();

Group inside group linq

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.

Translating VB.NET LINQ Query with Sum to C# LINQ Query

i have a LINQ Query which is written in VB.NET:
foo = (From p In lOrder
Group By p.ItemCode Into Group = Sum(p.Quantity)
).ToDictionary(Function(x) x.ItemCode, Function(x) x.Group)
My problem is to translate the Sum. C# seems to not like it very well.
Can anybody help me translating this query?
thank you
Here's an exact translation of your LINQ query in C#:
var foo = lOrder.GroupBy(p => p.ItemCode,
(key, values) => new
{
ItemCode = key,
Group = values.Sum(v => v.Quantity)
})
.ToDictionary(x => x.ItemCode, x => x.Group);
If I understand correctly, you want to get a mapping between the item code to the cumulative quantity of the items under this code.
Given an IEnumerable named orders you can do the following:
var foo = from p in orders
group p by p.ItemCode into OrderGroupedByCode
select new { Code = OrderGroupedByCode.Key, Sum = OrderGroupedByCode.Sum(x => x.Quentity) };
This gives you an IEnumerable of anonymous objects the you can easily turn into a dictionaryif you want:
foo.ToDictionary(x => x.Code, x => x.Sum)

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();

Group items and select specific item from each group with LINQ

There has got to be a one-liner to do this, and I just can't find it.
Given this query:
from x in new XPQuery<XPContent>(s)
select new { x.Category, x.ContentType, x.Name, x.ContentID, x.Date }
I need to select the record with the greatest date for each distinct ContentID. Can this be done cleverly with LINQ? Right now I'm doing this:
var q = (from x in new XPQuery<XPContent>(s)
select new { x.Category, x.ContentType, x.Name, x.ContentID, x.Date }).ToList();
var r = q.ToLookup(item => item.ContentID);
foreach (var rItem in r) {
var s = rItem.OrderByDescending(a => a.Date).First();
/* do stuff with s */
}
... but the ToLookup feels kind of clunky. Or do I have the best (simplest) solution?
Also, I know I shouldn't be using ToList, but please just ignore that for the time being.
Thanks in advance!
I think you want:
var q = from x in new XPQuery<XPContent>(s)
group x by x.ContentID into g
let latest = g.OrderByDescending(a => a.Date).First()
select new
{
latest.Category, latest.ContentType,
latest.Name, latest.ContentID, latest.Date
};
(Do note that there are more performant ways of finding the 'maximum' element from a group than by sorting it first, for example with a MaxBy operator.)
The query is quite simple; it just groups items by their ContentId, and then from each group, selects an instance of an anonymous type that is produced from the group's latest item.

Categories