How does GroupBy in LINQ work? - c#

I originally have a dictionary of <string, List<ProviderSummary>> called rowsDictionary
Now for each key of that dictionary I group its list of values by some criteria as below:
Dictionary<string, List<ProviderSummary>> providerGroups = rowsDictionary.ToDictionary(
x => x.Key,
v => v.Value.GroupBy(x => new { x.GroupID, x.GroupFin, x.ZipCode })
.Select(x => x.First())
.ToList());
so for example if key["1234"] originally had 6 items in its list of values, now it may have two items based on that grouping. My question and confusion is what happens to the rest of the values? ( those four) and what values will go in to these two lists that are returned for the group?

Group by works by taking whatever you are grouping and putting it into a collection of items that match the key you specify in your group by clause.
If you have the following data:
Member name Group code
Betty 123
Mildred 123
Charli 456
Mattilda 456
And the following query
var query = from m in members
group m by m.GroupCode into membersByGroupCode
select membersByGroupCode;
The group by will return the following results:
You wouldn’t typically want to just select the grouping directly. What if we just want the group code and the member names without all of the other superfluous data?
We just need to perform a select to get the data that we are after:
var query = from m in members
group m by m.GroupCode into membersByGroupCode
let memberNames = from m2 in membersByGroupCode
select m2.Name
select new
{
GroupCode = membersByGroupCode.Key,
MemberNames = memberNames
};
Which returns the following results:
 

What values will go in to the lists that are returned for the group?
The first for each group, because you do:
.Select(x => x.First())
What happens to the rest of the values?
They will not be projected into your target dictionary.

Your LINQ group by query takes the original list, performs additional grouping on it, and then prunes the list based on that grouping.
Consider a situation where a single list contains these items:
GroupID GroupFin ZipCode Name
------- -------- ------- ----
1 1 94111 A
1 1 94111 B
1 1 94111 C
1 1 94111 D
1 2 94110 E
1 2 94110 F
Group by would make two groups out of this list of six:
GroupID=1 GroupFin=1 ZipCode=94111
GroupID=1 GroupFin=2 ZipCode=94110
The first group would contain providers A, B, C, and D; the second group would contain E and F.
The next thing that your query does is applying First. This operation picks the initial item from the group; in this case, it would be A and E. The remaining items are thrown ignored.

Related

LINQ method syntax to group by a column, pick one element from each group with total count per group

I am looking for a LINQ query using the method syntax to group by a column, pick the first member of each group and add total count of each group into the selected entities of each group.
Is there a way to achieve this in a single elegant statement in LINQ method syntax?
Input:
OrderId Name Category
=============================
1 Sam X
2 Sam Y
3 Matthew A
4 Matthew B
Output:
OrderId Name Category Count
======================================
1 Sam X 2
4 Matthew B 2
Something like this. The value of category is irrelevant to me, I just want to get any element from the group.
Should be as simple as a GroupBy with a projection and some aggregates
var results = someList
.Group(x => x.Name)
.Select(x => new Entity()
{
Name = x.Key,
OrderId = x.First().OrderId,
Category = x.First().Category,
Count = x.Count()
});

Scan Lists of Objects and compare an Object Value

For example I have 3 lists (or more):
List1:
[{store:"store1",item:"item1",price:10},{store:"store1",item:"item2",price:5},{store:"store1",item:"item4",price:100},{store:"store1",item:"item10",price:10}]
List2:
[{store:"store2",item:"item1",price:15},{store:"store2",item:"item2",price:10},{store:"store2",item:"item10",price:110}]
List3:
[{store:"store3",item:"item1",price:5},{store:"store3",item:"item2",price:10},{store:"store3",item:"item10",price:100},{store:"store3",item:"item100",price:1}]
As you can see It's like 3 stores with different items and prices. Not all stores have all items so I would like to make a list by comparing the lists and finding the objects that contain "item1" for example and then choose the cheaper price. And also to compare the lists 1 by one (list 1 with list 2 , list 1 with list 3, list 2 with 1 and list 2 with 3). Do I make any sense?
Any answer is appreciated.
I've tried some things but I just cant understand it (and its for 2 stores):
var result = (from l1 in store1list join l2 in store2list on l1.Symbol equals l2.Symbol orderby l1.Symbol select new
{
store = l1.store,
price = l1.price,
item = l1.item
}).ToList();
You may Union your lists and then GroupBy item, and select ordering each group with price and taking the first one (cheapest) from each group.
var result = List1.Concat(List2).Concat(List3).GroupBy(x => x.item)
.Select(g => g.OrderBy(x=> x.price).First()).ToList();

Get list of items where their ID is equal to some Values - linq c#

