Reputation: 1069
How to multiply and sum all numeric children properties in LINQ
I have an object as follows
public class ResourceTier
{
//primary key. no duplicate
public int Id { get; set; }
public int ParentId { get; set; }
public decimal Volume { get; set; } //has value
public decimal UnitRate { get; set; } //has value
public decimal TotalPrice { get; set; } //has no value
}
The TotalPrice
default value is 0. This is the property where I want to fill the value. The TotalPrice
should be filled by multiplying the UnitRate
and Volume
properties and then summing of all the children if any.
This is the data
| Id | ParentId | Volume | UnitRate | TotalPrice |
| 1 | 0 | - | - | 180 |
| 2 | 0 | - | - | 30 |
| 3 | 1 | - | - | 130 |
| 4 | 1 | 5 | 10 | 50 |
| 5 | 2 | 3 | 10 | 30 |
| 6 | 3 | - | - | 50 |
| 7 | 3 | 2 | 40 | 80 |
| 8 | 6 | 4 | 10 | 40 |
| 9 | 6 | 1 | 10 | 10 |
When I try this, it is not working. This code has only the sum of the direct children but not all children (grand grand children and so on)
List<ResourceTier> result = ...;
result.ForEach(x => x.TotalPrice =
result.Where(c => c.ParentId == x.Id).Count() == 0 ?
s.UnitRates * s.Volume :
result.Where(c => c.ParentId == x.Id).Select(s => (s.UnitRates * s.Volume)).Sum());
Upvotes: 1
Views: 1450
Reputation: 30512
LINQ can't change your source collection!
However you can create a new collection of ResourceTiers
that match your requirements. After that you can decide to assign that to your output, or decide to save them in your database, table, etc.
IEnumerable<ResourceTier> sourceCollection = ...
IEnumerable<ResourceTier> resourceTiersWithTotalPrice = sourceCollection
.Select(resourceTier => new ResourceTier
{
Id = resourceTier.Id,
ParentId = resourceTier.ParentId,
...
TotalPrice = resourceTier.Volume * resourceTier.UnitRate,
});
Upvotes: -1
Reputation: 22511
A solution using only Linq will be hard to achieve, because it will typically only respect one level of children. In order to get the total of the grandchildren too, you have to walk the tree completely in order to get the sum of the children (and their children).
You have to create a method that uses recursion which means that it will call itself again with a different set of parameters. This way, you can get the total of the children first and then assign the value of the current node, e.g.:
private decimal SetTotal(IEnumerable<ResourceTier> tiers, ResourceTier current)
{
current.Total = current.Volume * current.UnitRate;
// Get children of current node
var children = tiers.Where(x => x.ParentId == current.Id && x.Id != current.Id); // The second condition explicitely excludes the current node to avoid infinite loops
foreach(var child in children)
current.Total += SetTotal(tiers, child); // Call method again for children
return current.Total;
}
The first call to the function would use the ids of all items that do not have a parent, e.g.:
foreach(var topLevelNode in tiers.Where(x => x.ParentId == 0))
SetTotal(tiers, topLevelNode);
Please note that the code above should demonstrate the principle of recursion. For sure there are more efficient ways to solve this a bit faster.
Upvotes: 3
Reputation: 1335
As other answers you need to calculate totalprice recursively:
public static decimal total(ResourceTier rt, List<ResourceTier> data)
{
if (data.Where(x=>x.ParentId==rt.Id).Count()==0)
return rt.Volume*rt.UnitRate;
else
{
var sum= data.Where(x => x.ParentId == rt.Id).Select(x=>total( x,data)).Sum();
return rt.UnitRate*rt.Volume+sum;
}
}
And the use this method:
var data = new List<ResourceTier> {
new ResourceTier{ Id=1, ParentId=0, Volume=0, UnitRate=0, TotalPrice=0 },
new ResourceTier{ Id=2, ParentId=0, Volume=0, UnitRate=0, TotalPrice=0 },
new ResourceTier{ Id=3, ParentId=1, Volume=0, UnitRate=0, TotalPrice=0 },
new ResourceTier{ Id=4, ParentId=1, Volume=5, UnitRate=10, TotalPrice=0 },
new ResourceTier{ Id=5, ParentId=2, Volume=3, UnitRate=10, TotalPrice=0 },
new ResourceTier{ Id=6, ParentId=3, Volume=0, UnitRate=0, TotalPrice=0 },
new ResourceTier{ Id=7, ParentId=3, Volume=2, UnitRate=40, TotalPrice=0 },
new ResourceTier{ Id=8, ParentId=6, Volume=4, UnitRate=10, TotalPrice=0 },
new ResourceTier{ Id=9, ParentId=6, Volume=1, UnitRate=10, TotalPrice=0 },
};
var result = data.Select(x=>new ResourceTier { Id=x.Id, ParentId=x.ParentId, UnitRate=x.UnitRate, Volume=x.Volume, TotalPrice= total(x, data) }).ToList();
output :
Upvotes: 0