LINQ: combining join and group by - c#

I have a query that combines a join and a group, but I have a problem. The query is like:
var result = from p in Products
join bp in BaseProducts on p.BaseProductId equals bp.Id
group p by p.SomeId into pg
select new ProductPriceMinMax {
SomeId = pg.FirstOrDefault().SomeId,
CountryCode = pg.FirstOrDefault().CountryCode,
MinPrice = pg.Min(m => m.Price),
MaxPrice = pg.Max(m => m.Price),
BaseProductName = bp.Name <------ can't use bp.
};
As you see, it joins the Products table with the BaseProducts table, and groups on an id of the Product table. But in the resulting ProductPriceMinMax, I also need a property of the BaseProducts table: bp.Name, but it doesn't know bp.
Any idea what I'm doing wrong?

Once you've done this
group p by p.SomeId into pg
you no longer have access to the range variables used in the initial from. That is, you can no longer talk about p or bp, you can only talk about pg.
Now, pg is a group and so contains more than one product. All the products in a given pg group have the same SomeId (since that's what you grouped by), but I don't know if that means they all have the same BaseProductId.
To get a base product name, you have to pick a particular product in the pg group (As you are doing with SomeId and CountryCode), and then join to BaseProducts.
var result = from p in Products
group p by p.SomeId into pg
// join *after* group
join bp in BaseProducts on pg.FirstOrDefault().BaseProductId equals bp.Id
select new ProductPriceMinMax {
SomeId = pg.FirstOrDefault().SomeId,
CountryCode = pg.FirstOrDefault().CountryCode,
MinPrice = pg.Min(m => m.Price),
MaxPrice = pg.Max(m => m.Price),
BaseProductName = bp.Name // now there is a 'bp' in scope
};
That said, this looks pretty unusual and I think you should step back and consider what you are actually trying to retrieve.

We did it like this:
from p in Products
join bp in BaseProducts on p.BaseProductId equals bp.Id
where !string.IsNullOrEmpty(p.SomeId) && p.LastPublished >= lastDate
group new { p, bp } by new { p.SomeId } into pg
let firstproductgroup = pg.FirstOrDefault()
let product = firstproductgroup.p
let baseproduct = firstproductgroup.bp
let minprice = pg.Min(m => m.p.Price)
let maxprice = pg.Max(m => m.p.Price)
select new ProductPriceMinMax
{
SomeId = product.SomeId,
BaseProductName = baseproduct.Name,
CountryCode = product.CountryCode,
MinPrice = minprice,
MaxPrice = maxprice
};
EDIT: we used the version of AakashM, because it has better performance

I met the same problem as you.
I push two tables result into t1 object and group t1.
from p in Products
join bp in BaseProducts on p.BaseProductId equals bp.Id
select new {
p,
bp
} into t1
group t1 by t1.p.SomeId into g
select new ProductPriceMinMax {
SomeId = g.FirstOrDefault().p.SomeId,
CountryCode = g.FirstOrDefault().p.CountryCode,
MinPrice = g.Min(m => m.bp.Price),
MaxPrice = g.Max(m => m.bp.Price),
BaseProductName = g.FirstOrDefault().bp.Name
};

Related

Linq EF group by to get latest entries into list of objects

I am trying to move from simple SQL to EF.
But there are some complex queries(joins) that it seems to hard to generate the linq for.
At first I tried to use sqltolinq tool to generate the linq but it gives error as some of the things are not supported in the query.
here is the linq:
var entryPoint = (from ep in dbContext.tbl_EntryPoint
join e in dbContext.tbl_Entry on ep.EID equals e.EID
join t in dbContext.tbl_Title on e.TID equals t.TID
where e.OwnerID == user.UID
select new {
UID = e.OwnerID,
TID = e.TID,
Title = t.Title,
EID = e.EID
});
The table entry has many entries that I would like to group and get the latest for each group. But then I would need to select into a view model object which will be bind to gridview.
I dont know where I can implement the logic to group by and get the latest from each and be able to get values from join table into viewModel object.
somewhere I need to add
group entry by new
{
entry.aID,
entry.bCode,
entry.Date,
entry.FCode
}
into groups
select groups.OrderByDescending(p => p.ID).First()
in the above linq to retrieve latest from each group.
You can insert group by right after the joins:
var query =
from ep in dbContext.tbl_EntryPoint
join e in dbContext.tbl_Entry on ep.EID equals e.EID
join t in dbContext.tbl_Title on e.TID equals t.TID
where e.OwnerID == user.UID
group new { ep, e, t } by new { e.aID, e.bCode, e.Date, e.FCode } into g
let r = g.OrderByDescending(x => x.e.ID).FirstOrDefault()
select new
{
UID = r.e.OwnerID,
TID = r.e.TID,
Title = r.t.Title,
EID = r.e.EID
};
The trick here is to include what you need after the grouping between group and by.
However, the above will be translated to CROSS APPLY with all joins included twice. If the grouping key contains fields from just one table, it could be better to perform the grouping/selecting the last grouping element first, and then join the result with the rest:
var query =
from e in (from e in dbContext.tbl_Entry
where e.OwnerID == user.UID
group e by new { e.aID, e.bCode, e.Date, e.FCode } into g
select g.OrderByDescending(e => e.ID).FirstOrDefault())
join ep in dbContext.tbl_EntryPoint on e.EID equals ep.EID
join t in dbContext.tbl_Title on e.TID equals t.TID
select new
{
UID = e.OwnerID,
TID = e.TID,
Title = t.Title,
EID = e.EID
};

SQL query into LINQ in asp.net mvc

Please anyone can help me to write this sql query into Linq. i have tried..
this is my sql query
select o.OrderID,o.Nature,o.Date,od.TotalPrice,os.OrderStatus,lo.FirstName,lo.EmailAddress,lo.PhoneNumber
from [dbo].[Order] o
inner join [dbo].[tbl_OrderDetails] od on od.OrderID = o.OrderID
inner join [dbo].[tbl_OrderHistory] oh on oh.OrderID = o.OrderID
inner join [dbo].[tbl_Login] lo on o.UserID = lo.UserID
inner join dbo.tbl_OrderStatus os on oh.OrderStatusID= os.OrderStatusID
group by o.OrderID,o.Nature,od.TotalPrice,o.Date,os.OrderStatus,lo.FirstName,lo.EmailAddress,lo.PhoneNumber
and this is my try
public override orderDetailModel orderDetails(int id)
{
var results = from o in obj.Orders
join od in obj.tbl_OrderDetails on o.OrderID equals od.OrderID
join oh in obj.tbl_OrderHistory on o.OrderID equals oh.OrderID
join l in obj.tbl_Login on o.UserID equals l.UserID
join os in obj.tbl_OrderStatus on oh.OrderStatusID equals os.OrderStatusID
where (od.OrderID == id)
group o by new { o.Nature, o.OrderID } into
select new orderDetailModel
{
OrderID = o.OrderID,
OrderStatus = os.OrderStatus,
Date = o.Date,
DeliveryNature = o.Nature,
EmailAddress = l.EmailAddress,
FirstName = l.FirstName,
PhoneNumber = l.PhoneNumber,
TotalPrice = od.TotalPrice
};
//group o by new {o.OrderID};
orderDetailModel data = (orderDetailModel)results.FirstOrDefault();
return data;
}
but this is wrong query its not working fine please help me
You need to correct the group by clause, like you have in the SQL query like this:-
group new { o, l } by new { o.OrderID,o.Nature,od.TotalPrice,o.Date,os.OrderStatus,
l.FirstName, l.EmailAddress,l.PhoneNumber } into g
select new orderDetailModel
{
OrderID = g.Key.OrderID,
OrderStatus = g.Key.OrderStatus,
Date = g.Key.Date,
..and so on
};
Since you need the grouping on two tables Order & tbl_Login you will have to first project them as anonymous type group new { o, l } then specify all the groupings and finally while projecting use Key to get the respective items.
I guess that actually, also the SQL query is not correct.
I would simply use a SELECT DISTINCT ... instead of Grouping all the columns.
Anyway, first thing to do:
Check if databases is designed correctly. As far as i can see, if you're joining the table with their Ids, i don't understand why you need to group all the data. If you have duplicates, maybe the error is in the Database design.
If you can't change your Database, or you are happy with it, then use the following LINQ approach:
var distinctKeys = allOrderDetails.Select(o => new { o.OrderID, o.Nature, o.TotalPrice, o.Date,o.OrderStatus,o.FirstName, o.EmailAddress,o.PhoneNumber }).Distinct();
var joined = from e in allOrderDetails
join d in distinctKeys
on new { o.OrderID, o.Nature,o.TotalPrice, o.Date,o.OrderStatus, o.FirstName, o.EmailAddress, o.PhoneNumber } equals d select e;
joined.ToList(); // gives you the distinct/grouped list

Linq group by, orderbydescending, and count

So I'm trying to do a linq statement to group two db tables and select the top 25 based on how many reviews each category has. So my sql statement is
SELECT TOP 25 BusinessCategories.Category, COUNT(*) as count
FROM Reviews
JOIN BusinessCategories
ON BusinessCategories.BusinessID=Reviews.BusinessID
GROUP BY BusinessCategories.Category
ORDER BY count desc
Which works perfectly. So now to try to do this in my web api I'm having troubles. This is what I have:
var top = (from review in Db.Reviews
from category in Db.BusinessCategories
where review.BusinessID == category.BusinessID
group review by category into reviewgroups
select new TopBusinessCategory
{
BusinessCategory = reviewgroups.Key,
Count = reviewgroups.Count()
}
).OrderByDescending(x => x.Count).Distinct().Take(25);
This gives me some of the same results, but it looks like when I call the api in the browser all the counts are the same...so I'm doing something wrong.
Try this may be it works for you
var top = (from review in Db.Reviews
join category in Db.BusinessCategories
on review.BusinessID equals category.BusinessID
group review by category into reviewgroups
select new TopBusinessCategory
{
BusinessCategory = reviewgroups.Key,
Count = reviewgroups.Key.categoryId.Count() //CategoryId should be any
//property of Category or you
//can use any property of category
}).OrderByDescending(x => x.Count).Distinct().Take(25);
Solve the problem by using this
[HttpGet]
[Queryable()]
public IQueryable<TopBusinessCategory> GetTopBusinessCategories()
{
var top = (from p in Db.BusinessCategories
join c in Db.Reviews on p.BusinessID equals c.BusinessID into j1
from j2 in j1.DefaultIfEmpty()
group j2 by p.Category into grouped
select new TopBusinessCategory
{
BusinessCategory = grouped.Key,
Count = grouped.Count(t => t.BusinessID != null)
}).OrderByDescending(x => x.Count).Take(25);
return top.AsQueryable();
}

How do I select multiple items from a database using LINQ

So I am selecting from a person table and I need to select the group ids of the groups that the person is in. How can I do this.
So far, I have:
var p = (from p in Context.Person
join g in Context.Group
on p.PersonId equals g.PersonId
select new
{
Name = p.Name,
Age = p.Age,
groupIds = ?????
}
So in the group table it will be a primary key of GroupId and PersonId so I need to select all the group ids. How can this be done?
You want a GroupJoin rather than a Join. The difference is that rather than having all of the related items flattened into a list of pairs, it groups all of the joined items into a sequence:
var query = from p in Context.Person
join g in Context.Group
on p.PersonId equals g.PersonId into groups
select new
{
Name = p.Name,
Age = p.Age,
groupIds = groups.Select(g => g.GroupId),
};
Using query syntax the use of the into keyword in conjunction with a join will result in a GroupJoin instead of a Join.
I coded on SO's editor. If I understand right you want Person's groups.
Otherwise correct me please.
var p = from p in Context.Person
select new
{
Name = p.Name,
Age = p.Age,
groups = from g in Context.Group
where p.PersonId == g.PersonId
select g.GroupId
};

How to select CompanyName and number of its products?

I want to write a Linq query that will return, columns with CompanyName of Supplier and number of all products of this company. Can you help me?
So far I got this:
var company = from pro in db.Products
join sup in db.Suppliers
on pro.SupplierID equals sup.SupplierID
group pro by pro.SupplierID
into g
select new { Name = g.Key, COUNT = g.Count() };
But this returns SupplierID not CompanyName. Database is Northwnd.
Use group join (i.e. join...into) to join suppliers with products and get all products of supplier in group:
from s in db.Suppliers
join p in db.Products
on s.SupplierID equals p.SupplierID into g
select new {
s.CompanyName,
ProductsCount = g.Count()
}
The following compiles and runs with Linq-for-objects. I can't vouch for whether Linq-to-SQL will cope.
var company = from sup in db.Suppliers
select new
{
Name = sup.CompanyName,
COUNT = db.Products
.Where(pro => pro.SupplierID == sup.SupplierID)
.Count()
};

Categories