List<classObject> group by on the objects in C# - c#

I have a requirement to group a list of class objects based on another list inside the Object.
class TransactionObject
{
public int ProjectId { get; set; }
public string uniqueId { get; set; }
public string OrgNumber { get; set; }
public string OrgName { get; set; }
public List<TransactionValue> TransactionValue{ get; set; } = new List<TransactionValue>();
public class TransactionValue
{
public DateTime TrnDate { get; set; }
public decimal EURTrans { get; set; }
public decimal LocaTrans { get; set; }
public decimal BrokeragePercentage { get; set; }
}
}
Now on this class, I have created a list of objects.
var TransactionList = new List<TransactionObject>();
I want to get the list of Unique ProjectsIdm OrgName and The sum of EUR Trans, Local Trans, based on a Group by on TrnDate.
Example:
ProjectId OrgName Trn Date EUR Trns Local Trns
543332 Organization 1 1-Jan-22 100 150
543332 Organization 1 1-Jan-22 150 20
I Need :
Sorry MY BAD I edited the correct output i require
ProjectId OrgName Trn Date EUR Trns Local Trns
543332 Organization 1 1-Jan-22 250 170
What I tried :
List<TransactionObject> result = TransactionList .GroupBy (g => new {
g.HoldingName, g.TransactionValues.First().TrntDate })
.Select(g => g.First())
.ToList();
I tried this, but it doesn't help me with the Sum of the columns, I am good in Java but new to C# please help me out. I have already crossed my deadline on this object.

I think it could be beneficial to split the operation into 2 stages.
var flattened = TransactionList
.SelectMany(
collectionSelector: o => o.Transactions,
resultSelector: (fullObject, transaction) => new { fullObject.ProjectId, fullObject.OrgName, Transaction = transaction });
var grouped = flattened
.GroupBy (t => new {t.ProjectId, t.OrgName, t.Transaction.TrnDate })
.Select( g => new
{
g.Key.ProjectId,
g.Key.OrgName,
g.Key.TrnDate,
SumEURTrans = g.Sum( t => t.Transaction.EURTrans),
SumLocaTrans = g.Sum( t => t.Transaction.LocaTrans)
})
.ToList();
foreach (var t in grouped)
{
Console.WriteLine($"{t.ProjectId}\t{t.OrgName}\t{t.TrnDate}\t{t.SumEURTrans}\t{t.SumLocaTrans}");
}
This produces
543332 Organization 1 1-Jan-22 250 170
543332 Organization 1 2-Jan-22 450 470
543333 Organization 1 1-Jan-22 250 170
for the example input of
var TransactionList = new [] {
new TransactionObject
{
ProjectId = 543332,
OrgName = "Organization 1",
Transactions = new List<TransactionObject.TransactionValue>
{
new TransactionObject.TransactionValue
{
TrnDate = "1-Jan-22",
EURTrans = 100,
LocaTrans = 150
},
new TransactionObject.TransactionValue
{
TrnDate = "1-Jan-22",
EURTrans = 150,
LocaTrans = 20
}
,new TransactionObject.TransactionValue
{
TrnDate = "2-Jan-22",
EURTrans = 200,
LocaTrans = 250
},
new TransactionObject.TransactionValue
{
TrnDate = "2-Jan-22",
EURTrans = 250,
LocaTrans = 220
}
}
},
new TransactionObject
{
ProjectId = 543333,
OrgName = "Organization 1",
Transactions = new List<TransactionObject.TransactionValue>
{
new TransactionObject.TransactionValue
{
TrnDate = "1-Jan-22",
EURTrans = 100,
LocaTrans = 150
},
new TransactionObject.TransactionValue
{
TrnDate = "1-Jan-22",
EURTrans = 150,
LocaTrans = 20
}
}
}
};
This will group by {t.ProjectId, t.OrgName, t.Transaction.TrnDate} across all objects and you need to decide if that's what you want (an example alternative being grouping only within each TransactionObject).

