Linq can these sub queries be optimised? - c#

This query takes a group of comments, then counts their upvotes and downvotes in the tblCommentVotes table.
At the moment, it counts these via the select new statement, in the form of a subquery. Would this be more efficient if it was in some sort of group by in the main query? Also if it would, could anyone show me how to do this, as I can't work out how you would do this.
// Get comments
var q = (
from C in db.tblComments
where
C.CategoryID == Category &&
C.IdentifierID == Identifier
join A in db.tblForumAuthors on C.UserID equals A.Author_ID
orderby C.PostDate descending
select new
{
C,
A.Username,
UpVotes = (from V in db.tblCommentVotes where V.CommentID == C.ID && V.UpVote == true select new { V.ID }).Count(),
DownVotes = (from V in db.tblCommentVotes where V.CommentID == C.ID && V.UpVote == false select new { V.ID }).Count()
}
)
.Skip(ToSkip > 0 ? ToSkip : 0)
.Take(ToTake > 0 ? ToTake : int.MaxValue);

What you need to do is to do an left outer join of the db.tblCommentVotes in the query expression, cause probably there might be no commentVotes?
When you have that, you should be able to perform ONE query in order to get your result.
It might look like this:
var q = (
from C in db.tblComments
where
C.CategoryID == Category &&
C.IdentifierID == Identifier
join A in db.tblForumAuthors on C.UserID equals A.Author_ID
// the following two lines are the left outer join thing.
join voteTemp in db.tblCommentVotes on voteTemp.CommentID equals C.ID into voteJoin
from vote in voteJoin.DefaultIfEmpty()
orderby C.PostDate descending
group C by new { Comment = C, Username = A.Username } into g
select new
{
g.Key.Comment,
g.Key.Username,
UpVotes = g.Count(x => x.UpVote),
DownVotes = g.Count(x => !x.UpVote)
}
)
.Skip(ToSkip > 0 ? ToSkip : 0)
.Take(ToTake > 0 ? ToTake : int.MaxValue);
This is untested and might not even compile, but I think it should be something like this.

db.tblComments.Where(c => c.CategoryID == Category && c.IdentifierID == Identifier)
.Join(db.tblForumAuthors, c => c.UserID, a => a.Author_ID,
(c, a) =>
new
{
CommentID = c,
AuthorName = a.UserName,
UpVotes = c.Join(db.tblCommentVotes, c => c.CommentID
v => v.CommentID,
(c, v) => v).Count(v => v.UpVote)
DownVotes = c.Join(db.tblCommentVotes, c => c.CommentID
v => v.CommentID,
(c, v) => v).Count(v => v.DownVote)
});

To optimise it's best first to measure.
Try, using something like LinqPad to view the generated SQL
Then use SQL Server Management Studio to see the query plan for that SQL
or:
Try running the code and seeing what SQL trace tells you is happening
Without the DB, it's quite hard (but fun) to guess whether that Linq will result in a single query or in multiple queries for working out the UpVotes and DownVotes. My guess is that calculating the UpVotes and DownVotes this way could be quite expensive - it may result in 2 additional queries per comment.

http://www.thereforesystems.com/view-query-generate-by-linq-to-sql/
without analyzing whats being output this question is impossible to answer.. however the link provided above should give you the tools necessary to perform this analysis yourself.

Related

Sql Query into Linq to Entity Framework

