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 .
Related
What I have now:
var batch_pymnts2 = (from a in ctx.WarehouseStatementBatchPayments
join b in ctx.WarehouseStatementBatches on a.WarehouseStatementBatchID equals b.ID
join c in ctx.WarehousePaymentInvoices on a.ID equals c.WarehouseStatementBatchPaymentID
where b.ID == batchID
select new
{
PaymentId = a.ID,
PaymentNet = a.Net,
PaymentType = a.Type
})
.GroupBy(d => d.PaymentId).Where(x => x.Count() == 1);
I need to query these results like so:
var test = (from a in batch_pymnts2 where a.PaymentNet > 100 select a).ToList();
However, I cant see the fields of the (anonymous) type that the first statement uses to project the results into.
Will I need to use a defined type in the query for the projection? Is there a way to do it with anonymous types?
[update]
I managed to change the source query a bit, moving the group by inside and before the group by. This lets the fields of the anonymous type being projected, be "exposed" in further statements.
var count2 = (from a in WarehouseStatementBatchPayments
join b in WarehouseStatementBatches on a.WarehouseStatementBatchID equals b.ID
join c in WarehousePaymentInvoices on a.ID equals c.WarehouseStatementBatchPaymentID
group a by a.ID into grp
from d in grp
where d.WarehouseStatementBatchID == batchID && grp.Count() == 1
select new { PaymentId = d.ID, PaymentNet = d.Net, PaymentType = d.Type }).ToList();
batch_pymnts2 is a sequence of group objects. In effect, it is a collection of collections of your anonymous type. Each item in batch_pymnts2 has this:
group.Key; /* a PaymentId value */
((IEnumerable)group); /* the anon type items grouped together in this group */
Those group objects implement the IGrouping interface. Their Key property is the PaymentId values that define the groups. If you enumerate the groups (they implement IEnumerable<T>), you'll get the anonymous objects that you grouped by PaymentId:
var test = batch_pymnts2.SelectMany(g => g.Where(anon => anon.PaymentNet > 100));
test is now an enumeration of your anonymous type, because we have now enumerated a subset of the anon items from each of the groups, and (in effect) unioned all those little enumerations of anon back into one big one.
If you want to select groups which have at least one anonymous thingy with PaymentNet > 100, try this:
// Groups which have at least one PaymentNet > 100
var t2 = batch_pymnts2.Where(g => g.Any(anon => anon.PaymentNet > 100));
// PaymentIds of the groups which have at least one PaymentNet > 100
var ids = t2.Select(g => g.Key);
// PaymentIds that appear only once
var singles = t2.Where(g => g.Count == 1).Select(g => g.Key);
I don't know why you're grouping them, or what your PaymentNet > 100 query is meant to accomplish, so I'm not sure exactly how to write the query you want. But your starting point is that you're querying a sequence of group objects which contain enumerations of your anonymous type -- not a sequence of that type itself.
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.
I'm using this linq query to get a list of doubles. The list looks correct, but now I need to total the doubles in the list.
var total = from sauId in schools
join item in entities.AmountTable
on sauId equals item.sauId
select item.amount;
Use Sum() on the result:
var total = (from sauId in schools
join item in entities.AmountTable
on sauId equals item.sauId
select item.amount).Sum();
If item.amount is a double, then total is also a double.
For anyone looking for fluent syntax, I believe this would be the way to do it:
var total = schools
.Join(
entities.AmountTable,
s => s.sauId,
at => at.sauId,
(s, at) => at.amount)
.Sum();
Try to group data by its sauId:
var qry = from item in entities.AmountTable
group item by item.sauId into grp
select new {
sauId = grp.Key,
amuont = grp.Sum(c=>c.amount)
};
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
How to use Linq to select and group complex child object from a parents list.
I have an OrderList each of order object has a OrderProductVariantList(OrderLineList), and each of OrderProductVariant object has ProductVariant, and then the ProductVariant object will have a Product object which contains product information.
My goal is to select and group the most popular products from the order list.
Can anyone help me with this?
Many thanks.
Your description is hard to follow, but I think you just want to get out the Products and rank them by the number of times they occur. SelectMany will be helpful for this.
var query = orderList.SelectMany( o => o.OrderLineList )
// results in IEnumerable<OrderProductVariant>
.Select( opv => opv.ProductVariant )
.Select( pv => p.Product )
.GroupBy( p => p )
.Select( g => new {
Product = g.Key,
Count = g.Count()
});
A query is not a result. To view the result you can iterate over the query object:
foreach (var result in query) {
Console.WriteLine(result);
}
As to why query wasn't available in the watch window, I can only imagine that it either wasn't in scope yet, or it had already gone out of scope. Try putting a breakpoint on the line immediately after the line you posted where you assign to query.