Need help refactoring a LINQ statement with a Group By clause? - c#

The Linq-To-SQL that needs refactoring is found below:
var query = from p in Cache.Model.Products
join sc in Cache.Model.ShishaCombinations on p.ProductID equals sc.ProductID
where sc.NumberOfMixes == mixes
select new { p.ProductID, p.Name };
with the following one-liner:
group by p.ProductID
I know that group by omits any need for the select clause so both can live together in the same LINQ-To-SQL statement so can someone help me refactor the above please?
To help me remove any repeating data in my results. <-- This is the entire point in this question. A product is repeated a number of times based on the number of Flavours that are used by the product. So as a result in the 'ShishaCombinations' table one ProductID may be repeated many times one for each flavour that it uses. I would like to group the results returned from the query above or call distinct on it as I don't want add the product 'n' times to my GUI because it appears 'n' number of times in my results. Hopefully that will clear up any confusion of what I am trying to do.

The code below is all what I needed:
var query1 = (from p in Cache.Model.Products
join sc in Cache.Model.ShishaCombinations on p.ProductID equals sc.ProductID
where sc.NumberOfMixes == mixes
select new { p.ProductID, p.Name }).Distinct();
Ufuk, thanks for answering mate. I knew my answer was a simple one lol : )

You can use into keyword after select clause. You cannot just group by because you are projecting an anonymous type.
var query = from p in Cache.Model.Products
join sc in Cache.Model.ShishaCombinations on p.ProductID equals sc.ProductID
where sc.NumberOfMixes == mixes
select new { p.ProductID, p.Name } into productInfo
group productInfo by productInfo.productId;

Related

LINQ Left Join SQL equivalent via DefaultIfEmpty fails to return results

I have had an extensive look around on SE, tried all of the suggestions, checked out MSDN how to perform Left Join equivalent in LINQ to SQL and I have constructed my LINQ query according to MSDN example.
However, the result is not what SQL would return and I am completely lost as to where am I going wrong.
Here is some details:
I have two tables, Customers and Reports. A customer can submit many reports or none. In the current state I have many more reports than customers.
LINQ code:
var query = {from c in customers
join r in reports on c.Id equals r.Id into temp
from items in temp.DefaultIfEmpty()
select new {
c.Id,
LastReportDate = items?.DateCreated ?? DateTime.MinValue
}).ToList();
SQL code:
SELECT [Customers].[Id], R.LastReport AS LastReportDate FROM [Customers]
LEFT JOIN (
SELECT Reports.Id, MAX( [Reports].[Created] ) AS LastReport
FROM Reports GROUP BY Reports.Id
) AS r ON [Customers].[Id] = r.[Id]
The problem is that the query returns number of elements equal to number of reports. However, what I want is to get a list with all customers and for those who have submitted a report I wish to display the date of the most recent report, for those who have not submitted anything, I am happy to leave it NULL or DateTime.MinValue
Any help would be greatly appreciated. I guess I am missing a group by call somewhere in my LINQ code...
Im thinking probably something like this:
var query =
from c in customers
join r in reports on c.Id equals r.Id into g
select new
{
c.Id,
LastReportDate = g.Max(x => (DateTime?)x.Created)
};
you are now joining on join r in reports on c.Id equals r.Id into temp
this looks like: join on a customer.Id on Reports.Id, since you say there are 1 to many relation/rapport. I think your table will have a Reports.CustomerId. Is this correct?
So your query should something look like:
var results = customer.Where(c => c.Reports.Any())
.SelectMany(c => {c, c.Reports.Max(r => r.Created)})
.ToList();
the select comes out of my head, so i am probably missing something ;)
Have you tried LinqPad ? There you can type your linq-queries, and directly see your sql code and results. Works like a charm!

How to turn a SQL Query Into Linq? "People who viewed this product also viewed this product."

I'm not really that good at linq so im having trouple trying to do groups and order by.
The goal of my query is, When someone is on a product page i want to show a list of products that people also viewed. I'm pretty sure this query is right but i have no idea how to to transfer to linq and get it to work in my C# project.
The Sql Query im trying to change to linq is
select pv.ProductId,p.Name, count(*) as 'ProductCount'
from Products p
join ProductVieweds pv
on p.ProductID = pv.ProductId
where UserId in (
select UserId
from ProductVieweds pv
where ProductID = 1 --The 1 in this example is the product the person is viewing.
)
and p.ProductID != 1 --change this
group by pv.ProductId,p.Name
order by 'ProductCount' desc
What I've been able to do so far.
var sub = (
from p in db.ProductsViewed
where p.ProductId == product.ProductID
select p.UserId).ToList();
var products = (from p in db.Products
join pv in db.ProductsViewed on c.ProductID equals p.ProductId
where p.ProductID != currentProduct.ProductID
where sub.Contains(pv.UserId)
select p).GroupBy(c => c.ProductID).ToList();
PeopleAlsoViewedModel model = new PeopleAlsoViewedModel
{
Products = products,
};
The Model
public class PeopleAlsoViewedModel
{
public IEnumerable<Product> Products { get; set; }
}
So i want to be able to group by Products and order by the count but i dont know how to do this and still get a IEnumerable< Product >. Please any help will be awesome, and if your gonna down vote at least tell me why.
Ok so I figured how to solve my problem and thought i should post it incase anyone reads this later.
The Query will look like this in C#
var products = (from c in db.Products
join p in db.ProductsViewed on c.ProductID equals p.ProductId
where c.ProductID != CurrentProduct.ProductID
where sub.Contains(p.UserId)
group c by c.ProductID into productGrouped
orderby productGrouped.Key
select productGrouped).SelectMany(group => group.Distinct()).ToList();
The SelectMany() turns the List< IGrouping< key, element>> into List< Element> I put distinct because multiple people can view the same product otherwise it returns duplicates.

