Linq Dynamic Expressions Groupby with sum - c#

I cannot figure out how to do a Linq.Dynamic on an ObservableCollection and sum some fields. Basically I want to do this;
var group
from x in MyCollection
group x by x.MyField into g
select new MyClass
{
MyField = g.Key,
Total = g.Sum(y => y.Total)
};
Figured it would be this in Linq.Dynamic;
var dGroup = MyCollection
.GroupBy("MyField ", "it")
.Select("new (it.Key as MyField , it as new MyClass { Total = sum(it.Total ) })");
However this keeps give me errors.
FYI MyCollection is a ObservableCollection<MyClass>
Edit:
I am sorry did not make this very clear. The reason I need it to be Linq.Dynamic is that the actual MyClass has about 10 properties that user can pick to group the collection MyCollection in. To make matters worse is the user can select multiple grouping. So hand coding the groups isn't an option. So while #Harald Coppoolse does work it requires that myClass.MyField to be hand coded.

So MyCollection is a sequence of MyClass objects, where every MyClass object has at least two properties: MyField and Total.
You want the sum of all Total values that have the same value for MyField
For example:
MyField Total
X 10
Y 5
X 7
Y 3
You want a sequence with two elements: one for the X with a grand total of 10 + 7 = 17; and one for the Y with a grand total of 5 + 3 = 8
In method syntax:
var result = MyCollection.Cast<MyClass>() // take all elements of MyCollection
.GroupBy(myClass => myClass.MyField) // Group into groups with same MyField
.Select(group => new MyClass() // for every group make one new MyClass object
{
MyField = group.Key,
Total = group // to calculate the Total:
.Select(groupElement => groupElement.Total) // get Total for all elements in the group
.Sum(), // and sum it
})
If your ObservableCollection is in fact an ObservableCollection<MyClass> than you won't need the Cast<MyClass> part.
There is a lesser known GroupBy overload that will do this in one statement. I'm not sure if this one will improve readability:
var result = MyCollection.Cast<MyClass>() // take all elements of MyCollection
.GroupBy(myClass => myClass.MyField, // group into groups with same MyField
myClass => myClass.Total, // for every element in the group take the Total
(myField, totals) => new MyClass() // from the common myField and all totals in the group
{ // make one new MyClass object
MyField = myField, // the common myField of all elements in the group
Total = totals.Sum(), // sum all found totals in the group
});

So this might not be the best way, but it is the only I found. FYI it more manual work than standard LINQ.
Here is the new Dynamic Linq;
var dGroup = MyCollection
.GroupBy("MyField ", "it")
.Select("new (it.Key as MainID, it as MyClass)");
The next problem is not only do you need to iterate through each MainID but you need to iterate through MyClass and sum for each MainID;
foreach (dynamic r in dGroup)
{
foreach (dynamic g in r.MyClass)
{
gTotal = gTotal + g.Total;
}
}
If someone can show me a better way to do this I will award the correct answer to that.

Related

LINQ to JSON group query on array

I have a sample of JSON data that I am converting to a JArray with NewtonSoft.
string jsonString = #"[{'features': ['sunroof','mag wheels']},{'features': ['sunroof']},{'features': ['mag wheels']},{'features': ['sunroof','mag wheels','spoiler']},{'features': ['sunroof','spoiler']},{'features': ['sunroof','mag wheels']},{'features': ['spoiler']}]";
I am trying to retrieve the features that are most commonly requested together. Based on the above dataset, my expected output would be:
sunroof, mag wheels, 2
sunroof, 1
mag wheels 1
sunroof, mag wheels, spoiler, 1
sunroof, spoiler, 1
spoiler, 1
However, my LINQ is rusty, and the code I am using to query my JSON data is returning the count of the individual features, not the features selected together:
JArray autoFeatures = JArray.Parse(jsonString);
var features = from f in autoFeatures.Select(feat => feat["features"]).Values<string>()
group f by f into grp
orderby grp.Count() descending
select new { indFeature = grp.Key, count = grp.Count() };
foreach (var feature in features)
{
Console.WriteLine("{0}, {1}", feature.indFeature, feature.count);
}
Actual Output:
sunroof, 5
mag wheels, 4
spoiler, 3
I was thinking maybe my query needs a 'distinct' in it, but I'm just not sure.
This is a problem with the Select. You are telling it to make each value found in the arrays to be its own item. In actuality you need to combine all the values into a string for each feature. Here is how you do it
var features = from f in autoFeatures.Select(feat => string.Join(",",feat["features"].Values<string>()))
group f by f into grp
orderby grp.Count() descending
select new { indFeature = grp.Key, count = grp.Count() };
Produces the following output
sunroof,mag wheels, 2
sunroof, 1
mag wheels, 1
sunroof,mag wheels,spoiler, 1
sunroof,spoiler, 1
spoiler, 1
You could use a HashSet to identify the distinct sets of features, and group on those sets. That way, your Linq looks basically identical to what you have now, but you need an additional IEqualityComparer class in the GroupBy to help compare one set of features to another to check if they're the same.
For example:
var featureSets = autoFeatures
.Select(feature => new HashSet<string>(feature["features"].Values<string>()))
.GroupBy(a => a, new HashSetComparer<string>())
.Select(a => new { Set = a.Key, Count = a.Count() })
.OrderByDescending(a => a.Count);
foreach (var result in featureSets)
{
Console.WriteLine($"{String.Join(",", result.Set)}: {result.Count}");
}
And the comparer class leverages the SetEquals method of the HashSet class to check if one set is the same as another (and this handles the strings being in a different order within the set, etc.)
public class HashSetComparer<T> : IEqualityComparer<HashSet<T>>
{
public bool Equals(HashSet<T> x, HashSet<T> y)
{
// so if x and y both contain "sunroof" only, this is true
// even if x and y are a different instance
return x.SetEquals(y);
}
public int GetHashCode(HashSet<T> obj)
{
// force comparison every time by always returning the same,
// or we could do something smarter like hash the contents
return 0;
}
}