If I understand you right - you can do it this way in one run:
public void GroupTransactions(TransactionObject[] transactionObjects)
{
var results = transactionObjects
// build a flat list of values to have both transactionObject and value for every transactionValue
.SelectMany(obj => obj.TransactionValues.Select(value => (obj, value)))
.GroupBy(tuple => new {tuple.obj.ProjectId, tuple.obj.OrgName, tuple.value.TrnDate})
// Get sums for every group. You can use '.Aggregate()' method instead of this custom GetSum but it seems less readable to me.
.Select(group => (group.Key, sum: GetSum(group.Select(tuple => tuple.value))))
.ToArray();
foreach (var result in results)
Console.WriteLine($"{result.Key.ProjectId} {result.Key.OrgName} {result.Key.TrnDate} {result.sum.euro} {result.sum.local}");
}
private static (decimal euro, decimal local) GetSum(IEnumerable<TransactionObject.TransactionValue> values)
{
decimal euro = 0, local = 0;
foreach (var value in values)
{
euro += value.EURTrans;
local += value.LocaTrans;
}
return (euro, local);
}

Related

Linq counting second grouping and counting without grouping

I'm trying to build a summary query that i will be using for statistics.
i have a dataTable with the folowing columns (approx 18000 rows) :
Artist / Album / file_path (one for each song) / rating /
each artist has 1 or several album with has songs and each songs have a rating
I want to have the following result :
For each artist ID (more reliable than the artist name), the total number of albums, the total number of songs, and the total number of ratings equal to 5.
Artist x / #album / #songs / #rating = 5 / song.first() //in song.first i have access to the file path, it can be any file path from the artist hence the first one.
I've been pulling my hair for several hours now and i cannot manage to get the # of albums per artist :( This is what i've been trying so far :
i have a Class for the query :
public class art_detail
{
public string artiste { get; set; }
public string fp { get; set; } // the file_path
public int nbr_album { get; set; }
public int nbr_song { get; set; }
public int nbr_rat5 { get; set; }
}
this is the query i came up to :
var result = from res in Globals.ds.Tables[0].AsEnumerable() // the table
.GroupBy(x => new { art = x.Field<int>("Artist_ID"), alb = x.Field<string>("album") })
.Select(x => new art_detail { artiste = x.Select(p =>p.Field<string>("artiste")).First(), fp = x.Select(p=>p.Field<string>("file_path")).First(), nbr_album = x.Key.alb.Count() })
.OrderBy(x => x.artiste)
select res;
The count is unfortunately completely wrong and i have no idea how to get the # of rating = 5 :(
Thanks for the help !
Edit :
Here is my query to make it work :
var table = Globals.ds.Tables[0].AsEnumerable();
var stats = table.GroupBy(x => x.Field<int>("Artist_ID"))
.Select(x => new art_detail
{
artiste = x.Select(p=>p.Field<string>("artiste")).First(),
nbr_album = x.Select(y => y.Field<string>("album")).Distinct().Count(),
fp = x.Select(y => y.Field<string>("file_path")).FirstOrDefault(),
nbr_song = x.Count(),
nbr_rat5 = x.Count(y => y.Field<int>("Rating") == 5)
});
Simpler than what i thought :)
Assuming a table whose schema matches this class:
public class Song
{
public string ArtistID { get; set; }
public string Album { get; set; }
public string FilePath { get; set; }
public int Rating { get; set; }
}
and given a LINQ source, you have the following query:
IQueryable<Song> table = /*insert source*/;
var stats = table.GroupBy(x => x.ArtistID);
.Select(x => new art_detail
{
artiste = x.Key,
nbr_album = x.Select(y => y.Album).Distinct().Count(),
nbr_song = x.Count(),
nbr_rat5 = x.Count(y => y.Rating == 5),
});
I used head compiled query as it seemed more understandable for me in this case:
Example model:
public class Artist
{
public string ArtistID { get; set; }
public string Album { get; set; }
public string FilePath { get; set; }
public int Rating { get; set; }
public int NumberOfSongs { get; set; }
}
Creating some dummy records for Usher and Beyonce:
//Usher
var artistOne = new Artist()
{
ArtistID = "Usher",
Album = "Lit",
FilePath = "dummy/path/here",
Rating = 5,
NumberOfSongs = 9
};
var artistTwo = new Artist()
{
ArtistID = "Usher",
Album = "Sick",
FilePath = "dummy/path/here",
Rating = 5,
NumberOfSongs = 11
};
var artistThree = new Artist()
{
ArtistID = "Usher",
Album = "Dope",
FilePath = "dummy/path/here",
Rating = 4,
NumberOfSongs = 14
};
//Beyonce
var artistFour = new Artist()
{
ArtistID = "Beyonce",
Album = "Hot",
FilePath = "dummy/path/here",
Rating = 5,
NumberOfSongs = 8
};
var artistFive = new Artist()
{
ArtistID = "Beyonce",
Album = "Fire",
FilePath = "dummy/path/here",
Rating = 4,
NumberOfSongs = 16
};
var listOfArtist = new List<Artist> { artistOne, artistTwo, artistThree, artistFour, artistFive };
Running query:
var result = from a in listOfArtist
where a.Rating == 5
group a by a.ArtistID into art
select new
{
artist = art.Key,
numberOfAlbums = art.Count(),
numberOfSongs = art.Sum(d => d.NumberOfSongs),
};
Results:
Hope this helps =)