I'm wondering if it is even possible to write the below sql query as a LINQ to Entity statement. Below is a simplified example of a real world problem that I'm trying to figure out:
Select
c.CustomerID,
c.CustomerName,
(Select count(p.ProductID) from Products p
where p.CustomerID = c.CustomerID and p.Category = 'HomeAppliance') as ApplianceCount,
(Select count(p.ProductID) from Products p
where p.CustomerID = c.CustomerID and p.Category = 'Furnishing') as FurnishingCount
from Customer c
where
c.CustomerMarket = 'GB'
order by c.CustomerID desc;
Any suggestions would be appreciated. Performance of the LINQ to Entity would need to be considered as it would involve retrieving lot of rows.
Something like (assuming the obvious context):
var res = await (from c in dbCtx.Customers
where c.CustomerMarket = "GB"
let homeCount = c.Products.Where(p => p.Category = "HomeAppliance").Count()
let furnCount = c.Products.Where(p => p.Category = "Furnishing").Count()
orderby c.CustomerID descending
select new {
CustomerID = c.CustomerID,
CustomerName = c.CustomerName,
ApplianceCount = homeCount,
FurnishingCount = furnCount
}).ToListAsync();
Performance of the LINQ to Entity would need to be considered as it would involve retrieving lot of rows.
You'll need to confirm the SQL generated is reasonable (best way to help that is not getting more columns than you need), after that performance is down to how well the server runs that SQL.
Yes, it is possible:
customers
.Where(cust => cust.CustomerMarket == "GB")
.Select(cust => new
{
cust.CustomerId,
cust.CustomerName,
ApplianceCount = products
.Where(prod => prod.CustomerId == cust.CustomerId && prod.Category == "HomeAppliance")
.Select(prod => prod.ProductId)
.Count(),
FurnishingCount = products
.Where(prod => prod.CustomerId == cust.CustomerId && prod.Category == "Furnishing")
.Select(prod => prod.ProductId)
.Count(),
});
Here both customers and products are IQueryable<T>s of the respective type.

Linq Query with double sub queries

I am struggling converting the following SQL query I wrote into Linq. I think I'm on the right track, but I must be missing something.
The error I'm getting right now is:
System.Linq.IQueryable does not contain a definition for .Contains
Which is confusing to me because it should right?
SQL
select Users.*
from Users
where UserID in (select distinct(UserID)
from UserPermission
where SupplierID in (select SupplierID
from UserPermission
where UserID = 6))
LINQ
var Users = (from u in _db.Users
where (from up in _db.UserPermissions select up.UserID)
.Distinct()
.Contains((from up2 in _db.UserPermissions
where up2.UserID == 6
select up2.SupplierID))
select u);
EDIT: I ended up going back to SqlCommand objects as this was something I had to get done today and couldn't waste too much time trying to figure out how to do it the right way with Linq and EF. I hate code hacks :(
I think there is no need to do a distinct here (maybe I am wrong). But here is a simpler version (assuming you have all the navigational properties defined correctly)
var lstUsers = DBContext.Users.Where(
x => x.UserPermissions.Any(
y => y.Suppliers.Any(z => z.UserID == 6)
)
).ToList();
Above if you have UserID field in Supplier entity, if it is NOT you can again use the navigational property as,
var lstUsers = DBContext.Users.Where(
x => x.UserPermissions.Any(
y => y.Suppliers.Any(z => z.User.UserID == 6)
)
).ToList();
Contains() only expects a single element, so it won't work as you have it written. Try this as an alternate:
var Users = _db.Users
.Where(u => _db.UserPermissions
.Select(x => UserID)
.Distinct()
.Where(x => _db.UserPermissions
.Where(y => y.UserID == 6)
.Select(y => y.SupplierID)
.Contains(x))
);
I didn't try on my side but you can try using the let keyword:
var Users = (from u in _db.Users
let distinctUsers = (from up in _db.UserPermissions select up).Distinct()
let subQuery = (from up2 in _db.UserPermissions
where up2.UserID == 6
select up2)
where
distinctUsers.SupplierID== subQuery.SupplierID &&
u.UserID==distinctUsers.UserID
select u);

LINQ syntax and the where clause

I have written some LINQ but it doesn't return the correct data, it seems to ignore my where clause. Can anybody advise me on what I am doing wrong with the syntax?
IEnumerable<Ranking> lst = (from r in results
join m in membersToRank on r.UserId equals m.userId
join t in teamsToRank on m.teamId equals t.teamId
where r.ResultDate >= rankingStart
&& r.ResultDate <= rankingEnd
select new Ranking
{
memberId = m.memberId,
chain = t.chain,
name = m.name,
teamId = m.teamId,
value = results.Count(i => i.IsCorrect && i.UserId == m.userId)
}).ToList();
This line
value = results.Count(i => i.IsCorrect && i.UserId == m.userId)
will bypass the where clause. You have have to repeat the where there
value = results.Where(...).Count(i => i.IsCorrect && i.UserId == m.userId)
or
var results2 = results.Where(...)
and then use only results2.
(as a sidenote, it will even bypass the join, so it could become a little more complex depending on what you want)

