1

I have a code snippet that uses let with a where clause.

private List<string> Example2()
{
    var query1 =
        from c in _context.Customers
        let custPurchases = _context.Purchases.Where(p => p.CustomerId == c.Id)
        where custPurchases.Any(x => x.Price > 500)
        select c.Name;

    return query1.ToList();
}

It compiles well, but I get such an error during execution:

System.InvalidOperationException: The query contains a projection 'c => DbSet() .Where(p => p.CustomerId == c.Id)' of type 'IQueryable'. Collections in the final projection must be an 'IEnumerable' type such as 'List'. Consider using 'ToList' or some other mechanism to convert the 'IQueryable' or 'IOrderedEnumerable' into an 'IEnumerable'.

Why does EF Core try to execute this code on the client side instead of on the server (database)? Where does EF Core encounter the problem during translation IQueryable to SQL?

P.S.

LINQPad 8 by default easily executes this code, but that's LINQ-to-SQL: enter image description here

Maybe you can provide some literature or links to dig deeper into the expression tree world?


I use SQL Server provider at LinqPad. Connection string is "Data Source=.;Integrated Security=SSPI;TrustServerCertificate=true;app=LINQPad".

17
  • 3
    I think LinqPad just does the final materialization for you, so you are comparing apples and oranges. (Not 100% sure, though) Commented Oct 6 at 10:42
  • 2
    How did you set up the connection in LinqPad, though? Is it using EF or LINQ to SQL? Commented Oct 6 at 10:47
  • 2
    What are the actual Customer and Purchase entities? Not the tables, the C# classes ? Also what are you actually using? EF Core is a completely different ORM from LINQ-to-SQL, which came out in 2007 more as a demo of LINQ than a full featured ORM. It was never migrated to .NET Core Commented Oct 6 at 11:22
  • 2
    I wouldn't call this a bug, it's a VERY GOOD feature - the question's code is trying to combine two other LINQ queries. That's not a problem with LINQ-to-Objects (ie collections). With databases, it results in terrible performance and unexpected problems: the entire Customers and Purchases tables will be loaded before joining in memory. The 20-year old LINQ-to-SQL allowed this for convenience but EF and EF Core don't, because the production consequences are too high. Commented Oct 8 at 7:35
  • 2
    So, instead of trying to be clever, EF Core is telling you what's wrong and asks you to fix it. Remember, EF Core is an ORM, not a query builder. It's job is to load graphs of related objects, not emulate SQL. Commented Oct 8 at 7:37

2 Answers 2

1

I would try to change the query to:

var query1 = _context.Customers
                     .Where(c => c.Purchases.Any(p => p.Price > 500))
                     .Select(c => c.Name);

This assumes there is a navigational property from a customer to its purchases.

There are limitations to what kind of expressions Entity Framework can translate to SQL, simple filters and selects are generally fine, but even nested expressions like this can be problematic in some cases. I would at least inspect the produced SQL to verify that it looks reasonable, but that may be because I'm used to old EF versions that are worse at query generation.

The simplest way I know to check if a query can be translated is to just test it, preferably with a unit test. It is fairly easy to setup a SQLite in-memory database provider for testing, but some features depend on the specific database provider, so the tests will be more accurate if you are using your actual database provider.

Sign up to request clarification or add additional context in comments.

3 Comments

Using an in-memory database provider for testing is a terrible idea — EF doesn’t translate queries for this provider and executes them as-is.
Thanks, I was not aware of that distinction. SQLite also offers an in memory database, and I presume that at least ensures queries are translated.
Yes, SQLite using translation, it is easy to check by enabling query logging. Though given that it still does not prove that query will be translatable for the destination database)
0

You should probably be using a join instead. You can then filter by purchase price and then group the results by customer before selecting the customer's name.

    var query1 =
        from c in _context.Customers
        join p in _context.Purchases on c.Id equals p.CustomerId
        where p.Price > 500
        group c by c.Name into g
        select g.Key;

With SQL Server and EF Core 8, this will translate into the following SQL*:

SELECT [c].[Name]
  FROM [dbo].[Customer] AS [c]
 INNER JOIN [dbo].[Purchase] AS [p] ON [c].[Id] = [p].[CustomerId]
 WHERE [p].[Price] > 500
 GROUP BY [c].[Customer]

* Schema, table and column names may be different of course

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.