Dynamic LINQ OrderBy performance issue, generating nested select - performance

This is a method used to fetch data for a table. While the code does "work", it has become a bottleneck in our application. In some areas it can take 30 seconds to load a page.
var orderBy = String.IsNullOrEmpty(options.SortColumn) ? GetOrderBy<T>() : options.SortColumn;
var orderbyDirection = (options.SortDirection == SortDirection.Desc ? " descending" : "");
options.TotalSize = queryable.Count();
queryable = queryable
.SearchProperties(options.SearchPhrase)
.OrderBy(orderBy + orderbyDirection)
.SetPaging(options);
return queryable;
On investigation I have found that when it hits the OrderBy statement is for some reason turning the query into a nested select.
an example of a query it would generate
{SELECT
[Project1].[Id] AS [Id],
[Project1].[EntryDate] AS [EntryDate],
[Project1].[Description] AS [Description],
[Project1].[DiaryTypeString] AS [DiaryTypeString],
[Project1].[DiaryViewType] AS [DiaryViewType],
[Project1].[DiaryGroup] AS [DiaryGroup],
[Project1].[ReminderDate] AS [ReminderDate],
[Project1].[HasAttachments] AS [HasAttachments],
[Project1].[OwnerId] AS [OwnerId],
[Project1].[PropertyId] AS [PropertyId],
[Project1].[TenancyId] AS [TenancyId],
[Project1].[SupplierId] AS [SupplierId]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[EntryDate] AS [EntryDate],
[Extent1].[Description] AS [Description],
[Extent1].[DiaryTypeString] AS [DiaryTypeString],
[Extent1].[DiaryViewType] AS [DiaryViewType],
[Extent1].[DiaryGroup] AS [DiaryGroup],
[Extent1].[ReminderDate] AS [ReminderDate],
[Extent1].[HasAttachments] AS [HasAttachments],
[Extent1].[OwnerId] AS [OwnerId],
[Extent1].[PropertyId] AS [PropertyId],
[Extent1].[TenancyId] AS [TenancyId],
[Extent1].[SupplierId] AS [SupplierId]
FROM [dbo].[vwDiary] AS [Extent1]
WHERE (([Extent1].[OwnerId] = #p__linq__0) OR (([Extent1].[OwnerId] IS NULL) AND (#p__linq__0 IS NULL))) AND ( NOT ((N'Work Order' = [Extent1].[DiaryTypeString]) AND ([Extent1].[DiaryTypeString] IS NOT NULL))) AND ( NOT ((N'Alert' = [Extent1].[DiaryTypeString]) AND ([Extent1].[DiaryTypeString] IS NOT NULL)))
) AS [Project1]
ORDER BY [Project1].[EntryDate] DESC}
Im a bit stuck on what to do at this point, is there something wrong with how the OrderBy is implemented? are there any alternative ways to order by a dynamic column?

Related

How can I convert T-SQL with aggregate function to LINQ?

I have a complex script with T-SQL and I couldn't convert it to LINQ syntax:
SELECT cc.ContractID,
Max(CASE WHEN cc.CompanyID = vc.CompanyID THEN vc.CompanyTitle END) AS CompanyID,
Max(CASE WHEN cc.ContractorID = vc.CompanyID THEN vc.CompanyTitle END) AS ContractorID
FROM ConContracts cc
JOIN ViewCompanies vc
ON vc.CompanyID IN ( cc.CompanyID, cc.ContractorID )
GROUP BY cc.ContractID
Can any one help me??
I used Linqer to try and convert this query and ended up with:
from cc in db.ConContracts
from vc in db.ViewCompanies
where
vc.CompanyID == cc.CompanyID ||
vc.CompanyID == cc.ContractorId
group new {cc, vc} by new {
cc.ContractID
} into g
select new {
g.Key.ContractID,
CompanyID = g.Max(p => (p.cc.CompanyID == p.vc.CompanyID ? p.vc.CompanyTitle : null)),
ContractorID = g.Max(p => (p.cc.ContractorId == p.vc.CompanyID ? p.vc.CompanyTitle : null))
}
I'm by no means a LINQ expert, so don't know for sure whether this is a valid answer, but thought it might be helpful or interesting if it is at least functionally satisfactory, as it means you could have used Linqer to answer your question.
This isn't an exact equivalent to your T-SQL, but I suspect it might give you the results you want.
I suspect that ContractID is unique to your ConContracts table and CompanyID is unique to the ViewCompanies table and hence the max is only added because you need it if you are using group by.
So, if your tables are defined correctly you can do something like
(from cc in ConContracts
select new
{
cc.ContractID,
CompanyID = cc.Company.CompanyTitle,
ContractorID = cc.Contractor.CompanyTitle
}

Nullable DateTime in Where Clause in EF Linq Query

Hi I have a table where there is a RespondBy property which is of DateTime and is Nullable. Here is the linq I'm trying to run over EF6:
IEnumerable<Enquiry> ASAPEnquiries = db.Enquiries
.Where(enq => enq.RespondBy == null && enq.JobCostings.Count == 0)
.OrderBy(enq => enq.FlReference);
However, when I run if (ASAPEnquiries.Count() > 0) I get an error stating Nullable object must have a value. How would one query the database using linq if you want to check null DateTime columns?
Thank you.
EDIT:
The SQL that is produced by EF when tested in MSSMS brings back the desired result FYI. SQL Produced:
SELECT
[Project1].[Id] AS [Id],
[Project1].[FlReference] AS [FlReference],
[Project1].[EnquiryDate] AS [EnquiryDate],
[Project1].[ContactName] AS [ContactName],
[Project1].[ProjectReference] AS [ProjectReference],
[Project1].[EnquiryDetails] AS [EnquiryDetails],
[Project1].[RespondBy] AS [RespondBy],
[Project1].[CreatedBy] AS [CreatedBy],
[Project1].[Created] AS [Created],
[Project1].[ModifiedBy] AS [ModifiedBy],
[Project1].[Modified] AS [Modified],
[Project1].[RowVersion] AS [RowVersion],
[Project1].[Enquiry_Customer] AS [Enquiry_Customer],
[Project1].[Enquiry_EnquiryStatus] AS [Enquiry_EnquiryStatus]
FROM ( SELECT
[Extent1].[Id] AS [Id],
[Extent1].[FlReference] AS [FlReference],
[Extent1].[EnquiryDate] AS [EnquiryDate],
[Extent1].[ContactName] AS [ContactName],
[Extent1].[ProjectReference] AS [ProjectReference],
[Extent1].[EnquiryDetails] AS [EnquiryDetails],
[Extent1].[RespondBy] AS [RespondBy],
[Extent1].[CreatedBy] AS [CreatedBy],
[Extent1].[Created] AS [Created],
[Extent1].[ModifiedBy] AS [ModifiedBy],
[Extent1].[Modified] AS [Modified],
[Extent1].[RowVersion] AS [RowVersion],
[Extent1].[Enquiry_Customer] AS [Enquiry_Customer],
[Extent1].[Enquiry_EnquiryStatus] AS [Enquiry_EnquiryStatus],
(SELECT
COUNT(1) AS [A1]
FROM [dbo].[JobCostings] AS [Extent2]
WHERE [Extent1].[Id] = [Extent2].[JobCosting_Enquiry]) AS [C1]
FROM [dbo].[Enquiries] AS [Extent1]
) AS [Project1]
WHERE ([Project1].[RespondBy] IS NULL) AND (0 = [Project1].[C1])
ORDER BY [Project1].[FluidReference] ASC
Also, if I iterate over the collection, I get no error so it seems to be something to do with calling .Count() on the collection.
Use the HasValue property to check if a Nullable has value.
IEnumerable<Enquiry> ASAPEnquiries = db.Enquiries
.Where(enq => enq.RespondBy.HasValue && enq.JobCostings.Count == 0)
.OrderBy(enq => enq.FlReference);
Please make sure that your Enquiry class assign value to the JobCostings property in it's constructor
e.g.
public Enquiry()
{
this.JobCostings = new HashSet<JobCosting>();
//.......
}
and try this one :
IEnumerable<Enquiry> ASAPEnquiries = db.Enquiries
.Where(enq => enq.RespondBy == null && !enq.JobCostings.Any())
.OrderBy(enq => enq.FlReference);
It would seem I have egg on my face. The if statement was calling a void where I was then using item.RespondBy.Value.ToShortDateTime(). How embarresing. Thank you all for your help though, I appreciate your time.

EF Linq query with conditional include

So I have the following Linq query:
var member = (from mem in
context.Members.Include(m =>
m.MemberProjects.Select(mp => mp.Project))
where mem.MemberId == memberId
select mem).FirstOrDefault();
This returns a Member entity, with a set of MemberProjects that have a Project child. I would like to limit the MemberProjects to only those for which the Project child has a property
ProjectIdParent == null.
One of my failed attempts might make the intent clearer:
var member = (from mem in context.Members
.Include(m => m.MemberProjects
.Where(mp =>
mp.Project.ProjectIdParent == null)
.Select(proj => proj.Project))
where mem.MemberId == memberId
select mem).FirstOrDefault();
This of course complains of an invalid Include expression because of the Where clause.
Any thoughts on how to do this would be great :)
DISCLAIMER: I havent tested this. This is just an idea. If you let me know the results, I will update this accordingly. (Skip to the update part for the tested solutions)
var member = (from mps in context.MemberProjects
.Include(m => m.Members)
.Include(m => m.Projects)
where mps.Project.ProjectIdParent == null
select mps)
.FirstOrDefault(mprojs => mprojs.Member.MemberId == memberId);
I'd also analyze the queries using something like EFProfiler to make sure the generated queries dont leave the realm of sanity.
You can also take a look at this post by Jimmy Bogard on Many to Many relationships with ORMs.
Update
I came up with multiple tested solutions for this with EF 6.1.3. My Edmx looked like below:
The setup data is like below:
I was able to run code below to get the MemberFive correctly
var member = context.Members.FirstOrDefault
(m => m.MemberId == memberId
&& m.Projects.Any(p => p.ProjectParentId == null));
The generated SQL looked like this:
SELECT TOP (1) [Extent1].[MemberId] AS [MemberId],
[Extent1].[MemberName] AS [MemberName]
FROM [dbo].[Members] AS [Extent1]
WHERE ([Extent1].[MemberId] = 1)
AND (EXISTS (SELECT 1 AS [C1]
FROM (SELECT [MemberProjects].[MemberId] AS [MemberId],
[MemberProjects].[ProjectId] AS [ProjectId]
FROM [dbo].[MemberProjects] AS [MemberProjects])
AS [Extent2]
INNER JOIN [dbo].[Projects] AS [Extent3]
ON [Extent3].[ProjectId] = [Extent2].[ProjectId]
WHERE ([Extent1].[MemberId] = [Extent2].[MemberId])
AND ([Extent3].[ProjectParentId] IS NULL)))
If you dont like the generated query you can use this:
var memberQuery = #"Select M.* from Members M
inner join MemberProjects MP on M.MemberId = Mp.ProjectId
inner join Projects P on MP.ProjectId = P.ProjectId
where M.MemberId = #MemberId and P.ProjectParentId is NULL";
var memberParams = new[]
{
new SqlParameter("#MemberId", 1)
};
var member3 = context.Members.SqlQuery(memberQuery, memberParams)
.FirstOrDefault();
The later consistently returned under 20ms vs the other one hovered around 60ms (if that matters to you).
I hope this helps.

Dynamic select in query with linq

I see Dynamic linq in below link:
ScottGu
I want to use this method to select a dynamic query like this:
I have a complex select and this way not solve my problem,
this is my select:
Select sUserName,sname, sFamily,sMobail,sid,sNumberSt,sActive,sPic,sDate from Student where {0} order by sid desc";
which {0} is a complex Condition:
sname like %somthing% and susername=N'something' and sid in (select sidfk from tablex where userteacher=N'name1')
and this condition is passed to method.
I must say that:
I don's know my condition,my condition specified with user search params.this condition that I say,Is only one example of what I want to do.
How can I do this?
Only way that solve my problem:
I send tow parameters instead of one,one for student and one for tablex:
var az = db.tablex.Where(p => p.userteacher== name1)
.Select(p => p.sidfk).ToList();
var query = db.Students.Where(textSearch).Where(s=>az.Contains(s.sid)).OrderByDescending(s => s.sid)
.Select(s => new
{
s.sUserName,
s.sname,
s.sFamily,
s.sMobail,
s.sid,
s.sNumberSt,
s.sActive,
s.sPic,
s.sDate,
});
wiche textSearch is :
sname like %somthing% and susername=N'something'
with dynamic linq
any better way is exist?
You don't need to use dynamic linq for this situation.
string paramA = "", paramB = "";
var query = from x in context.table1
where x.name == paramA
where context.table2.Where(y => y.field1 == paramB).Any(y => y.id == x.id)
select x;
Dynamic Linq usually use if in query you don't know what field will be use, so in your sample you use only params for conditions with field, so you don't ned dynamic linq
you can little optimize you query like this
var query = from student in db.Students
join teacher in db.tablex on student.sid equals teacher.sidfk
where student.sname.Contains('somthing') &&
susername=='something' &&
teacher.userteacher=='name1'
orderby s.sid descending
select new
{
s.sUserName,
s.sname,
s.sFamily,
s.sMobail,
s.sid,
s.sNumberSt,
s.sActive,
s.sPic,
s.sDate,
};

LINQ: Self join query, how to accomplish this?

Can anyone help?
I have 1 class, basically it holds Members and within that class is a List.
The members i have in a List also... So basically it goes like this,
I have 2 members and each member has a number of sessions.
I wish to only return each member with 1 Session.
I have done a LINQ query, but of course it doesn't work...
I think i need to do a self join, any ideas?
Basically my error is m doesn't exist in my subquery self join.
var sessions =
from m in this.members
join s in
(
from se in m.Sessions
group se by se.Name into g
select new {Name = g.Key, SessioEndTime = g.Max(a=>a.SessioEndTime)}
)
on m.Name equals s.Name
select new { MemberName = m.Name, SessionTime = s.SessioEndTime}
I would appreciate any feedback anyone has.
Thanks in advance.
EDIT
Ok i managed to do it like the following, but is this the best way?
var sessions =
from m in this.members
let sn = m.Sessions.OrderByDescending(a => a.SessionEndTime).FirstOrDefault()
select new { MemberName = m.Name, SessionTime = sn.SessioEndTime}
This way sn contains 1 record, but i have access to all the properties...
But is this the best way to do using a LET?
Thanks.
Unless I am missing something you need this, no?
var sessions =
from m in members
select new {
MemberName = m.Name,
SessionTime = m.Sessions.Max(s => s.SessioEndTime)
};
You have to change the way you think about LINQ queries, think more from object point rather than from SQL implementation point. What is it that I need? I need all members, each with its latest session end time, then act on that.
EDIT:
The let option you used is ok, just keep something in mind FirstOrDefault will return null if member has an empty list of Sessions, and then sn.SessionEndTime hits null reference. If on the other hand you are certain that every member has at least one session use First instead or aggregate.
Also don't use FirstOrDefault() in the let, it kind of messes up the LINQ and prevents it from tying it to the master (causing a separate SQL query for each master to detect missing subsets), so usable queries with let are:
from m in Members
let sn = m.Sessions.Max(s => s.SessioEndTime)
select new { MemberName = m.Name, SessionTime = sn};
from m in Members
let sn = m.Sessions.OrderByDescending(a => a.SessioEndTime).First()
select new { MemberName = m.Name, SessionTime = sn.SessioEndTime};
As for ordering vs Max aggregation, both queries will generate a subquery:
-- MAX
SELECT [t0].[Name] AS [MemberName], (
SELECT MAX([t1].[SessioEndTime])
FROM [Session] AS [t1]
WHERE [t1].[memberId] = [t0].[id]
) AS [SessionTime]
FROM [Member] AS [t0]
GO
-- ordering
SELECT [t0].[Name] AS [MemberName], (
SELECT [t2].[SessioEndTime]
FROM (
SELECT TOP (1) [t1].[SessioEndTime]
FROM [Session] AS [t1]
WHERE [t1].[memberId] = [t0].[id]
ORDER BY [t1].[SessioEndTime] DESC
) AS [t2]
) AS [SessionTime]
FROM [Member] AS [t0]
With a descending index on SessioEndTime the ordering script is about twice slower (you can get execution plans for these to check for yourself), without the index its about 5times slower.

Resources