how to use group by in a linq query? - c#

I have a list of products with productname, productprice & category. I want to write a linq query to group all the products according to category. I have tried the following:
var p = from s in productlist
group s by s.Category into g
select new { Category = g.Key, Products = g};
With this query, it's showing a table with two column as category & product. Category column has two categories as expected but under products column, there is no data. I would love to have all the product list separated by the category.

OK..Need a bit more help on this, now when I run the code, its just
showing a table with categories column showing two categories as
expected, but the product column not showing any data.
You need to select the products from the group:
Products = g.Select(p => p).ToList()
Have a look at following with some additional properties.
var categories = from s in productlist
group s by s.category into g
select new {
Category = g.Key,
Products = g.ToList(),
ProductCount = g.Count(),
AveragePrice = g.Average(p => p.productprice)
};

Related

C# Linq Group, Count & Sum

I am looking for help with a Linq query I am struggling with. I am trying to build a page that shows the current stock level of all products based on the stock that has been added and the items that have been sold. These are my tables:
Product
Id
Code
Name
Stock
Id
ProductId
Quantity
Sold
Id
ProductCode
I would like the page to show the following columns:
Stock added - sum of the quantity field of all related rows from the stock table.
Items sold - count of all related rows from the Sold table
Stock remaining = stock added - items sold.
This is my attempt at the Linq query so far:
return await (from product in _context.Products
join stock in _context.Stocks on product.Id equals stock.ProductId
join so in _context.SaleProducts on product.Code equals so.Code into sold
from subSold in sold.DefaultIfEmpty()
group new { product, stock, sold } by product into g
select new StockViewModel
{
ProductId = g.Key.Id,
ProductCode = g.Key.Code,
ProductName = g.Key.Name,
StockAdded = g.Sum(x => x.stock.Quantity),
ItemsSold = g.Select(x => x.sold).Count()
})
.ToArrayAsync();
The items sold is showing the same value for each row and it is only showing products that have had stock added.
I would like to show all products whether stock has been added or not. I would also like to show all products whether any have been sold or not
Thanks to #T N for the heads up regarding Linq subqueries, this is the query I was looking for:
var stockRecords = await (from p in _context.Products
select new StockViewModel
{
ProductId = p.Id,
ProductName = p.Name,
StockAdded = (
(from s in _context.Stocks
where s.ProductId == p.Id
select s.Quantity).Sum()
),
ItemsSold = (
(from s in _context.SaleProducts
where s.Code == p.Code
select s).Count()
)
})
.ToArrayAsync();

How do I convert this statement into linq?

How can I use Sum() in Linq?
select sum(op.Quantity),
sum(p.UnitPrice),
sum(p.Discount),
o.CreateAt
from OrderProduct op join
[Order] o on o.ID = op.OrderID join
Product p on p.ID = op.ProductID
group by o.CreateAt
Instead of just writing a SQL statement, it would be more helpful to define your goal. You have some tables and you want to extract information from it. Which information do you want? Now I have to guess your structure.
It seems you have Orders and Products and OrderProducts.
Every Order has zero or more OrderProducts; every OrderProduct belongs to exactly one Order. Every Order also has a CreateAt property.
Every Product has zero or more OderProducts; every OrderProduct belongs to exactly one Product. Every Product also has a UnitPrice and a Discount.
Finally every OrderProduct has a property Quantity
You didn't mention whether you are using entity framework or not. But even if not, you have access to the three tables using IQueryables. In Entity Framework this is done via the DbSets:
IQueryable<Product> products = ...
IQueryable<Order> orders = ...
IQueryable<OrderProduct> orderProducts = ...
The query in baby steps:
var joinedItems = from product in Products
join orderProduct in orderProducts on oderProduct.ProductId == product.Id
join order in orders on order.Id == orderProduct,OrderId
select new
{
Quantity = orderProduct.Quantity,
UnitPrice = product.UnitPrice,
Discount = product.Discount,
CreateAt = order.CreateAt,
};
Multiple joins is the only time I use query syntax instead of method syntax.
Click here to see the method syntax for multiple joins
Because every product has only one UnitPrice, I assume you don't want the sum of all UnitPrice of one Product, but you want the sum of all Unitprices of items created on CreateAt, and similarly the sum of all Discounts and Quantities.
So we first group all joinedItems into joinedItems with the same CreateAt:
var itemsGroupedByCreateAt = joinedItems
.GroupBy(joinedItem => joinedItem.CreateAt);
In words: take all joinedItems and group them into groups where all joinedItems in the group have the same value for CreateAt.
The result is a sequence of Groups. Every group has joinedItems. All joinedItems in the group have the same value for CreateAt. this common value is the Key of the group.
So all you have to do is for every group to sum the UnitPrices of all joinedItems in the group. Do the same for the Quantity and Discount. Note that every group will create one sumResult:
var sumResults = itemsGroupedByCreateAt
.Select(group => new
{
TotalQuantity = group
.Select(groupElement => groupElement.Quantity)
.Sum(),
TotalUnitPrice = group
.Select(groupElement => groupElement.UnitPrice)
.Sum(),
Totaldiscount = group
.Select(groupElement => groupElement.UnitPrice)
.Sum(),
// only if you need it in your result:
CreateAt = group.Key,
});
In words: take all your groups of joinedItems. From every group, take the joinedItems in the group. From these joinedItems take only the Quantities, and Sum them. Put the result in totalQuantity.
Do the same for the UnitPrices and the Discounts
Of course you can do this all in one big linq statement. Not sure whether this would improve readability and maintainability.

