Search code examples
c#linq.net-coreentity-framework-coreef-core-7.0

Client evaluation in top-level EF Core projection


Suppose I perform a query and project into an anonymous type; that would perform evaluation on the server:

var people = await context
  .People
  .OrderBy(x => x.Name)
  .Select(x => new { x.Id, x.Surname, x.Forename })
  .ToListAsync();

But suppose I perform a query and need to project into a concrete type.

Option 1: map results into concrete type:

var people = (await context
  .People
  .OrderBy(x => x.Name)
  .Select(x => new { x.Id, x.Surname, x.Forename })
  .ToListAsync())
  .Select(x => new PersonDto(x.Id, x.Surname, x.Forename));

Option 2: use concrete type in "top-level projection":

var people = await context
  .People
  .OrderBy(x => x.Name)
  .Select(x => new PersonDto(x.Id, x.Surname, x.Forename));
  .ToListAsync()

About (2), the docs state that in a top-level projection, some evaluation is performed in the database and some on the client. (But the docs show an example of a client function in an anonymous type, so it differs to my use case.)

In this specific example, am I right in assuming that all evaluation is performed on the server, and that (2) is just a short-cut for (1), i.e. they are functionally equivalent?


Solution

  • Question:

    all evaluation is performed on the server, and that (2) is just a short-cut for (1), i.e. they are functionally equivalent?

    Answer

    Obviously, the PersonDto object is instantiated on client side, but, EF is able to get the three constructor parameters values, x.Id, x.Surname, x.Forename from the database. Just this 3 values ( Select id, surname, forename from people order by name ):

    EF Core supports partial client evaluation in the top-level projection (essentially, the last call to Select()). If the top-level projection in the query can't be translated to the server, EF Core will fetch any required data from the server and evaluate remaining parts of the query on the client. If EF Core detects an expression, in any place other than the top-level projection, which can't be translated to the server, then it throws a runtime exception

    Full explanation at https://learn.microsoft.com/en-us/ef/core/querying/client-eval docs.

    Test

    Here a sample of EF translating top-level projection to SQL. Expression:

    var result = 
        dbcontext.
        Customers.
        Select(x => new PersonName2CharsDto(x.Name.Substring(0,2))).
        ToList();
    

    The generated sql (for sqlite):

    SELECT substr("c"."Name", 0 + 1, 2)
    FROM "Customers" AS "c"