Linq groupby with where clause - c#

I am attempting to group my items and have a where clause included and I am not quite sure where to put my items.
here is what i have so far:
#{
var trust = new trusteeEntities();
var gen = (from g in trust.Documents
where g.doc_type == "Minutes"
orderby g.meeting_date descending
group g by g.meeting_date into f
select g);
foreach (var f in gen)
{
<div class="documents">
<span class="date">#string.Format("{0:MMMM d, yyyy}", f.meeting_date)</span>
<p>#f.title</p>
</div>
}
}

You have to order the items after the grouping as GroupBy does not keep order. Moreover, you select the wrong items. To select the groups use select f instead of select g.
from g in trust.Documents
where g.doc_type == "Minutes"
group g by g.meeting_date into f // Groups the items g into groups called g
orderby f.Key descending // Orders the groups by their key (which corresponds to g.meeting_date)
select f // Selects the group
I also would highly recommend you to rename your variables:
from document in trust.Documents
where document.doc_type == "Minutes"
group document by document.meeting_date into documentGroup // Groups the items g into groups called g
orderby documentGroup.Key descending // Orders the groups by their key (which corresponds to document.meeting_date)
select documentGroup // Selects the group
To show the groups (not sure of that part because I've never written ASP.NET code nor HTML code):
foreach (var documentGroup in gen)
{
<div class="documents">
<span class="date">#string.Format("{0:MMMM d, yyyy}", documentGroup.Key)</span>
foreach (var document in documentGroup)
{
<p>#f.title</p>
}
</div>
}
Update
Given the code in the foreach, I think you don't need to group the documents by their date. If so, the Linq query is:
from document in trust.Documents
where document.doc_type == "Minutes"
orderby document.meeting_date descending
select document

Related

Group by with linq to update collection

I have a IEnumerable collection called allEventsDetails and collection properties
are Digit, Name and status.
I need to update the same collection if Two or more Digits values are equal.
var categories = from p in allEventsDetails
group p by p.Digit into g
where g.Count() > 1
select g.Select(s => s.status = "true");
How can I use linq to do this?
you could do something like this:
(from x in allEventsDetails
group x by x.Digit into y
where y.Count()>1
select y).SelectMany(z=>z).ToList().ForEach(x=>x.Status="true");
What you could also do is:
var temp= (from x in allEventsDetails
group x by x.Digit into y
where y.Count()>1
select y).SelectMany(z=>z);
foreach (var x in temp)
{
x.Status= "test";
}
This way you don't have to create a "temporary" list.

Why is iterating a group different than iterating a collection in linq

In the query below it is easy to see how we are basically looping through sampleOrders and evaluating each order i.e. we can access the properties of ord:
var o = (
from ord in sampleOrders
select ord.Quantity
In the query below we are grouping orders by product into orderGroups. orderGroups is conceptually a "list of lists" where each list has a key and a property which contains associated orders. The preceeding description is very loose of course - the point is that if I iterate the collection I would expect to receive an object with a key property and a property that contains the orders. This behavior seems to be consistent with the first query above. But in fact each group is an order. It appears by iterating the list of groups that we bypass the group itself and in fact iterate each order.
Why is this grouping behavior different from the behavior in the first query above?
var o = (
from ord in sampleOrders
group ord by ord.Product into orderGroups
from grp in orderGroups
select grp // grp is an order, not a group of orders
BTW, Please note I am not asking how to access the groups..... my question is related to the concept and the syntax.
Here is how to access each group.... I am not asking how to do this:
var o = (
from ord in sampleOrders
group ord by ord.Product into orderGroups
select new {orderGroups.Key, SumQty=orderGroups.Sum(x => x.Quantity)}
Edit: Working example per request
public static void LinqTest()
{
List<Order> sampleOrders = new List<Order>()
{
new Order{Product="A", Quantity = 3},
new Order{Product="A", Quantity = 4},
new Order{Product="B", Quantity = 5},
new Order{Product="C", Quantity = 6},
};
var orders =
from ord in sampleOrders
group ord by ord.Product into orderGroups
select orderGroups;
foreach (var grp in orders) // here is the outer list
{
Console.WriteLine("Product: " + grp.Key.ToString());
// here is the inner list hence the term "list of lists"
// it is in fact a hierarchical object however this fact is irrelelvent to my question
//
foreach (Order ord in grp)
Console.WriteLine("Qty: " + ord.Quantity);
}
Console.WriteLine();
Console.WriteLine("following listing is grouped orders but we see each order not groups");
var orders2 =
from ord in sampleOrders
group ord by ord.Product into orderGroups
from grp in orderGroups
select grp;
foreach(Order ord2 in orders2)
Console.WriteLine("Product: " + ord2.Product); // each item returned is an order not a group of orders
Console.ReadKey();
}
}
class Order
{
public string Product { get; set; }
public int Quantity { get; set; }
}
EDIT 2:
Here is another stab at clarifying my question:
In the first query above "from x in listOfx" gives you an object, x, which is the same object that listOfx is composed of.
In the group query, "from x in listOfxGroups" gives you an object which is different than the objects that compose listOfxGroups. listOfxGroups is in fact a list of groups... but when we iterate it we get x not "group of x".
from ord in sampleOrders
group ord by ord.Product into orderGroups
from grp in orderGroups
select grp
At a glance, one might think that this translates to this:
sampleOrders.GroupBy(ord => ord.Product)
.Select(grp => grp); // grp is an `IGrouping<,>`
But in actuality, it translates to this:
sampleOrders.GroupBy(ord => ord.Product)
.SelectMany(orderGroup => orderGroup); // orderGroup is an `IGrouping<,>`
.Select(grp => grp); // grp is an Order
This is how LINQ statements are translated: after the initial from keyword, any remaining froms produce a SelectMany. For example:
A query expression with a second from clause followed by a select clause
from x1 in e1
from x2 in e2
select v
is translated into
( e1 ) . SelectMany( x1 => e2 , ( x1 , x2 ) => v )
C# 5 language spec, section 7.16.2.4
from a in x
from b in a
select b
Now, the lambda expression in SelectMany must return an IEnumerable<T> (which it does, because IGrouping<TKey, TValue> is also an IEnumerable<TValue>), and SelectMany flattens out all of the IEnumerable<T>s into one big IEnumerable<T>, which means that the individual items in it are Orders, in this case.
Update
Just to help clarify, imagine the following:
var orderGroups = // orderGroups is plural: IEnumerable<IGrouping<,>>
from ord in sampleOrders
group ord by ord.Product;
var o =
from orderGroup in orderGroups // orderGroup is singular: IGrouping<,>
from item in orderGroup // `item` instead of `grp`
select item;
This is effectively what you produce with your into clause, but with different variable names.
var o =
from ord in sampleOrders
group ord by ord.Product into orderGroup
from item in orderGroup
select item; // item is an order, not a group of orders
orderGroups is conceptually a "list of lists"
No, it's not. It's conceptually a single group, not a group of groups. When you say:
from grp in orderGroups
grp now represents one item in the group (in this case, an order), not one group, as you're iterating all of the items in one group.
If you selected out the group, then the entire query would represent a list of groups (or a list of lists).
var listOfGroups = from order in sampleOrders
group order by order.Product into orderGroup
select orderGroup;
Looking at Microsoft's documenation for the group caluse.
The group clause returns a sequence of IGrouping objects that contain zero or more items that match the key value for the group
so if you wanted a group returned your code might look like this:
var o = (
from ord in sampleOrders
group ord by ord.Product // Should return a group.
Since you're using the into the keyword, you're going to get an order object because you used the' select keyword .

Group a collection and take first 5 from each group

I have a collection. I need to group the collection by "A" property. And I have to sort each group by"B" property. Then select first 5 from each group.
Can anyone suggest a LINQ query for this?
The way I tried is not working.
(from item in Recipes
orderby item.Rating descending
group item by item.MainCategory).Take(5)
The query should return IEnumerable<IGrouping<string, myrecipetype>>
You are taking first five groups. Instead you need to select first five items from each group:
from item in Recipes
orderby item.Rating descending
group item by item.MainCategory into g
select g.Take(5)
UPDATE:
from item in Recipes
orderby item.Rating descending
group item by item.MainCategory into g
select g.Take(5).GroupBy(item => item.MainCategory).First()
Edit: In your case, with sorting added (after the OP was updated):
Recipes.GroupBy(recipy => recipy.MainCategory)
.Select(recipyGroup => recipyGroup.OrderBy(recipy => recipy.Rating)
.Take(5))

Return full results linq grouping query

I'm trying to group and still retrieve all the data in the table. I'm still pretty new to Linq and can't seem to tell what I'm doing wrong. I not only want to group the results but I still want to retrieve all the columns in the table. Is this possible?
(from r in db.Form
orderby r.CreatedDate descending
group r by r.Record into myGroup
where myGroup.Count() > 0
where (r.CreatedDate > lastmonth)
where r.Name == "Test Name"
select new { r, myGroup.Key, Count = myGroup.Count() }
)
Some how "r" loses its context or since I grouped "r" it has been replaced. Not sure.
You need to split your task into two steps to accomplish what you want.
Group data
var groupedData = db.Form.Where(item=>item.CreatedDate > lastMonth && item.Name == "Test Name")
.OrderByDescending(item=>item.item.CreatedDate)
.GroupBy(item=>item.Record)
.Select(group => new {Groups = group, Key = group.Key, Count = group.Count()})
.Where(item => item.Groups.Any());
From the grouped data select Form elements
var formElements = groupedData.SelectMany(g => g.Groups).ToList();
Since you're filtering on individual records, you should filter them before grouping rather than after:
(
from r in db.Form
where r.CreatedDate > lastMonth)
where r.Name == "Test Name"
orderby r.CreatedDate descending
group r by r.Record into myGroup
where myGroup.Count() > 0
select new { Groups = myGroup, myGroup.Key, Count = myGroup.Count() }
)
// Groups is a collection of db.Form instances

Linq getting a list from another list

I have two collections: one is Items And Another is ActiveItems
The only intersection between these two collection is Name
I want a list with Linq from Items where the Items names are in the ActiveItems with that name
I wrote this code is there a better idea:
Items.Where(i => ActiveItems.Count(v=> v.Name==i.Name) > 0)
I would probably create a set of the names from ActiveItems and then use that:
var activeNames = new HashSet<string>(activeItems.Select(x => x.Name));
var itemsWithActiveNames = items.Where(x => activeNames.Contains(x.Name))
.ToList();
Another option is to use a join, e.g. with a query expression:
var query = from activeItem in activeItems
join item in items on activeItem.Name equals item.Name
select item;
Note that this will give duplicate item values if there are multiple ActiveItem values with the same name. Another alternative join, which doesn't have this problem but is a bit clumsier:
var query = from item in items
join activeItem in activeItems
on item.Name equals activeItem.Name
into g
where g.Any()
select item;
Note that all of these will avoid the O(N * M) check for names - they'll all use hash tables behind the scenes, to give an O(N + M) complexity.
Items.where(i => ActiveItems.Any(a => i.Name == a.Name))
var results = from i1 in collection1.Items
join i2 in collection2.ActiveItems on i1.Name equals i2.Name
select i2.Name;
Using a join:
from item in Items
join active in ActiveItems on item.Name equals active.Name
select item

Categories