I have an object ChartObject that contains a list. Then I have a list of ChartObjects that I used LINQ to remove duplicate attributes. But I don't know how to combine each"itemList" property of the ChartObjects.
public class ChartObject
{
public string Type { get; set; }
public int Year { get; set; }
public long Cost { get; set; }
public List<Items> itemList { get; set; }
}
List<ChartObject> coList = GetItems();
result = coList.GroupBy(i => new { i.Type, i.Year }).Select(g => new ChartObject
{
Type = g.Key.Type,
Year = g.Key.Year,
Cost = g.Sum(i => i.Cost),
itemList = g.Select(i => i.itemList) //Does not work
}).ToList();
I'm assuming it's not as simple as just casting the itemList as the compiler says and there must be some sort of aggregation to be done?
Assuming you just want to combine all of the items in the group, you're looking for SelectMany, and you'll need to call ToList to get a List<Items>:
g.SelectMany(i => i.itemList).ToList();
Related
I have a list of objects, TargetList populated from the database which I want to group together based on the AnalyteID, MethodID and InstrumentID fields, but the Unit fields will be stored in a list applicable to each grouped object.
Furthermore, it is only possible for one of the available units to have a target assigned to it. Therefore, during the grouping I need a check to see if a target is available and, if so, skip creation of the unit list.
The TargetList object contains the following attributes:
public int id { get; set; }
public int AnalyteID { get; set; }
public string AnalyteName { get; set; }
public int MethodID { get; set; }
public string MethodName { get; set; }
public int InstrumentID { get; set; }
public string InstrumentName { get; set; }
public int UnitID { get; set; }
public string UnitDescription { get; set; }
public decimal TargetMean { get; set; }
public List<Unit> Units { get; set; }
I have a method for multi-grouping using LINQ:
TargetList.GroupBy(x => new { x.AnalyteID, x.MethodID, x.InstrumentID })...
But unsure as to how to check for a target at a row before extracting all available units at current group if target doesn't exist.
I created a solution which groups all rows returned from the database based on the AnalyteID, MethodID and InstrumentID ('names' of each of these are included in the grouping aswell).
Additionally, all non-unique Unit attributes (UnitID and UnitDescription) are placed into a list only if the TargetMean is 0.
targetViewModel.TargetList
// Group by unique analyte/method/instrument
.GroupBy(x => new { x.AnalyteID, x.AnalyteName, x.MethodID, x.MethodName, x.InstrumentID, x.InstrumentName })
// Select all attributes and collect units together in a list
.Select(g => new TargetView
{
id = g.Max(i => i.id),
AnalyteID = g.Key.AnalyteID,
AnalyteName = g.Key.AnalyteName,
MethodID = g.Key.MethodID,
MethodName = g.Key.MethodName,
InstrumentID = g.Key.InstrumentID,
InstrumentName = g.Key.InstrumentName,
TargetMean = g.Max(i => i.TargetMean),
UnitID = g.Max(i => i.UnitID),
UnitDescription = g.Max(i => i.UnitDescription),
// only extract units when target mean is 0
Units = g.Where(y => y.TargetMean == 0)
.Select(c => new Unit { ID = c.UnitID, Description = c.UnitDescription }).ToList()
}).ToList();
Note: The Max method is used to extract any required non-key attributes, such as the TargetMean/id. This works fine because only one row will ever be returned if a TargetMean exists.
It does feel 'dirty' to use the Max method in order to obtain all other non-key attributes though so if anyone has any other suggestions, please feel free to drop an answer/comment as I am interested to see if there are any cleaner ways of achieving the same result.
I have a list of an object defined by a model, and I want to group by a specific field. When grouped, some fields will need to be summed together. I want to group by ItemName, and the Amount will be all summed together.
My model is:
public class Item{
public string ItemName { get; set; }
public string ItemColor { get; set; }
public string SomethingElse { get; set; }
public decimal Amount { get; set; }
}
My current attempt is:
List<Item> fullListOfItems = GetItemsFromDatabase();
List<Item> groupedList = fullListOfItems
.GroupBy( l => l.ItemName )
.Select(i =>
new Item()
{
ItemName = i.Key.ItemName,
ItemColor = i.Key.ItemColor,
SomethingElse = i.Key.SomethingElse,
Amount = i.Sum(k=>k.Amount)
}
);
I'm getting an error after each key statement, saying that the grouping model does not contain a defintion for ItemName.
Any help would be appreciated. Thanks.
your code should look like this
List<Item> groupedList = fullListOfItems
.GroupBy(l => l.ItemName)
.Select(i =>
new Item()
{
ItemName = i.Key,
ItemColor = i.First().ItemColor,
SomethingElse = i.First().SomethingElse,
Amount = i.Sum(k => k.Amount)
}
).ToList();
instead of i.Key.ItemColor you have a List of Items and you have to pick one value (e.g. First()) - Key is the value of ItemName of each group
I have a class, that contains a list of classes, which in them selves, contains a list of classes, as below:
public class grouping
{
public List<Member> members { get; set; }
public string group_id { get; set; }
}
public class Member
{
public string member_id { get; set; }
public List<vStat> vstats { get; set; }
}
public class vStat
{
public string t1 { get; set; }
public string t2 { get; set; }
public string t3 { get; set; }
public string v_id { get; set; }
}
Each List vstats contains a number of vStats, where v_id only occurs once per list, but each members vstats list will repeat these v_id's. t1,2 and 3 are all integers, but unfortunately are stored in strings. I could cast them earlier if needs be, but was hoping to do all this in one go.
What I want to do is combine each Members vstats list into a single list for all members, with a single value for each v_id and the sum of the values for t1,t2,t3:
I can do this with a number of loops inside loops, but it seems there should be a more efficient way of doing this.
members.SelectMany(m => m.vstats)
.GroupBy(v => v.v._id)
.Select(g => new vStat {
t1 = g.Sum(x => Int32.Parse(x.t1)).ToString(),
t2 = g.Sum(x => Int32.Parse(x.t2)).ToString(),
t3 = g.Sum(x => Int32.Parse(x.t3)).ToString(),
v_id = g.Key
});
This query returns single list of aggregated vStat objects for all members.
If you have List<grouping> then you can first flatten its members and then its vStats using SelectMany and then you can project the result like:
List<grouping> groupList = new List<grouping>();
var result = groupList.SelectMany(grp=> grp.members)
.SelectMany(mem=> mem.vstats)
.GroupBy(v => v.v_id)
.Select(vgrp=> new vStat
{
v_id = vgrp.Key,
t1 = vgrp.Sum(r=> int.Parse(r.t1)).ToString(),
t2 = vgrp.Sum(r=> int.Parse(r.t2)).ToString(),
t3 = vgrp.Sum(r=> int.Parse(r.t3)).ToString()
});
Not really sure how much faster it is as compare to your loop code since it internally uses loops.
I unable to come up with a linq query for the following scenario.
public class Product
{
public virtual string ProductName { get; set; }
public virtual IList<SubProduct> SubProducts { get; set; }
}
public class SubProduct
{
public string SubProductName { get; set; }
public int SubProductTypeId { get; set; }
}
public class SubProductType
{
public int SubProductTypeId{ get; set; }
public string Description { get; set; }
}
var productList = List<Product>();
var subProductTypeLlist = List<SubProductType>();
I have a list of products and each product has list of SubProducts. I want to get the query to represent {ProductName, Description}. Please suggest how to write linq query.
Something like this should do the trick:
var result = productList
.SelectMany(p => p.SubProducts
.Select(sp => new { SubProduct = sp, ProductName = p.ProductName }))
.Select(sp =>
new { Description = subProductTypeList
.Single(spt => spt.SubProduct.SubProductTypeId == sp.SubProductTypeId).Description,
ProductName = sp.ProductName })
In the SelectMany, we first do a Select on the internal IEnumerable (IList implements IEnumerable) to convert each SubProduct object to an anonymous class holding the SubProduct object and the ProductName. The SelectMany then converts that to a flat list. We then use Select on that list to create a new anonymous class again, where this time, we grab the Description from subProductTypeList. The result is an IEnumerable of an anonymous class with the members Description and ProductName.
I have two classes Teams and PlayerTeams
public class PlayerTeams
{
public int ID { get; set; }
public string PlayerName { get; set; }
public string PlayerCountry { get; set; }
public bool IsActive { get; set; }
public string PlayerTeam { get; set; }
}
public class Players
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
public bool? Status { get; set; }
}
I have a list of PlayerTeams which is grouped by PlayerTeam like this.
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy( x => x.PlayerTeam)
.ToList();
Its of type List<IGrouping<string, PlayerTeams>> but I want it to be of type List<IGrouping<string, Players>> as I do not want the redundant key information on every row.
How could I possibly achieve that? I could only think of something like .ConvertAll() on the IGrouping. I am not able to make it also.
Is there an efiicient way to do this?
If you can change the grouping, I'd just use:
var groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.ToList();
Where Players.FromPlayerTeam is a static method in Players which takes a PlayerTeam and returns a Players.
Additionally, I'd suggest using ToLookup instead of GroupBy - a Lookup is exactly what you want here, without bothering with the ToList call.
This not testet, just an idea.
If you have trouble converting your linq statement, which is expecting the IGrouping type, to a string list, then you might have to select it before.
var groupedPlayers = new List<string>();
groupedPlayers = teams
.OrderBy(x => x.PlayerName)
.GroupBy(x => x.PlayerTeam, Players.FromPlayerTeam)
.Select(x => x.Key) // << added select
.ToList();