Concatenating a LINQ (To SQL) Query

I am building a LINQ query, which will have comparisons attached to the 'where' section (the number of these comparisons depends on the user's selections).
In the code-behind, I want something like this:
var builtQuery =
from q in dc.Leads
join sr in dc.SalesReps on q.SalesRepID equals sr.SalesRepID
join co in dc.Companies on q.CompanyID equals co.CompanyID
join or in dc.Origins on q.OriginID equals or.OriginID
join pr in dc.Products on q.ProductID equals pr.ProductID
where
Here, in between the 'from' and 'select' parts, I will add a number of comparisons (depending on the user's selection of checkboxes).
And Finally:
select new { q.Ref, sr.Rep, q.Deposit, q.Sale, q.Title, q.Names, q.Surname, q.HomePhone, q.WorkPhone, q.Mobile, q.Address, q.Suburb, q.County, q.Postcode, co.CompanyName, or.OriginName, pr.ProductName, q.Telemarket, q.Entered };
In PHP (using MySQL) I could simply concatenate a number of strings, which make up the query. But, in c#/LINQ To SQL, the query is not a string and so I have no idea how to do this...There were a couple similar questions on SO, but they're not quite the same thing.
Any ideas??
Thanks!
I would do it in the following way
var intermediateQuery=
from q in dc.Leads
join sr in dc.SalesReps on q.SalesRepID equals sr.SalesRepID
join co in dc.Companies on q.CompanyID equals co.CompanyID
join or in dc.Origins on q.OriginID equals or.OriginID
join pr in dc.Products on q.ProductID equals pr.ProductID
select new { q.Ref, sr.Rep, q.Deposit, q.Sale, q.Title, q.Names, q.Surname, q.HomePhone, q.WorkPhone, q.Mobile, q.Address, q.Suburb, q.County, q.Postcode, co.CompanyName, or.OriginName, pr.ProductName, q.Telemarket, q.Entered };
and then add some filters considering user input
if(SomeUserProductFilter)
{
var result = intermediateQuery.Where(p=>p.ProductName = 'UserProductName');
}
Do not be afraid that this approach will retrieve all data and than filters it in memory. LINQ sends query to database only when you call ToList(), ToArray() or use result in foreach loop

LINQ Left Outer Join combined with Let

In a previous question I asked how I would get a Customers first Order, it was answered thus :
var minOrders = from customer in DataSet.Customers
let order = (from o in DataSet.Orders where o.CustomerId == customer.CustomerId
order by o.OrderTimestamp
select o).first()
select new {
customer.Name,
order.OrderAmount
});
This is great, but how do I include a Left Outer Join onto the above? That is, return all Customers even if they have no orders, so something like :
var minOrders = from customer in DataSet.Customers LEFT OUTER JOIN
let order = (from o in DataSet.Orders where o.CustomerId == customer.CustomerId
order by o.OrderTimestamp
select o).first()
select new {
customer.Name,
order.OrderAmount
});
I know, in hindsight I should of asked this at the same time..
Thanks, Joe
Firstly, using let to do the join like this isn't ideal in the first place. There's a join clause in LINQ for a reason :)
Left outer joins aren't specifically supported in LINQ, but you can fake them like this:
var minOrders = from customer in DataSet.Customers
join order in DataSet.Orders.OrderBy(o => o.OrderTimestamp)
on customer.CustomerId equals o.CustomerId
into customerOrders
let order = customerOrders.FirstOrDefault()
select new {
customer.Name,
OrderAmount = order == null ? 0m : order.OrderAmount
};
Usually a left outer join uses from foo in bar.DefaultIfEmpty instead of let foo = bar.FirstOrDefault() but in this case you're only after the first match anyway, hence the different approach.
I'm pretty sure this works logically - whether the SQL translation will work or not is a different matter.

Using DISTINCT on a subquery to remove duplicates in Entity Framework

I have question about use of Distinct with Entity Framework, using Sql 2005. In this example:
practitioners = from p in context.Practitioners
join pn in context.ProviderNetworks on
p.ProviderId equals pn.ProviderId
(notNetworkIds.Contains(pn.Network))
select p;
practitioners = practitioners
.Distinct()
.OrderByDescending(p => p.UpdateDate);
data = practitioners.Skip(PageSize * (pageOffset ?? 0)).Take(PageSize).ToList();
It all works fine, but the use of distinct is very inefficient. Larger result sets incur unacceptable performance. The DISTINCT is killing me. The distinct is only needed because multiple networks can be queried, causing Providers records to be duplicated. In effect I need to ask the DB "only return providers ONCE even if they're in multiple networks". If I could place the DISTINCT on the ProviderNetworks, the query runs much faster.
How can I cause EF to add the DISTINCT only the subquery, not to the entire resultset?
The resulting simplified sql I DON'T want is:
select DISTINCT p.* from Providers
inner join Networks pn on p.ProviderId = pn.ProviderId
where NetworkName in ('abc','def')
IDEAL sql is:
select p.* from Providers
inner join (select DISTINCT ProviderId from Networks
where NetworkName in ('abc','def'))
as pn on p.ProviderId = pn.ProviderId
Thanks
Dave
I dont think you need a Distinct here but a Exists (or Any as it is called in Linq)
Try this:
var q = (from p in context.Practitioners
where context.ProviderNetworks.Any(pn => pn.ProviderId == p.ProviderId && notNetworkIds.Contains(pn.Network))
orderby p.UpdateDate descending
select p).Skip(PageSize * (pageOffset ?? 0)).Take(PageSize).ToList();

Categories