Nested GroupBy extension method in LINQ - c#

Can someone show me a a simple example of a nested GroupBy extension method. I am trying to group an object into a list. I figure it will look something like this:
//Code
List<stats> myStats = GetStatsList().GroupBy( d => d.date).GroupBy(t => t.team)
Select(s => new stats
{
Date =
Team =
}).AsEnumerable().ToList();

What about this:
List<stats> myStats = GetStatsList()
.GroupBy(s => new { s.date, s.team })
.Select(g => new stats { Date = g.Key.date , Team = g.Key.team})
.AsEnumerable()
.ToList();

I assume you want this:
List<stats> myStats = GetStatsList()
.GroupBy(s => new{ s.Date, s.Team})
.Select(g => new stats
{
Date = g.Key.Date,
Team = g.Key.Team,
}).ToList();
I'm grouping by an anonymous type with the two properties you want to use in different GroupBys. Then i can access them via Group.Key.PropertyName.

var myStats = (from s in GetStatsList()
group by new { Date = s.date, Team = s.team} into g
select g.Key).ToList();
Note that this gives you a List<T> where T is an anonymous type -- that might be sufficient. If you explicitly need a List then:
var myStats = (from s in GetStatsList()
group by new { s.date, s.team} into g
select new stat { Date = g.Key.date, Team = g.Key.team}).ToList();

Related

Merge two arrays into one and get each item's occurrence in their parent array

I have two arrays of dates. I want to merge them into one array that stores each (distinct) dates and their occurrences in their parent arrays.
For example:
var today = new DateTime (year:2022, month:01, day:01);
var firstDateArray = new DateTime[] { today.AddDays(1), today.AddDays(7), today, today.AddDays(3), today };
var secondDateArray = new DateTime[] { today.AddDays(7), today, today.AddDays(3), today.AddDays(3), today.AddDays(1) };
I want to get something like this as a result.
How do I go about this please?
Here I used a tuple (DateTime, int, int) to represent the data:
List<(DateTime, int, int)> result = firstDateArray.
Concat(secondDateArray).
Distinct().
Select(x => (
x,
firstDateArray.Where(y => y == x).Count(),
secondDateArray.Where(z => z == x).Count()
)).
ToList();
You can use .GroupBy, .Select, and .Join for this:
var today = new DateTime (year:2022, month:01, day:01);
var firstDateArray = new DateTime[] { today.AddDays(1), today.AddDays(7), today, today.AddDays(3), today };
var secondDateArray = new DateTime[] { today.AddDays(7), today, today.AddDays(3), today.AddDays(3), today.AddDays(1) };
var firstGrouped = firstDateArray
.GroupBy(d => d)
.Select(g => new { Date = g.Key, Count = g.Count() });
var secondGrouped = secondDateArray
.GroupBy(d => d)
.Select(g => new { Date = g.Key, Count = g.Count() });
var joined = firstGrouped
.Join(secondGrouped, f => f.Date, s => s.Date, (f, s) => new { Date = f.Date, FirstCount = f.Count, SecondCount = s.Count })
.OrderBy(j => j.Date)
.ToList();
GroupBy basically collects items with a common key (in this case the DateTime) and then lets you iterate through the collections.
Join takes 4 arguments: the second enumerable, the key selector for the first, the key selector for the second (it uses these to facilitate the join), and the result selector to produce the result you want.
Documentation:
GroupBy
Select
Join
OrderBy
ToList
Try it online

C#- LINQ Grouping on a custom created date range

So, I have this LINQ query:
var Result = from u in Users
group u by new {u.AccountType, u.Id,u.CreationDate} into usergroup
select new {id=usergroup.Key.Id,CreationDate=usergroup.Key.CreationDate,AccountType=usergroup.Key.AccountType};
that returns the following data set:
I am able to get the individual group count like this:
var myresult=Result.GroupBy(n=>n.AccountType).Select(n=>new {AccountType=n.Key,TotalCount=n.Count()});
which gives me:
Now suppose, I define a custom date range of Months from January-December, how can I do a group on the first data-set to give me count of AccountType based on each month based on the CreationDate column into my custom date range?
I am trying to understand what type you would like your result to be in. If you want a list of Months, each of which would have a tally of Accounts per AccountType, then you could try something like this:
var myResult2 = result.GroupBy(o => o.CreationDate.Month).Select(monthGroup => new
{
Month = System.Globalization.DateTimeFormatInfo.CurrentInfo.GetMonthName(monthGroup.Key),
Accounts = monthGroup.GroupBy(o => o.AccountType).ToDictionary(accountGroup => accountGroup.Key, accountGroup => accountGroup.Count())
});
Try this:
I think this will do your job
.GroupBy(g => new { g.CreationDate.Date.Month, g.AccountType })
.Select(t => new { t.Key.AccountType, t.Key.Month, Count = t.Count() });
If CreationDate is a string:
var group = result.GroupBy(g => new { DateTime.Parse(g.CreationDate).Date.Month, g.AccountType })
.Select(user => new { user.Key.AccountType, user.Key.Month, Count = user.Count() });
foreach (var g in group)
{
Console.WriteLine($"Users with Month[{g.Month}] : {g.Count}, AccType: {g.AccountType};");
}
I Have Try Below Code:
var data = Users.GroupBy(x => new {x.column1 , x.column2 ,...})
.Select(y=> new className() { column1 = y.Key.column1 , column2 = y.Key.column2}).ToList<className>();

