简体   繁体   中英

Linq Query slow with Count. SQL query is very fast. What am I doing wrong?

I have a very basic parent children relationship. For my main page, I just wanted to get the counts from the children table.

var assignmenTotal = new AssignmentUser
{
    IsSupervisor = supervisor,
    AssignmentTotals = (
        from a in db.Assignments
        where (StartDate.HasValue) 
            ? DbFunctions.TruncateTime(a.CreatedDate) == StartDate 
            : a.IsArchived == false
        orderby a.ID ascending
        join b in db.Adjustments on a.ID equals b.AssignmentID
        group b by new {a.ID,a.UserName,a.Status,a.CreatedDate,a.IsArchived} 
        into g
        select new AssignmentTotals
        {
            ID =  g.Key.ID,
            UserName = g.Key.UserName,
            Status = g.Key.Status,
            ImportedDate = DbFunctions.TruncateTime(g.Key.CreatedDate),
            StartingLocation = (db.Adjustments
                .Where(x => x.AssignmentID == g.Key.ID)
                .OrderBy(x => x.LocationID)
                .Select(x => x.LocationID)
                .FirstOrDefault()),
            EndingLocation = (db.Adjustments.
                Where(x => x.AssignmentID == g.Key.ID)
                .OrderByDescending(x => x.LocationID)
                .Select(x => x.LocationID)
                .FirstOrDefault()),
            TotalLocations = g.Count(x => x.LocationID != null),
            TotalLicensePlates = g.Count(x => x.ExpectedLicensePlateID != null),
            TotalAdjCompleted = g.Count(x => x.Status == "C"),
            IsSameUser = (currUser == g.Key.UserName ? true : false),
            IsArchived = g.Key.IsArchived
        })
        .OrderBy(x => x.ID)
        .ToList()
};

Now total flatten rows are about 1000 and this is taking about 10 seconds to complete. If I write a SQL Query

SELECT ID, UserName, Status, b.StartLocation, b.EndLocation, b.TotalLocations, 
       b.TotalLicensePlates, b.TotalLocations 
FROM Assignments a
INNER JOIN(
SELECT AssignmentID,
min(LocationID) as StartLocation, max(LocationID) as EndLocation,
COUNT(CASE WHEN LocationID is NOT NULL THEN 1 ELSE 0 end) AS TotalLocations,
SUM(CASE WHEN ExpectedLicensePlateID IS NOT NULL THEN 1 ELSE 0 END )TotalLicensePlates,
SUM(CASE WHEN Status = 'C' THEN 1 ELSE 0 END )TotalAdjCompleted
FROM dbo.Adjustments
group by AssignmentID
) b on (a.ID = b.AssignmentID)
WHERE convert(date,a.CreatedDate) ='04/23/2021'

This takes less than a second to complete.

I think my problem is in the linq COUNT part. I have tried doing a subquery but is still slow. I think the problem is that the linq query is bringing all the data to client and doing all the work in the client instead of having the server doing all the work?

Is there a better way to do this?

Edit: I'm using Entity Framework and when I checked the SQL profiler, the SQL send is very long and complicated.

Entity Framework (just like any other programmatic Object Relational Mapper) get worse performance and efficient the more complex your request is.

So, options:

  1. Deal with it. Eh, it's not the best. Clearly it's going to be slow, but if it work, it works.
  2. Use Raw SQL Queries in EF EF6 / core .
  3. Use a different ORM, but you'd need to evaluate those yourself to find the pros and cons.
  4. Forgo SQL in c# entirely and use Stored Procedures, View, Functions, and other SQL objects to keep SQL queries in the database. (thanks Antonín Lejsek )

Problem here, that you have written non equivalent LiNQ Query. There is no optimal prediction what to do with FirstOrDefault in projection. It creates additional OUTER APPLY joins which is slow.

Rewrite your query to be closer to the SQL as possible:

 var query =
    from a in db.Assignments
    where (StartDate.HasValue) 
        ? DbFunctions.TruncateTime(a.CreatedDate) == StartDate 
        : a.IsArchived == false
    orderby a.ID ascending
    join b in db.Adjustments on a.ID equals b.AssignmentID
    group b by new {a.ID,a.UserName,a.Status,a.CreatedDate,a.IsArchived} 
    into g
    select new AssignmentTotals
    {
        ID =  g.Key.ID,
        UserName = g.Key.UserName,
        Status = g.Key.Status,
        ImportedDate = DbFunctions.TruncateTime(g.Key.CreatedDate),
        StartingLocation = g.Min(x => x.LocationID)
        EndingLocation = g.Max(x => x.LocationID),
        TotalLocations = g.Count(x => x.LocationID != null),
        TotalLicensePlates = g.Count(x => x.ExpectedLicensePlateID != null),
        TotalAdjCompleted = g.Count(x => x.Status == "C"),
        IsSameUser = (currUser == g.Key.UserName ? true : false),
        IsArchived = g.Key.IsArchived
    };

var totals = query
        .OrderBy(x => x.ID)
        .ToList();

var assignmenTotal = new AssignmentUser
{
    IsSupervisor = supervisor,
    AssignmentTotals = totals
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM