It feels strange to me that some LINQ predicates that will materialize a collection (running SQL in the Entity framework case), and that materialization changes what is valid code, without changing the compile-time programming contract.
For example, given bool IsGoodBook(string)
:
entities.Books.ToList().Where(b => IsGoodBook(b.title)).Count()
works, but takes forever because it loads the entire Books table in to memory.
But
entities.Books.Where(b => IsGoodBook(b.title)).Count()
fails at runtime, because IsGoodBook is a custom function not understood by my database.
I would have expected b.title
to be of some sort of ProxyString type in the first case (maybe dependent on the EF-library I'm using - SQL Server, SQLite, PostgreSQL etc.), and an ordinary C# string in the second.
I further would have expected something like .Materialize<T>(Func<T>)
and friends so that you could call .Materialize(m => m.Count)
, .Materialize(m => m.ToDictionary(...))
. I concede that's a bit more code but it makes it clear where the SQL execution happens.
Using the same types here seems to reintroduce the same class of bugs that were finally put to rest by making string?
a different type than string
.
Was there a rationale for using the same types on both sides of materialization?
See also https://learn.microsoft.com/en-us/ef/core/providers/sql-server/functions