C# Linq To Create Group On Multiple Properties and format the resultant group into single key/property?

I have following scenario where I want to find duplicates after forming the group and realign/format the duplicate data with some common class.
Example -
var lst = new List<Test>
{
new Test{Category="A",Class="Class1",Id="101",Name="John"},
new Test{Category="B",Class="Class2",Id="102",Name="Peter"},
new Test{Category="A",Class="Class2",Id="103",Name="David"},
new Test{Category="C",Class="Class3",Id="104",Name="Julia"},
new Test{Category="D",Class="Class4",Id="105",Name="Ken"},
new Test{Category="A",Class="Class1",Id="106",Name="Robert"},
};
I have created the group as -
var group =
from c in lst
group c by new
{
c.Category,
c.Class
} into g
select new
{
Category = g.Key.Category,
Class = g.Key.Class,
Id = lst.Where(x => g.Key.Category == x.Category && g.Key.Class==x.Class)
.Select(y => y.Id).ToList()
};
Which results me 2 group items for Category A with different Classes -
GroupItem1 - Category = "A" , Class = "Class1", Id = {101,106}
GroupItem2 - Category = "A" , Class = "Class2", Id = {103}
So I have requirement to show result in such case as below with other categories as -
Category = "A", Class = "Class1 OR SomeCommonClass", Id = {101,106,103}
Is it possible to achieve this result with minimum code and optimized logic.
If you want to group by Category and get the result below is the query.
var group =
from c in lst
group c by new
{
c.Category
} into g
select new
{
Category = g.Key.Category,
Class = lst.Where(x => g.Key.Category == x.Category).Select(y => y.Class).ToList(),
Id = lst.Where(x => g.Key.Category == x.Category)
.Select(y => y.Id).ToList()
};
Replace your group query with this:
var groups =
from c in lst
group c by c.Category into g
select new { Category = g.Key, Class = g.Select(c => c.Class).Distinct().Join(" or "), IDs = g.Select(c => c.Id).ToList() };
where Join is an IEnumerable extension method:
public static string Join(this IEnumerable<string> strings, string sep) => String.Join(sep, strings.ToArray());
var group = lst.GroupBy(l => l.Category)
.Select(x => new
{
Category = x.Key,
Class = string.Join(" OR ", x.Select(c => c.Class).Distinct()),
Ids = x.Select(c => c.Id).ToList()
}).ToList();

Group By using Select statement in Linq

var query = from i in SFC.Supplies_ReceiveTrans
orderby i.Poprctnm descending
select new { RR = i.Poprctnm };
Result:
RR-01,
RR-01,
RR-02,
RR-02,
RR-02,
RR-TEST,
RR-TEST,
How do i group RR in this kind of statement
Result:
RR-01,
RR-02,
RR-TEST
just a few modification to ask if is it possible to do this one or what you have in your mind? Sorry for asking too much just really interested in learning more on linq.. how do i convert it into string coz its showing true or false.. boolean statement
var query = SFC.Supplies_ReceiveTrans.Select(s =>
s.Poprctnm.StartsWith(p))
.Distinct()
.OrderBy(p => p)
.Select(p => new { RR = p })
.Take(10);
You can use Distinct or GroupBy methods in this case
var query = SFC.Supplies_ReceiveTrans.Select(s=> s.Poprctnm)
.Distinct()
.OrderByDescending(p => p)
.Select(p=> new { RR = p });
if you use OrderByDescending then the result will be
RR-TEST
RR-02
RR-01
But I think you want OrderBy then the result will be
RR-01
RR-02
RR-TEST
So try below
var query = SFC.Supplies_ReceiveTrans.Select(s=> s.Poprctnm)
.Distinct()
.OrderBy(p => p)
.Select(p=> new { RR = p });
var query = SFC.Supplies_ReceiveTrans
.GroupBy(x=>x.Poprctnm)
.Select(g=>g.First())
.OrderByDescending(x=>x.Poprctnm)
.Select(x=>new { RR = x.Poprctnm });
If you want to get result as group:
var query = SFC.Supplies_ReceiveTrans
.GroupBy(x=>x.Poprctnm)
.OrderByDescending(g=>g.Key);
var result = SFC.Supplies_ReceiveTrans
.Select(x => new { RR = x.Poprctnm })
.Distinct()
.OrderByDescending(x => x.Poprctnm);
Looks like you need Distinct here, not group
var query = SFC.Supplies_ReceiveTrans
.Select(x => new {RR = i.Poprctnm})
.Distinct()
.OrderByDescending(i => i);