LINQ Group & Sum on a list of Models where a property is another model

I havent been able to find any answers to this specific question on LINQ Group & Aggregation so am hoping someone here can help. I have a list of models of such:
public class BasketProduct
{
public ProductItem Product { get; set; }
public int Quantity { get; set; }
public decimal SubTotal { get; set; }
public DateTime DateAdded { get; set; }
}
where the first property is another model:
public class ProductItem
{
public int ID { get; set; }
public string Description { get; set; }
public char Item { get; set; }
public decimal Price { get; set; }
public string ImagePath { get; set; }
public string Barcode { get; set; }
}
I basically want to be able to Group and Aggregate on this list:
List<BasketProduct> allBasketProducts =
Using the following:
allBasketProducts = allBasketProducts
.GroupBy(x => x.Product.ID)
.Select(y => new BasketProduct
{
Product.ID = y.First().Product.ID,
Product.Item = y.First().Product.Item,
Product.Description = y.First().Product.Description,
Quantity = y.Sum(z => z.Quantity),
Product.ImagePath = y.First().Product.ImagePath,
Product.Price = y.First().Product.Price,
SubTotal = y.Sum(z => z.SubTotal)
}).ToList();
However it seriously doesn't like this (as per red squigly lines and even red'er text):
Can someone help please?
Your issue isn't actually related to LINQ, it's your ProductItem constructor. You need to construct its nested Product object explicitly, like this:
allBasketProducts
.GroupBy(x => x.Product.ID)
.Select(y => new BasketProduct
{
Quantity = y.Sum(z => z.Quantity),
SubTotal = y.Sum(z => z.SubTotal),
Product = new ProductItem
{
ID = y.First().Product.ID,
Item = y.First().Product.Item,
Description = y.First().Product.Description,
ImagePath = y.First().Product.ImagePath,
Price = y.First().Product.Price
}
}).ToList();
var totals =
(from b in allBasketProducts
group new { b.Quantity, b.SubTotal, Product= b.Product } by b.Product.ID into g
select new BasketProduct
{
Product = g.First().Product,
SubTotal = g.Sum(z => z.SubTotal),
Quantity = g.Sum(z => z.Quantity)
}).ToList();
Try following :
allBasketProducts = allBasketProducts
.GroupBy(x => x.Product.ID)
.Select(y => new BasketProduct()
{
Product = new ProductItem() {
ID = y.First().Product.ID,
Item = y.First().Product.Item,
Description = y.First().Product.Description,
ImagePath = y.First().Product.ImagePath,
Price = y.First().Product.Price
},
Quantity = y.Sum(z => z.Quantity),
SubTotal
When you specify the type of your Select, the compiler expects only the properties of that type. So you can only set the properties Product, Subtotal, Quantity and DateAdded in that code of yours.
You can find the Product simply by selecting the first Product that has an ID that matches your grouping Key:
var allBasketProductsGroupedByProductID = allBasketProducts
.GroupBy(x => x.Product.ID)
.Select(y => new BasketProduct
{
Product = y.First(i => i.Product.ID == y.Key).Product,
Quantity = y.Sum(z => z.Quantity),
SubTotal = y.Sum(z => z.SubTotal)
}).ToList();
try this
List allBasketProducts = new List();
allBasketProducts = new List<BasketProduct>()
{
new BasketProduct()
{
Product = new ProductItem()
{
ID = 1,
Price = 5,
},
Quantity = 2,
SubTotal = 2,
},
new BasketProduct()
{
Product = new ProductItem()
{
ID = 1,
Price = 5,
},
Quantity = 4,
SubTotal = 2,
},
new BasketProduct()
{
Product = new ProductItem()
{
ID = 2,
Price = 10,
},
Quantity = 3,
SubTotal = 2,
},
new BasketProduct()
{
Product = new ProductItem()
{
ID = 3,
Price = 20,
},
Quantity = 3,
SubTotal = 2,
},
new BasketProduct()
{
Product = new ProductItem()
{
ID = 2,
Price = 20,
},
Quantity = 3,
SubTotal = 2,
}
};
allBasketProducts = allBasketProducts
.GroupBy(x => x.Product.ID)
.Select(y => new BasketProduct()
{
Product = new ProductItem()
{
ID = y.First().Product.ID,
Item = y.First().Product.Item,
Description = y.First().Product.Description,
ImagePath = y.First().Product.ImagePath,
Price = y.First().Product.Price
},
Quantity = y.Sum(z => z.Quantity),
SubTotal = y.Sum(z => z.SubTotal)
}).ToList();

How to combine List items

I've a class like this:
public class ReportList
{
public int? ProjectId { get; set; }
public string Name { get; set; }
public string ProjectName { get; set; }
public int LevelId { get; set; }
public int Minutes { get; set; }
public int Hours { get; set; }
public int ExtraMinutes { get; set; }
public int ExtraHours { get; set; }
}
And I've list of this class
List<ReportList> repList = new List<ReportList>();
I've added items to this list:
repList.Add(new ReportList(1 , "a" , "project a", 2, 30, 1, 45, 2));
repList.Add(new ReportList(1 , "b" , "project a", 2, 30, 2, 15, 1));
repList.Add(new ReportList(1 , "c" , "project a", 2, 0, 3, 10, 0));
I want to combine this list items into one item by sum minutes and hours. So the list should be like this:
{1, "a", "project a", 2, 60, 6, 70, 3};
What can I do?
Use GroupBy extension method on ProjectId,ProjectName and LevelId fields.
var results = repList.GroupBy(x=> new {x.ProjectId, x.ProjectName, LevelId })
.Select(x=> new // or create new ReportList object.
{
ProjectId = x.Key.ProjectId,
ProjectName = x.Key.ProjectName,
Name = x.First().Name, // I assume it is first one as per example, modify if you want.
LevelId = x.Key.LevelId,
Minutes = x.Sum(s=>s.Minutes),
Hours = x.Sum(s=>s.Hours ),
ExtraMinutes = x.Sum(s=>s.ExtraMinutes ),
ExtraHours = x.Sum(s=>s.ExtraHours)
})
.ToList() ;
If you want more optimized version of answer posted by user Hari Prasad you could use following;
int minuteSum = 0;
int hoursSum = 0;
int extraMinutesSum = 0;
int extraHoursSum = 0;
foreach (var report in repList)
{
minuteSum += report.Minutes;
hoursSum += report.Hours;
extraMinutesSum += report.ExtraMinutes;
extraHoursSum += report.ExtraHours;
}
var firstItemInRepList = repList.First();
var result = new ReportList(firstItemInRepList.ProjectId,
firstItemInRepList.Name,
firstItemInRepList.ProjectName,
firstItemInRepList.LevelId,
minuteSum,
hoursSum,
extraMinutesSum,
extraHoursSum);
I know its more crude version but it will take less cpu.
var results = repList
.GroupBy(x => "all")
.Select(x=> new {
ProjectId = x.First().ProjectId,
Name = x.First().Name,
ProjectName = x.First().ProjectName,
LevelId = x.First().LevelId,
Minutes = x.Sum(s=>s.Minutes),
Hours = x.Sum(s=>s.Hours ),
ExtraMinutes = x.Sum(s=>s.ExtraMinutes),
ExtraHours = x.Sum(s=>s.ExtraHours)
});
I am refering answer posted by user Hari Prasad, but as per question requirement we need to apply groupby only on ProjectId i guess.
Please refer below code.
var processedResult = repList.GroupBy(x => x.ProjectId)
.Select(x => new ReportList
{
ProjectId = x.Key,
ProjectName = x.First().ProjectName, //As per your example it is first row data
Name = x.First().Name, //As per your example it is first row data
LevelId = x.First().LevelId,
Minutes = x.Sum(s => s.Minutes),
Hours = x.Sum(s => s.Hours),
ExtraMinutes = x.Sum(s => s.ExtraMinutes),
ExtraHours = x.Sum(s => s.ExtraHours)
}).ToList();

Linq Get totals using group by

I have the following class:
class Item
{
public decimal TransactionValue { get; set; }
public string TransactionType { get; set; }
}
And I have this list:
var items = new List<Item>
{
new Item
{
TransactionValue = 10,
TransactionType = "Income"
},
new Item
{
TransactionValue = 10,
TransactionType = "Income"
},
new Item
{
TransactionValue = -5,
TransactionType = "Outgoing"
},
new Item
{
TransactionValue = -20,
TransactionType = "Outgoing"
}
};
And I am trying to get the sums based on ValueType, I have tried the below but it is adding everything and giving me one total which is -5, what I want is totals for each transaction type so I want to get a new class which is Totals class below and with this data: TotalIncoming : 20 and TotalOutgoing : - 25.
var r = items.Sum(x => x.TransactionValue);
class Totals
{
public decimal TotalIncoming { get; set; }
public decimal TotalOutgoing { get; set; }
}
Thanks
You can achieve your desired result with following query:-
Totals result = new Totals
{
TotalIncoming = items.Where(x => x.TransactionType == "Income")
.Sum(x => x.TransactionValue),
TotalOutgoing = items.Where(x => x.TransactionType == "Outgoing")
.Sum(x => x.TransactionValue)
};
But, as you can see with your Type Totals, we need to hard-code the TransactionType and we have no clue from the result that this Sum belongs to which type apart from the naming convention used.
I will create the below type instead:-
class ItemTotals
{
public string ItemType { get; set; }
public decimal Total { get; set; }
}
Here we will have the TransactionType along with its corresponding Total in the result, we can simply group by TransactionType & calculate the sum, here is the query for same:-
List<ItemTotals> query = items.GroupBy(x => x.TransactionType)
.Select(x => new ItemTotals
{
ItemType = x.Key,
Total = x.Sum(z => z.TransactionValue)
}).ToList();
Here is the Complete Working Fiddle, you can choose from both.
I'm sure there is a probably a clever way to do this in one line using Linq, but everything I could come up with was quite ugly so I went with something a bit more readable.
var results = items.GroupBy(x => x.TransactionType)
.ToDictionary(x => x.Key, x => x.Sum(y => y.TransactionValue));
var totals = new Totals
{
TotalIncoming = results["Income"],
TotalOutgoing = results["Outgoing"]
};

Group by with multiple columns using lambda

How can I group by with multiple columns using lambda?
I saw examples of how to do it using linq to entities, but I am looking for lambda form.
var query = source.GroupBy(x => new { x.Column1, x.Column2 });
I came up with a mix of defining a class like David's answer, but not requiring a Where class to go with it. It looks something like:
var resultsGroupings = resultsRecords.GroupBy(r => new { r.IdObj1, r.IdObj2, r.IdObj3})
.Select(r => new ResultGrouping {
IdObj1= r.Key.IdObj1,
IdObj2= r.Key.IdObj2,
IdObj3= r.Key.IdObj3,
Results = r.ToArray(),
Count = r.Count()
});
private class ResultGrouping
{
public short IdObj1{ get; set; }
public short IdObj2{ get; set; }
public int IdObj3{ get; set; }
public ResultCsvImport[] Results { get; set; }
public int Count { get; set; }
}
Where resultRecords is my initial list I'm grouping, and its a List<ResultCsvImport>. Note that the idea here to is that, I'm grouping by 3 columns, IdObj1 and IdObj2 and IdObj3
if your table is like this
rowId col1 col2 col3 col4
1 a e 12 2
2 b f 42 5
3 a e 32 2
4 b f 44 5
var grouped = myTable.AsEnumerable().GroupBy(r=> new {pp1 = r.Field<int>("col1"), pp2 = r.Field<int>("col2")});
Further to aduchis answer above - if you then need to filter based on those group by keys, you can define a class to wrap the many keys.
return customers.GroupBy(a => new CustomerGroupingKey(a.Country, a.Gender))
.Where(a => a.Key.Country == "Ireland" && a.Key.Gender == "M")
.SelectMany(a => a)
.ToList();
Where CustomerGroupingKey takes the group keys:
private class CustomerGroupingKey
{
public CustomerGroupingKey(string country, string gender)
{
Country = country;
Gender = gender;
}
public string Country { get; }
public string Gender { get; }
}
class Element
{
public string Company;
public string TypeOfInvestment;
public decimal Worth;
}
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>()
{
new Element { Company = "JPMORGAN CHASE",TypeOfInvestment = "Stocks", Worth = 96983 },
new Element { Company = "AMER TOWER CORP",TypeOfInvestment = "Securities", Worth = 17141 },
new Element { Company = "ORACLE CORP",TypeOfInvestment = "Assets", Worth = 59372 },
new Element { Company = "PEPSICO INC",TypeOfInvestment = "Assets", Worth = 26516 },
new Element { Company = "PROCTER & GAMBL",TypeOfInvestment = "Stocks", Worth = 387050 },
new Element { Company = "QUASLCOMM INC",TypeOfInvestment = "Bonds", Worth = 196811 },
new Element { Company = "UTD TECHS CORP",TypeOfInvestment = "Bonds", Worth = 257429 },
new Element { Company = "WELLS FARGO-NEW",TypeOfInvestment = "Bank Account", Worth = 106600 },
new Element { Company = "FEDEX CORP",TypeOfInvestment = "Stocks", Worth = 103955 },
new Element { Company = "CVS CAREMARK CP",TypeOfInvestment = "Securities", Worth = 171048 },
};
//Group by on multiple column in LINQ (Query Method)
var query = from e in elements
group e by new{e.TypeOfInvestment,e.Company} into eg
select new {eg.Key.TypeOfInvestment, eg.Key.Company, Points = eg.Sum(rl => rl.Worth)};
foreach (var item in query)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Points.ToString());
}
//Group by on multiple column in LINQ (Lambda Method)
var CompanyDetails =elements.GroupBy(s => new { s.Company, s.TypeOfInvestment})
.Select(g =>
new
{
company = g.Key.Company,
TypeOfInvestment = g.Key.TypeOfInvestment,
Balance = g.Sum(x => Math.Round(Convert.ToDecimal(x.Worth), 2)),
}
);
foreach (var item in CompanyDetails)
{
Console.WriteLine(item.TypeOfInvestment.PadRight(20) + " " + item.Balance.ToString());
}
Console.ReadLine();
}
}

Categories

Resources