Group By - Linq to SQL?

I have a problem.
In SQL, when I do something like:
SELECT * FROM Fruits GROUP BY fruitName;
I can have something like:
ID FRUITNAME PRICE
1 Apple $5.00
4 Banana $3.00
6 Mango $5.00
How can convert that, to Linq with Lambda? I tried .GroupBy(), but only groups with a Key (fruitName) in another object, example:
Apple
1 $5.00
2 $6.00
3 $6.00
Banana
4 $3.00
5 $2.00
Mango
6 $5.00
May you help me? thanks!
UPDATE:
In MySQL this is the logic that I used, and it works without problem
No, you can't have "something like" the table above from such a query. Your SQL query would crash and burn because you're selecting columns (id and price) that don't appear in the group by clause.
What are you actually trying to do?
It looks like you're trying to get the Fruit with the lowest id in its respective group when grouped by fruitName.
In which case, something like:
fruits
.GroupBy(fr => fr.fruitName)
.Select(grp => grp.OrderBy(fr => fr.Id).First())
would do the trick.
I did it in this form:
fruits.
GroupBy(p => p.idFruit,
p => p.fruitName,
(key, g) => new {
idFruit = key,
fruitName= g.ToList().FirstOrDefault()
}
)
when you use group by you need to aggregate all of the other columns, like max min ...etc. otherwise sql query will not execute.
in C# side, it's slightly different, cause you can produce the same sql or you can aggregate in memory, after pulling all of the records.
EX: LINQ format:
from f in fruits
group by f.fruitName into g
select new{
fruit= f.fruitName,
price= g.Max() // or whatever selector to grab one of the prices.
}
Method groups will look like:
fruits.GroupBy(f=> f.fruitName).Select(f=> new {
fruit= f.Key,
price= f.MAx() // or any other wat to select one of the prices
});
List<Tuple<string, int>> Fruits = new List<Tuple<string, int>>();
Fruits.Add(Tuple.Create("Apple", 10));
Fruits.Add(Tuple.Create("Apple", 5));
Fruits.Add(Tuple.Create("Apple", 20));
Fruits.Add(Tuple.Create("Mango", 10));
Fruits.Add(Tuple.Create("Orange", 10));
Fruits.Add(Tuple.Create("Mango", 20));
Fruits.Add(Tuple.Create("Orange", 12));
foreach (var item in Fruits.GroupBy(s => s.Item1))
{
string FruitName = item.Key;
StringBuilder sb = new StringBuilder();
foreach (var price in item)
sb.Append(price.Item2 + ", ");
Console.WriteLine(string.Format("Fruit Name :{0} Prices :{1}", FruitName, sb.ToString().Trim(new char[] { ',', ' ' })));
}
output:
Fruit Name :Apple Prices :10, 5, 20
Fruit Name :Mango Prices :10, 20
Fruit Name :Orange Prices :10, 12

Joining parent and child collection in LINQ for a single merged output