Here is a query:
from order in db.tblCustomerBuys
where selectedProducts.Contains(order.ProductID)
select order.CustomerID;
selectedProducts is a list containing some target products IDs, for example it is { 1, 2, 3}.
The query above will return customerIDs where they have bought one of the selectedProducts. for example if someone has bought product 1 or 2, its ID will be in result.
But I need to collect CustomerIDs where they have bought all of the products. for example if someone has bought product 1 AND 2 AND 3 then it will be in result.
How to edit this query?
the tblCustomerBuys are like this:
CustomerID - ID of Customer
ProductID - the product which the customer has bought
something like this:
CustomerID ProdcutID
---------------------------
110 1
110 2
112 3
112 3
115 5
Updated:
due to answers I should do grouping, for some reason I should use this type of query:
var ID = from order in db.tblCustomerBuys
group order by order.CustomerID into g
where (selectedProducts.All(selProdID => g.Select(order => order.ProductID).Contains(selProdID)))
select g.Key;
but it will give this error:
Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.
The updated query is the general LINQ solution of the issue.
But since your query provider does not support mixing the in memory sequences with database tables inside the query (other than Contains which is translated to SQL IN (value_list)), you need an alternative equivalent approach of All method, which could be to count the (distinct) matches and compare to the selected items count.
If the { CustomerID, ProductID } combination is unique in tblCustomerBuys, then the query could be as follows:
var selectedCount = selectedProducts.Distinct().Count();
var customerIDs =
from order in db.tblCustomerBuys
group order by order.CustomerID into customerOrders
where customerOrders.Where(order => selectedProducts.Contains(order.ProductID))
.Count() == selectedCount
select customerOrders.Key;
And if it's not unique, use the following criteria:
where customerOrders.Where(order => selectedProducts.Contains(order.ProductID))
.Select(order => order.ProductID).Distinct().Count() == selectedCount
As your question is written, it is a bit difficult to understand your structure. If I have understood correctly, you have an enumerable selectedProducts, which contains several Ids. You also have an enumeration of order objects, which have two properties we care about, ProductId and CustomerId, which are integers.
In this case, this should do the job:
ver result = db.tblCustomerBuys.GroupBy(order => order.CustomerId)
.Where(group => !selectedProducts.Except(group).Any())
.Select(group => group.Key);
What we are doing here is we are grouping all the customers together by their CustomerId, so that we can treat each customer as a single value. Then we are treating group as a superset of selectedProducts, and using a a piece of linq trickery commonly used to check if one enumeration is a subset of another. We filter db.tblCustomerBuys based on that, and then select the CustomerId of each order that matches.
You can use Any condition of Linq.
Step 1 : Create list of int where all required product id is stored
Step 2: Use Any condition of linq to compare from that list
List<int> selectedProducts = new List<int>() { 1,2 } // This list will contain required product ID
db.tblCustomerBuys.where(o=> selectedProducts .Any(p => p == o.ProductID)).select (o=>o.order.CustomerID); // This will return all customerid who bought productID 1 or 2

Separate list of strings into a new list depending on its first letter

I have a list of country names
Afghanistan
Albania
Bahamas, The
Bahrain
Cambodia
Cameroon
.
.
.
What I want to do is separate this list into other lists depending on the first letter.
So basically I want to have a list of countries that begin with a, b, c, ......
So, you have a collection of strings. You can use LINQ to group them and convert them to a Dictionary.
First, you'll need to group them based on the first letter (in this situation, case matters so a and A will be treated differently) using GroupBy(n => n[0]) where n[0] gets the first character in the string.
Second, you'll want to convert the grouping to something that you can use an indexer with. A Dictionary would be perfect. Use ToDictionary(g => g.Key, g => g).
When you string it together, it'll look like:
var dict = names.GroupBy(n => n[0]).ToDictionary(g => g.Key, g => g);
And allow you to get the grouped names using:
foreach(var n in dict['A'])
{
// Print out each country starting with 'A'
Console.WriteLine(n);
}
You can do it like this:
var countriesWithStartingLetterA = countries.Where(x => x.StartsWith("A")).ToList();

LINQ: Selecting items from a list (Group By/Select/Sum & Max!)

Just getting my head around Linq and having lots of fun! Can any one aid me with a query for this:
I have a list of data:
Key Value
Aaa 12
AaA 10
AAa 5
BBB 2
Bbb 1
1. I want to group by Key.ToUpper()
2. For every group I need the Max(Value) & Sum(Value)
3. For every group I want to select the entries
There the Value != Max(value)
the final result should be like this:
Key Max Total
AaA 12 27
AAa 12 27
Bbb 2 3
Thanks!
Update, actually I also need the Key from the Maximum entry:
Key Max Total Correct
AaA 12 27 Aaa
AAa 12 27 Aaa
Bbb 2 3 BBB
:)
var results =
from kvp in source
group kvp by kvp.Key.ToUpper() into g
select new
{
Group = g,
Max = g.Max(kvp => kvp.Value),
Total = g.Sum(kvp => kvp.Value)
} into ag
from x in ag.Group //SelectMany
where x.Value != ag.Max
//for the update to the question - note: possibly ambiguous
let correct = ag.Group.Where(y => y.Value == ag.Max).First().Key
select new
{
Key = x.Key,
Max = ag.Max,
Total = ag.Total,
Correct = correct
};
I kinda like the question because of all the little parts (some are rarely used) that are required to make the answer.
Max = g.Max(kvp => kvp.Value),
Total = g.Sum(kvp => kvp.Value)
Performing multiple aggregations on a group is straightforward, yet challenging if you don't know how.
select a into b
This clause takes everything that happened before and starts a new query with the target. Without it, I'd have to start a new query like this:
var A = ... select a
var B = from b in A
It's important to note that the select into clause removes kvp and g from scope.
from b in source
from a in b.A //SelectMany
This "unpacking" of the child collection turns my query about b's into a query about a's. Unlike the default Enumerable.SelectMany overload, it leaves the parent (b) in scope.
where x.Value != ag.Max
Comparing a child's property with a parent's property? Delightful. It's important to remember to break out where anytime you want to filter, even if you just grouped (there is no HAVING).

Categories

Resources