Home > Enterprise >  EF core translate query form SQL
EF core translate query form SQL

Time:09-17

I have the following query which I run on my sqlite DB:

SELECT * FROM ServicesData
WHERE JobID IN (
  SELECT JobID
  FROM ServicesData
  WHERE JObID IS NOT NULL
  GROUP BY JobID
  ORDER BY MAX(Time) DESC LIMIT 3
)
ORDER BY Time;

(this gets all entries of the recent last 3 jobs, see table and result here SQL query for entries of last N jobs)

Running the query against my sqlite DB in LinqPad takes 0.02 seconds.

I am trying to translate this to linq-to-sql in EF Core

I tried:

ServicesData.Where(x => ServicesData
  .Where(y => y.JobID!=null)
  .GroupBy(y => y.JobID)
  .OrderByDescending(y => y.Max(z => z.Time))
  .Select(y => y.Key)
  .Take(3)
  .Contains(x.JobID)
)

Which seems to give the correct same results as the SQL above

However LinqPad shows the following SQL for it:

SELECT *
FROM "ServicesData" AS "s"
WHERE EXISTS (
    SELECT 1
    FROM (
        SELECT "s0"."JobID", MAX("s0"."Time") AS "c"
        FROM "ServicesData" AS "s0"
        WHERE "s0"."JobID" IS NOT NULL
        GROUP BY "s0"."JobID"
        ORDER BY MAX("s0"."Time") DESC
        LIMIT 3
    ) AS "t"
    WHERE ("t"."JobID" = "s"."JobID") OR ("t"."JobID" IS NULL AND "s"."JobID" IS NULL))

and it is very slow, taking 2.6 seconds !

For some reasons the contains was not translated to the IN operator

What am I doing wrong ?

CodePudding user response:

Does the query itself take 2.6 seconds, or is that the time it takes to get back your entities for the 3 resulting jobs? EF materializing entities takes time, so it will never be as fast as raw SQL spitting out results.

Still, from that query this part seems a bit of a concern:

OR ("t"."JobID" IS NULL AND "s"."JobID" IS NULL))

Since the inner query is only considering Job IDs that are not null, the above statement shouldn't alter the results, but it honestly isn't necessary. EF generating an EXISTS rather than an IN clause is fairly common with the SQL generation, and generally shouldn't pose an obvious performance hit.

You might try:

ServicesData.Where(x => x.JobID != null
    && ServicesData
        .Where(y => y.JobID!=null)
        .GroupBy(y => y.JobID)
        .OrderByDescending(y => y.Max(z => z.Time))
        .Select(y => y.Key)
        .Take(3)
        .Contains(x.JobID))
    .OrderBy(x => x.Time);

I'd be curious if that strips off the extra Null = Null allowance.

EF is a useful tool, but at the end of the day it needs to generate SQL to cover many possible permutations and combinations of operations so it cannot be expected to pick the most efficient SQL in all cases. Sometimes using two simpler queries, or resorting to an explicit generated SQL Statement / Sproc might be justified.

For instance:

var jobIds = ServiceData.Where(x => x.JobID != null)
    .GroupBy(x => x.JobID)
    .OrderByDescending(x => x.Max(y => y.Time))
    .Select(x => x.Key)
    .Take(3)
    .ToList();

var serviceData = ServiceData.Where(x => jobIds.Contains(x.JobId))
    .OrderBy(x => x.Time)
    .ToList();

It's a matter of weighing up performance against potentially introducing complexity in the forms of work-arounds to avoid expensive performance traps that might be present from EF's generation rules.

CodePudding user response:

In fact you don't do anything wrong! It's the EF issue that I encountered to many times! When you group by on a column x and then apply contains on x you get such a translation to SQL!

  • Related