Selecting list of object type after grouping in linq

I have a table such as the following:
ProductId, CategoryId
123, Category1
123, Category2
123, Category1
My parameter is productId and I need to return a list of type Category based on distinct categories for the given productId in the above table.
You could leverage the .Distinct() function from LINQ to select all the distinct categories belonging to the specified productId.
var pList = (from p in context.Products
where p.ProductId == productId
select p.Category).Distinct().ToList();
var list = context.Products
.Where(p=>p.ProductId==productId)
.Distinct();

LINQ: Group by aggregate but still get information from the most recent row?

Let's say I have a table that holds shipping history. I'd like to write a query that counts the amount of shipments per user and gets the shipping name from the most recent entry in the table for that user.
Table structure for simplicity:
ShipmentID
MemberID
ShippingName
ShippingDate
How do I write a LINQ C# query to do this?
It sounds like might want something like:
var query = from shipment in context.ShippingHistory
group shipment by shipment.MemberID into g
select new { Count = g.Count(),
MemberID = g.Key,
MostRecentName = g.OrderByDescending(x => x.ShipmentDate)
.First()
.ShipmentName };
Not really a LINQ answer, but personally, I'd be dropping to SQL for that, to make sure it isn't doing any N+1 etc; for example:
select s1.MemberID, COUNT(1) as [Count],
(select top 1 ShippingName from Shipping s2 where s2.MemberID = s1.MemberID
order by s2.ShippingDate desc) as [LastShippingName]
from Shipping s1
group by s1.MemberID
You can probably do LINQ something like (untested):
var qry = from row in data
group row by row.MemberId into grp
select new {
MemberId = grp.Key,
Count = grp.Count(),
LastShippingName =
grp.OrderByDescending(x => x.ShippingDate).First().ShippingName
};

How to use linq-to-sql group by to list duplicated records?

Suppose I have two tables: Category and Product. I want use linq-to-sql to select all categories (with products) that have duplicated products.
My query goes like this:
from p in db.Products
group p by p.CategoryId into c
select new
{
categoryId = c.Key,
products = from PbyC in c
group PbyC by PbyC.Name into dupl
where dupl.Count() > 1
select dupl;
}
It works but LINQPad lists empty Categories (without any duplicated Product). How to modify the query?
It would be great to have Category name display somehow instead of Id.
EDIT: I have relationship between tables.
db.Products.GroupBy(p => new { p.Name, p.CategoryId })
.Select(g => new { g.Key.CategoryId, Count = g.Count })
.Where(ng => ng.Count > 1)
.Select(ng => ng.CategoryId);
That'll select the ids of the categories with more than one Product of the same name, and is assuming you don't have any relationships set up between the objects so you'll need to join the results to the Categories table to get detailed info.
If you do have a relationship set up then you can always change the CategoryId to Category (or whatever the related object is named) and just select Category.Name in the last Select.

Categories