Why is this LINQ so much slower than its SQL counterpart?
I have the below LINQ to SQL method that takes an inordinate amount of time to execute yet its SQL counterpart is quite simple and fast. Am I doing something wrong in the LINQ part? I am just trying to return some data to display, read-only, in a Data Grid.
I understand that if the tool doesn't fit don't use it and as such I could just do a SQL call here but I'd like to understand why there is such a difference.
Below is the LINQ and then the SQL that it dumps.
public static DataTable GetEnrolledMembers(Guid workerID)
{
using (var DB = CmoDataContext.Create())
{
var AllEnrollees = from enrollment in DB.tblCMOEnrollments
where enrollment.CMOSocialWorkerID == workerID || enrollment.CMONurseID == workerID
join supportWorker in DB.tblSupportWorkers on enrollment.EconomicSupportWorkerID
equals supportWorker.SupportWorkerID into workerGroup
from worker in workerGroup.DefaultIfEmpty()
select
new
{
enrollment.ClientID,
enrollment.CMONurseID,
enrollment.CMOSocialWorkerID,
enrollment.EnrollmentDate,
enrollment.DisenrollmentDate,
ESFirstName = worker.FirstName,
ESLastName = worker.LastName,
ESPhone = worker.Phone
};
var result = from enrollee in AllEnrollees.AsEnumerable()
where (enrollee.DisenrollmentDate == null || enrollee.DisenrollmentDate > DateTime.Now)
let lastName = BLLConnect.MemberLastName(enrollee.ClientID)
let firstName = BLLConnect.MemberFirstName(enrollee.ClientID)
orderby enrollee.DisenrollmentDate ascending , lastName ascending
select new
{
enrollee.ClientID,
LastName = lastName,
FirstName = firstName,
NurseName = BLLAspnetdb.NurseName(enrollee.CMONurseID),
SocialWorkerName = BLLAspnetdb.SocialWorkerName(enrollee.CMOSocialWorkerID),
enrollee.EnrollmentDate,
enrollee.DisenrollmentDate,
ESWorkerName = enrollee.ESFirstName + " " + enrollee.ESLastName,
enrollee.ESPhone
};
DB.Log = Console.Out;
return result.CopyLinqToDataTable();
}
}
And the SQL:
SELECT [t0].[ClientID], [t0].[CMONurseID], [t0].[CMOSocialWorkerID], [t0].[EnrollmentDate], [t0].[D开发者_StackOverflow社区isenrollmentDate], [t1].[FirstName] AS [ESFirstName], [t1].[LastName] AS [ESLastName], [t1].[Phone] AS [ESPhone]
FROM [dbo].[tblCMOEnrollment] AS [t0]
LEFT OUTER JOIN [dbo].[tblSupportWorker] AS [t1] ON [t0].[EconomicSupportWorkerID] = ([t1].[SupportWorkerID])
WHERE ([t0].[CMOSocialWorkerID] = @p0) OR ([t0].[CMONurseID] = @p1)
-- @p0: Input UniqueIdentifier (Size = 0; Prec = 0; Scale = 0) [060632ee-be09-4057-b17b-2d0190d0ff74]
-- @p1: Input UniqueIdentifier (Size = 0; Prec = 0; Scale = 0) [060632ee-be09-4057-b17b-2d0190d0ff74]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.4926
By adding AsEnumerable()
:
var result = from enrollee in AllEnrollees.AsEnumerable()
...
You're forcing the first query to be evaluated completely, and every record (AllEnrollees) to be fetched from the DB.
With your SQL statement, you're doing all of the filtering on the server, which will be much faster.
First, I don't think you are comparing apples to apples, you have plenty of BllConnect.Something calls around the select of the second linq query. Additionally you have to take out the AsEnumerable as mentioned in the other answer.
Consider the below (given you add the corresponding relations in the db and/or linq2sql designer):
public static DataTable GetEnrolledMembers(Guid workerID)
{
using (var DB = CmoDataContext.Create())
{
var AllEnrollees = from enrollment in DB.tblCMOEnrollments
where enrollment.CMOSocialWorkerID == workerID
|| enrollment.CMONurseID == workerID
let w = enrollment.EconomicSupporterWorker
select new
{
enrollment.ClientID,
enrollment.CMONurseID,
enrollment.CMOSocialWorkerID,
enrollment.EnrollmentDate,
enrollment.DisenrollmentDate,
ESFirstName = w != null ? w.FirstName : null,
ESLastName = w != null ? w.LastName : null,
ESPhone = w != null ? w.Phone : null
};
var filteredEnrollees = AllEnrollees
.Where(e=> e.DisenrollmentDate == null || e.DisenrollmentDate > DateTime.Now);
//benchmark how much it delays if you do a .ToList until here
// ... when comparing the sql, run it on the same remote computer you are running this,
// so you take into account the time to transfer the data.
filteredEnrollees = filteredEnrollees
.OrderBy(e=> e.DisenrollmentData) // benchmark here again
.ThenBy(e=> BLLConnect.MemberLastName(enrollee.ClientID)); // prob. causing issues
var result = // do what you already had, but against filteredEnrollees and benchmark
// prob. issues with BllConnect.* and BllAspnetdb.* being called for each record /
// ... doesn't happen in sql side
DB.Log = Console.Out;
return result.CopyLinqToDataTable();
}
}
精彩评论