Group By Multiple Columns

How can I do GroupBy multiple columns in LINQ
Something similar to this in SQL:
SELECT * FROM <TableName> GROUP BY <Column1>,<Column2>
How can I convert this to LINQ:
QuantityBreakdown
(
MaterialID int,
ProductID int,
Quantity float
)
INSERT INTO #QuantityBreakdown (MaterialID, ProductID, Quantity)
SELECT MaterialID, ProductID, SUM(Quantity)
FROM #Transactions
GROUP BY MaterialID, ProductID
Use an anonymous type.
Eg
group x by new { x.Column1, x.Column2 }
Procedural sample:
.GroupBy(x => new { x.Column1, x.Column2 })
Ok got this as:
var query = (from t in Transactions
group t by new {t.MaterialID, t.ProductID}
into grp
select new
{
grp.Key.MaterialID,
grp.Key.ProductID,
Quantity = grp.Sum(t => t.Quantity)
}).ToList();
For Group By Multiple Columns, Try this instead...
GroupBy(x=> new { x.Column1, x.Column2 }, (key, group) => new
{
Key1 = key.Column1,
Key2 = key.Column2,
Result = group.ToList()
});
Same way you can add Column3, Column4 etc.
Since C# 7 you can also use value tuples:
group x by (x.Column1, x.Column2)
or
.GroupBy(x => (x.Column1, x.Column2))
C# 7.1 or greater using Tuples and Inferred tuple element names (currently it works only with linq to objects and it is not supported when expression trees are required e.g. someIQueryable.GroupBy(...). Github issue):
// declarative query syntax
var result =
from x in inMemoryTable
group x by (x.Column1, x.Column2) into g
select (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity));
// or method syntax
var result2 = inMemoryTable.GroupBy(x => (x.Column1, x.Column2))
.Select(g => (g.Key.Column1, g.Key.Column2, QuantitySum: g.Sum(x => x.Quantity)));
C# 3 or greater using anonymous types:
// declarative query syntax
var result3 =
from x in table
group x by new { x.Column1, x.Column2 } into g
select new { g.Key.Column1, g.Key.Column2, QuantitySum = g.Sum(x => x.Quantity) };
// or method syntax
var result4 = table.GroupBy(x => new { x.Column1, x.Column2 })
.Select(g =>
new { g.Key.Column1, g.Key.Column2 , QuantitySum= g.Sum(x => x.Quantity) });
You can also use a Tuple<> for a strongly-typed grouping.
from grouping in list.GroupBy(x => new Tuple<string,string,string>(x.Person.LastName,x.Person.FirstName,x.Person.MiddleName))
select new SummaryItem
{
LastName = grouping.Key.Item1,
FirstName = grouping.Key.Item2,
MiddleName = grouping.Key.Item3,
DayCount = grouping.Count(),
AmountBilled = grouping.Sum(x => x.Rate),
}
Though this question is asking about group by class properties, if you want to group by multiple columns against a ADO object (like a DataTable), you have to assign your "new" items to variables:
EnumerableRowCollection<DataRow> ClientProfiles = CurrentProfiles.AsEnumerable()
.Where(x => CheckProfileTypes.Contains(x.Field<object>(ProfileTypeField).ToString()));
// do other stuff, then check for dups...
var Dups = ClientProfiles.AsParallel()
.GroupBy(x => new { InterfaceID = x.Field<object>(InterfaceField).ToString(), ProfileType = x.Field<object>(ProfileTypeField).ToString() })
.Where(z => z.Count() > 1)
.Select(z => z);
var Results= query.GroupBy(f => new { /* add members here */ });
A thing to note is that you need to send in an object for Lambda expressions and can't use an instance for a class.
Example:
public class Key
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
This will compile but will generate one key per cycle.
var groupedCycles = cycles.GroupBy(x => new Key
{
Prop1 = x.Column1,
Prop2 = x.Column2
})
If you wan't to name the key properties and then retreive them you can do it like this instead. This will GroupBy correctly and give you the key properties.
var groupedCycles = cycles.GroupBy(x => new
{
Prop1 = x.Column1,
Prop2= x.Column2
})
foreach (var groupedCycle in groupedCycles)
{
var key = new Key();
key.Prop1 = groupedCycle.Key.Prop1;
key.Prop2 = groupedCycle.Key.Prop2;
}
group x by new { x.Col, x.Col}
.GroupBy(x => (x.MaterialID, x.ProductID))
.GroupBy(x => x.Column1 + " " + x.Column2)
For VB and anonymous/lambda:
query.GroupBy(Function(x) New With {Key x.Field1, Key x.Field2, Key x.FieldN })

Categories