I have a collection of items which each contain a secondary collection. I need to generate a list of new items based off the total set of secondary items. Eg if you have a list of dogs and each dog has a list of friends, I need a total list of all dog friends for all dogs.
Normally I would do this with SelectMany but the difficulty here is my secondary objects do not contain a reference back to the parent object, and the new list needs to contain fields from both secondary and parent objects. ie as below
var dogs = List<Dog>
var newList = dogs.SelectMany(i => i.DogFriends).Select(dogFriend => new NewObject
{
fieldOne = dog.propertyOne,
fieldTwo = dog.propertyTwo
fieldThree = dogFriend.propertyOne
});
In the above, I have no reference to dog however, so I cannot select those.
What I'm looking for is a way to do a join on the two tables as in SQL, so you can select values from both objects. But I want the total values to match the total number of child objects. Ie if you have 3 dogs with 3 friends each, the final list will have 9 objects.
How can I achieve this with LINQ?
Indeed .SelectMany(...) is the answer. Another usage is:
var data = dogs
.SelectMany(d => d.DogFriends.Select(df => new { d, df })
.Select(x =>
// Now you can use both d and df
new NewObject {
fieldOne = x.d.propertyOne,
fieldTwo = x.d.propertyTwo
fieldThree = x.df.propertyOne
}
)
.ToArray();
Even simpler is to use the query LINQ notation. This basically translates to the above at compile time.
var data = from d in dogs
from df in d.DogFriends
select new NewObject {
fieldOne = d.propertyOne,
fieldTwo = d.propertyTwo
fieldThree = df.propertyOne
}
Use .SelectMany with the resultSelector overload
var query = petOwners.SelectMany(
petOwner => petOwner.Pets,
(petOwner, petName) => new { petOwner, petName }
)

Merging lists of IEnumerable type using a common attribute's value

I have 6 IEnumerable<SalesSummaryDAO> type lists.
SalesSummaryDAO is like below,
public class SalesSummaryDAO {
public int Year {get;set;}
public decimal? Cost {get;set;}
}
Here the lists are ;
IEnumerable<SalesSummaryDAO> FabricCost = getFabricCostDataByYear();
IEnumerable<SalesSummaryDAO> AccessoryCost = getAccessoryCostDataByYear();
IEnumerable<SalesSummaryDAO> FOBRevenue = getFobRevenueDataByYear();
IEnumerable<SalesSummaryDAO> StockCost = getStockLotsCostDataByYear();
IEnumerable<SalesSummaryDAO> StockRevenue = getStockLotsRevenueDataByYear();
IEnumerable<SalesSummaryDAO> FixedCost = getFixedCost();
As you can see each list contains a set of objects of SalesSummaryDAO type. What I need to do is, I want to calculate Net Profit for each and every year using those lists. As an example, for 2015 net profit is calculated like below :
NetProfit (2015) = FOBRevenue(2015) + StockRevenue(2015) - FabricCost(2015) - AccessoryCost(2015) - StockCost(2015) - FixedCost(2015)
After calculating the net profit for each year I want to store them in another IEnumerable<SalesSummaryDAO> list, where cost variable holds the net profit for each year.
You can use a grouping:
var resultList = FabricCost.Concat(AccessoryCost) // append together
.Concat(FOBRevenue) // append together
.Concat(StockCost) // append together
.Concat(StockRevenue) // append together
.Concat(FixedCost) // append together
.GroupBy(x => x.Year) // group by year
.Select(g => new SalesSummaryDAO ()
{
Year = g.Key,
Cost = g.Sum(x => x.Cost)
}) // project to SalesSummaryDAO
.ToList(); // make it a real list!
This would group all your list by year and sum them up- just make the items you want to subtract have negative costs (e.g. fabric costs).

Using a nested foreach to store one foreach value into an array

Alright so I didn't really know how to word this question, but I did my best. The goal I am trying to accomplish is to go through categories using a foreach loop. Inside the category foreach loop another foreach loop will go through Numbers. Right now it is grabbing ever value in the tables and storing them into an array. My goal is to only store the highest number in each category into the array.
Here is how the tables would look:
Category Table
Title NumberId
Type 1
Priority 2
Likelihood 3
Numbers Table
Order NumberId
3 1
2 1
1 1
3 2
2 2
1 2
3 3
2 3
1 3
So my goal would be instead of storing every order value into the array. I would like to store the highest number according to each number id. So there array would include 3,3,3.
This is what I have that stores every number into an array:
int[] values = new int[count];
foreach(var x in Category)
{
foreach(var w in x.Numbers)
{
values[y] = w.Order;
y++;
}
}
Solution:
int[] values = new int[count];
foreach(var x in Category)
{
foreach(var w in x.Numbers)
{
values[y] = x.Numbers.Select(o => o.Order).Max();
y++;
break;
}
}
You can use IEnumerable.Max() :
foreach(var x in Category)
{
values[y] = x.Numbers.Select(o => o.Order).Max();
y++;
}
This can be accomplished relatively easily through LINQ as:
int[] values = new int[count];
foreach(var x in Category)
{
values.[y] = x.Numbers.OrderBy(w => w.Order).Reverse().First();
y++;
}
This orders the x.Numbers by their ascending order, reverses the order (to place the highest value first in the order), and then selects the first value.
Ensure with this method that you've actually got a value for x.Number, else you'll get an exception thrown by the .First() call.
If you're unable to use LINQ (e.g. if you're on .NET 2.0), then consider using a Dictionary with the Category as the key, and the highest Number as the value:
Dictionary<int, int> categoriesByHighestOrders = new Dictionary<int, int>();
foreach(var x in Category)
{
if (!categoriesByHighestOrders.Keys.Contains[x.SomeIndetifier])
categoriesByHighestOrders.Add(x.SomeIdentifier, 0);
foreach(var w in x.Numbers)
{
if (categoriesByHighestOrders[x.SomeIndetifier] < w.Order
categoriesByHighestOrders[x.SomeIndetifier] = w.Order;
}
}

Categories