How can I write a linq query for this?

I need to write following query in Linq to SQL but not sure what is the best way of doing, given it has two derived tables. Any suggestions.
SELECT A.ID
FROM
(
SELECT *
FROM Orders
WHERE ProductID = 5
) A
JOIN
(
SELECT CustomerID, MAX(Price) Price
FROM Orders
WHERE ProductID = 5
GROUP BY CustomerID
) B
ON A.CustomerID = B.CustomerID and A.Price = B.Price
var b = (
from o in db.Orders
where o.ProductID == 5
group o by o.CustomerID into og
select new {
CustomerID = og.Key
Price = Max(og.Price)
}
);
var a = (
from o in db.Orders
join p in b on new {a.CustomerID, a.Price} equals
new {b.CustomerID, b.Price}
where o.ProductID == 5
select a.ID
);
var r = a.ToString();
These two links are invaluable when forming things like this:
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
http://msdn.microsoft.com/en-us/vstudio/bb688085
Can you simplify this with LINQ, especially if you use method syntax instead of query syntax.
orders.Where(o => o.ProductID == 5)
.GroupBy(o => o.CustomerID)
.SelectMany(g => g.Where(o => o.Price == g.Max(m => m.Price)));
My advice when writing LINQ, do not simply attempt to convert a SQL statement exactly. Think about the desired result and develop a solution designed for LINQ.
Something along these lines:
var result = from a in context.Orders
join b in (context.Orders.Where(o => o.ProductID == 5).GroupBy(o => o.CustomerID).Select(g => new { CustomerID = g.Key, Price = g.Max(o => o.Price)))
on new {a.CustomerID, a.Price} equals new {b.CustomerID, b.Price}
where a.ProductID == 5
select a.ID;

Efficient way to sub Queries in Linq

here is my linq code:
BOOK entity = db.BOOKS
.Where(s => s.ID == (from p in db.LIBRARY
from b in db.BOOKS
where (p.ID == 123) && (p.idpage == b.idpage)
select b.fields));
My actual oracle code is:
SELECT DISTINCT BOOKS.ID
FROM LIBRARY,BOOKS
WHERE LIBRARY.ID = 123 AND LIBRARY.ID = BOOKS.ID
But its showing the error in s.ID that..
Delegate 'System.Func Project.Models.BOOKS,int,bool' does not take 1 arguments
Why does this happen? Are there any workarounds?
Your SQL is using a join, so you can do the same thing in LINQ. Either of these approaches will suffice:
// join
var query = (from b in db.BOOKS
join p in db.LIBRARY on b.IdPage equals p.IdPage
where p.ID == 123
select b.Id).Distinct();
// 2 from statements (SelectMany) can also be used as a join
var query = (from b in db.BOOKS
from p in db.LIBRARY
where p.ID == 123 && b.IdPage == p.IdPage
select b.Id).Distinct();
// fluent syntax
var query = db.BOOKS
.Where(b => db.LIBRARY.Any(p =>
p.ID == 123 && b.IdPage == p.IdPage))
.Select(b => b.Id)
.Distinct();
s.ID is comparing to an Enumerable, so you get the error.
At the end of the LINQ query, add a SingleOrDefault().
Your subquery returns a sequence of values, not a single values, so you can't compare it to a scalar property like ID. You should use First on the result of the subquery to get the first result (or Single if there should be only one)
BOOK entity = db.BOOKS
.Where(s => s.ID == (from p in db.LIBRARY
from b in db.BOOKS
where (p.ID == 123) && (p.idpage == b.idpage)
select b.fields).First());
You should be able to use the navigation properties on your BOOKS class to do something like this:
var bookIds = db.BOOKS.Where(b => b.LIBRARIES.Any(l => l.ID == 123))
.Select(b => b.ID)

Categories

Resources