I have collection of Customers, potentially group-able by their preferences (oranges, apples)
CustomerID | Preference | Age
1 oranges 35
2 apples 32
... ... ...
100 oranges 48
I need kind of of summary table so can group Customers into new collection like this:
var GroupedCustomers = Customers
.GroupBy (a => new {a.Preference, ...}) //could be grouped by more complex compound key
.Select (a => new { CustomerPreference = a.Key.Prefence, TotalOrders = a.Count () })
How can I access the inner collections of each group with all original properties of their members (e.g. "Age" of each customer)? For example, I need to bind the list of "orange lovers" to a GridView and compute their average age.
The issue in the actual case is a complex compound key and hundreds of groups so I don't want to enumerate them every time from the original collection.
You need to bind the ungrouped collection to the GridView, then you can apply the filters, keeping the View and the ViewModel synchronized
Follow the documentation example under How to: Group, Sort, and Filter Data in the DataGrid Control
Comments
Binding is just subcase. I want to find general approach. Let say I
want to proceed with some math manipulation on the many properties of
the group members (like age). Does you answer mean whatever I apply
GroupBy, all properties that weren't been included to the group key
are lost?
No, they aren't. Add the list of the grouped items to the group, for example
var GroupedCustomers = Customers
.GroupBy (c=> new {a.Preference, ...}, //could be grouped by more complex compound key
c => c,
(key, g) => new {
CustomerPreference = key.Preference,
SubItems = g.ToList(), // <= this is your inner collection
...})
This my own attempt.
We should limit initial statement by GroupBy only to keep collection unchanged:
var GroupedCustomers = Customers
.GroupBy (a => new {a.Preference, ...});
Then, the inner collections would be as simple as:
var GroupByIndex = GroupedCustomers[0]; //retrieve entire group by its index
var GroupdByContent = GroupedCustomers.First(i => i.Key.Preference == "oranges") //retrieve group by key
Related
This question already has answers here:
C# Linq Grouping
(2 answers)
Closed 1 year ago.
Struggling with the concept of this: I have a method that returns an IEnumerable like so:
public IEnumerable<IFruit> FruitStall(IEnumerable<IFruit> fruits)...
and each IFruit is as follows
public decimal Amount { get; }
public string Code { get; }
and my FruitsStall method returns one IFruit per currency with the sum of all money of the same code.
e.g
{APL10, APL20, APL50} => {APLE80}
or
{APL10, APL20, ORG50} => {APL30, ORG50}
Can anyone point me in the right direction of this? Not too sure how to go about this.
I was looping through the IEnumerable with a
foreach (var item in fruits)
{
fruits.Code
}
but unsure where to go there on
When you said:
{APL10, APL20, APL50} => {APLE80}
or
{APL10, APL20, ORG50} => {APL30, ORG50}
If you meant
I have this: {new Fruit("APL",10), new Fruit("APL", 20), new Fruit("ORG",50)} and I want to generate a list like {new Fruit("APL",30), new Fruit("ORG",50)}
I would say:
You need to have some container that can hold all the different codes and map them to a sum of Amounts. For this we often use a dictionary:
var d = new Dictionary<string,decimal>();
foreach(var f in fruit){
if(!d.ContainsKey(f.Code))
d[f.Code] = f.Amount;
else
d[f.Code] += f.Amount;
}
At the end of this operation your dictionary will contain a unique set of fruit codes and the sum of all the amounts. You can turn it back into a list of fruit by enumerating the dictionary and creating a list in a similar way to how you created the list initially
Once you get your head round that, you can take a look at using LINQ, and do something like:
var summedFruits = fruits
.GroupBy(f => f.Code)
.Select(g => new Fruit(g.Key, g.Sum(f => f.Amount)));
(This assumes your Fruit has a constructor that takes a Code and an Amount). When you GroupBy in LINQ you get an output that is like a List of Lists. The original input list is broken up into some number of lists; where everything in each list has the same value for what you declared was the key (I said to group by code)
So your original representation:
{APL10, APL20, ORG50}
Would end up looking like:
Key = APL, List = {APL10, APL20}
Key = ORG, List = {ORG50}
Your "one list of three things" has become "a list of (a list of two APL ) and (a list of one ORG)"
If you then run a select on them you can create a new Fruit that uses the Key as the code and sums up the amount in each list
Key = APL, List = {APL10, APL20}, Sum = 30
Key = ORG, List = {ORG50}, Sum = 50
In this code:
var summedFruits = fruits
.GroupBy(f => f.Code)
.Select(g => new Fruit(g.Key, g.Sum(f2=> f2.Amount)));
f is a fruit, one of the items in the original list of 3 fruits. We group by the fruit's code. g is the result of the grouping operation, it is a "list of fruit with a common code", the Key is the code (apl or org). g is a list, so you can call sum on it. Every item inside the g list is a fruit, which is why I switch back to f (when I say f2), to help remember that it's an individual fruit- we're summing the amount. For the first list of APL the sum is 30. At the end of the operation a new List results; one with two elements - an APL and an ORG, but the Amounts are the summations of all
var res = fruits.GroupBy(x => x.Code).ToDictionary(g => g.Key, g => g.Sum(x => x.Amount));
I have this dictionary Dictionary<string, List<Payments>>, which holds employees, each employee having a list of payments. The Payments class has a string property named PayCategoryId. I want to filter this dictionary and get only the employees with payments having some specific PayCategoryId values and for each employee only those payments. I'm pretty sure this can be achieved by using LINQ, but I have almost zero experience with LINQ, so need your help.
The original (unfiltered) dictionary has 76 items (employees). The employee which I'll use as an example has 27 payments, some of them having the required PayCategoryId.
What I've done:
List with the required PayCategoryId:
var payCategoriesID = new List<string> (){ "a", "b", "c" };
Semi-filter the dictionary with this LINQ (I'm sure it's a mess, but is working!):
var result = dict.Where(o => o.Value.Where(x => payCategoriesID.Contains(x.PayCategoryId)).Any()).ToDictionary(mc => mc.Key, mc => mc.Value);
The semi-filtered resulted dictionary has only 34 items. The employees having no payments with the required PayCategoryId were filtered out. But my example employee still has all 27 payments in the list. I need his list to be filtered too and have only the payments having PayCategoryId = one of the IDs from payCategoriesID list.
Of course, the example employee is just an example, all employees should be filtered.
Can you help, please?
Adrian
You can use Select() to project into an enumerable of anonymous type and get only the value you want, then build back a dictionary.
var match = dict
.Select(kv => new
{
Employee = kv.Key,
Payments = kv.Value.Where(p => payCategoriesID.Contains(p.PayCategoryId))
})
.Where(emp => emp.Payments.Any())
.ToDictionary(k => k.Employee, v => v.Payments);
The first step creates an object with key and filtered values, then the Where() remove the empty lists. That way you only iterate through the ids list once (per element on the dictionary).
Also, not really related, but the Any() method has an overload which takes a predicate, so instead of
o.Value.Where(x => payCategoriesID.Contains(x.PayCategoryId)).Any()
you can do directly
o.Value.Any(x => payCategoriesID.Contains(x.PayCategoryId))
Same goes for other LINQ methods such as Count(), First(), FirstOrDefault(), Last() and more.
var result = dict.Where(o => o.Value.Where(x => payCategoriesID.Contains(x.PayCategoryId)).Any()).ToDictionary(mc => mc.Key, mc => mc.Value.Where(t=> payCategoriesID.Contains(t.PayCategoryId)));
Following from my question previously here
I used
var distinctAllEvaluationLicenses = allEvaluationLicenses.GroupBy((License => License.dateCreated)).OrderByDescending(lics => lics.Key).First();
To group the IQueryable
allEvaluationLicenses
by using License's property 1 which is 'dateCreated'
But now, how can I order them by using a different property such as 'nLicenceID'?
Is it possible to do something like this:
var distinctAllEvaluationLicenses = allEvaluationLicenses.GroupBy((License => License.dateCreated)).OrderByDescending(lics => (sort by nLicenseID here) ).First();
For LINQ-to-Objects, the objects inside each group retain the ordering in which they are discovered:
The IGrouping<TKey, TElement> objects are yielded in an order based on the order of the elements in source that produced the first key of each IGrouping<TKey, TElement>. Elements in a grouping are yielded in the order they appear in source.
So: if your aim is to order the contents of each group, simply order the source:
var distinctAllEvaluationLicenses = allEvaluationLicenses
.OrderByDescending({whatever})
.GroupBy({etc}).First();
Note that this is not guaranteed to work for other LINQ sources, and note that it doesn't influence the order in which the groups are presented. To do that you could perhaps do something like:
var distinctAllEvaluationLicenses = allEvaluationLicenses
.GroupBy({etc}).
.OrderBy(grp => grp.Min(item => x.SomeProp)).First();
which would present the groups in order of the minimum SomeProp in each. Obviously adjust to max / etc as necessary.
To sort the items within the group you can use Select:
var distinctAllEvaluationLicenses = allEvaluationLicenses.GroupBy(License => License.dateCreated)
.Select(group => group.OrderByDescending(item => item.nLicenceID));
I have a table structure, each table with a primary key field named as "ID" and foreign key names matching their parent table's primary key. Therefore, the tables below have relationships where their primary key appears in another table and the first field in any table is it's primary key:
Category
--------
CategoryID
Title
CategoryList
------------
CategoryListID
CategoryID
ListID
List
----
ListID
Title
DataPoint
---------
DataPointID
RecordedDateTime
DataPointValue
--------------
DataPointValueID
DataPointID
TheValue
The above is a many-to-many join between Category and List, via CategoryList. It is also a one-to-many join from List to DataPoint, DataPoint to DataPointValue.
Using C#/LINQ and given a List of the CategoryID values, I would like to retrieve:
All the List entries attached to the Category I have ID's for. With those List entries, I would like to take the most recent 1 DataPoint, as ordered by RecordedDateTime Descending. From there I would like to retrieve every DataPointValue attached to the DataPoint.
The LINQ I have is:
DBDataContext context = new DBDataContext(ConnectionString);
context.LoadOptions = new DataLoadOptions();
context.LoadOptions.LoadWith<DataPoint>(p => p.DataPoints.OrderByDescending(p.RecordedDataTime).FirstOrDefault());
// this next line is how I get the list of category IDs, but don't worry about that...
List<int> categoryIDs = (from TreeNode n in nodes
select Int32.Parse(n.Value)).Distinct().ToList();
var lists = from i in context.List
join ci in context.CategoryLists on i.ListID equals ci.ListID
join p in context.DataPoints on i.ListID equals p.ListID
join v in context.DataPointValues on p.DataPointID equals v.DataPointID
where categoryIDs.Contains(ci.CategoryID)
orderby i.Title ascending
select new
{
List = i,
DataPoint = p,
DataPointValues = p.DataPointValues
};
But this is obviously not working - the LoadWith is causing me issues. Could someone explain how to construct the LoadWith so that it will cause as few SQL queries as possible to retrieve this (admittedly large) amount of data, please?
Many thanks,
Matt.
You asked this a month ago, but here's an answer anyway...
There are a few issues here:
Once you set the LoadOptions property on the context, you can't change it. You should create and configure your DataLoadOptions object, and when you're done, assign it to the context.
LoadWith specifies what children get automatically loaded with the parent. So loadOptions.LoadWith<DataPoint>(p => p.DataPointValues) would automatically load the DataPoint's children, and not wait until the DataPoint.DataPointValues (or whatever you name it) property is accessed. LoadWith makes the loading non-lazy (eager).
AssociateWith allows you to filter and order the in children that automatically-loading relationship. For example loadOptions.AssociateWith<DataPoint>(p => p.DataPointValues.OrderByDescending(v => v.TheValue)) would sort the DataPointValues by value.
And finally, I'd probably break up your query into two, just to make it easier.
// first setup a DataPoint -> DataPointValue relationship in your DBML
// then set up the DataPointValues to automatically load with DataPoint:
dataLoadOptions.LoadWith<DataPoint>(dp => dp.DataPointValues);
// then assign the load options to the context here
// First query
List<int> listIDs = context.CategoryLists
.Where(cl => categoryIDs.Contains(cl.CategoryListID))
.Select(cl => cl.ListID)
.ToList();
// Second query(ies) - this isn't the most elegant, but simple is usually better :)
List<DataPoint> dataPoints = new List<DataPoint>();
foreach (int listID in listIDs)
{
DataPoint foundDP = context.DataPoints
.Where(dp => listIDs.Contains(dp.ListID))
.OrderByDescending(dp => dp.RecordedDateTime)
.Take(1)
.SingleOrDefault();
// Remember, at this point DataPointValues will already be loaded into the DataPoint
if (foundDP != null)
dataPoints.Add(foundDP);
}
Anyway, that's a longwinded answer that you may or may not even need! Ah well, it's practice for me, I guess. Hope it helps.
EDIT:
Sorry, started thinking about this...
You could possibly do this instead (cleaner, faster):
loadOptions.LoadWith<List>(l => l.DataPoints);
loadOptions.AssociateWith<List>(l => l.DataPoints.OrderByDescending(dp => dp.RecordedDateTime).Take(1));
loadOptions.LoadWith<DataPoint>(dp => dp.DataPointValues);
// assign the LoadOptions here,
// then:
List<DataPoint> dataPoints = context.CategoryLists
.Where(cl => categoryIDs.Contains(cl.CategoryID))
.Select(cl => cl.List.DataPoints)
.ToList();
I have an IEnumerable of items that I would like to group by associated categories. The items are grouped by the categories that are associated with them - which is a List - so a single item can potentially be a part of multiple categories.
var categories = numbers.SelectMany(x => x.Categories).Distinct();
var query =
from cat in categories
select new {Key = cat,
Values = numbers.Where(n => n.Categories.Contains(cat))};
I use the above code, and it does in fact work, but I was wondering if there was a more efficient way of doing this because this operation will likely perform slowly when numbers contains thousands of values.
I am pretty much asking for a refactoring of the code to be more efficient.
You can use LINQ's built-in grouping capabilities, which should be faster than a contains lookup. However, as with any performance-related question, you should really write code to collect performance metrics before deciding how to rewrite code that you know works. It may turn out that there's no performance problem at all for the volumes you will be working with.
So, here's the code. This isn't tested, but something like it should work:
var result = from n in numbers
from c in n.Categories
select new {Key = c, n.Value}
into x group x by x.Key into g
select g;
Each group contains a key and a sequence of values that belong to that key:
foreach( var group in result )
{
Console.WriteLine( group.Key );
foreach( var value in group )
Console.WriteLine( value );
}