What problem are you trying to solve?
I would like to report a subtle issue where an EF Core query can accidentally switch from System.Linq.Queryable to System.Linq.Enumerable when a Func<> delegate is used instead of an Expression<Func<...>>.
This can happen when a predicate is stored in a variable typed as Func<T, bool> and then used in a query against a DbSet / IQueryable source.
Minimal example:
Func<Employee, bool> predicate = e => e.Salary > 3000;
var employees = context.Employees
.Where(predicate)
.ToList();
In this case, the call resolves to:
System.Linq.Enumerable.Where(
IEnumerable source,
Func<TSource, bool> predicate)
instead of:
System.Linq.Queryable.Where(
IQueryable source,
Expression<Func<TSource, bool>> predicate)
Since DbSet implements IQueryable but is also enumerable, the code compiles successfully. However, the predicate is not part of the EF Core expression tree and therefore cannot be translated to SQL.
As a result, a developer may incorrectly assume that the predicate is translated and executed in the database, while the filtering actually happens on the client side after the data is enumerated.
Currently, no warning is produced for this scenario.
This can be especially confusing for developers who are learning EF Core, because the query looks like a normal EF Core LINQ query but behaves differently due to overload resolution.
Describe the solution you'd like
I would like EF Core analyzers to produce a warning when a System.Linq.Enumerable LINQ operator is invoked on a source whose type is, or implements, IQueryable.
For example, the analyzer could detect cases like:
IQueryable source = context.Employees;
Func<Employee, bool> predicate = e => e.Salary > 3000;
var employees = source
.Where(predicate)
.ToList();
Suggested warning message:
Possible unintended client-side evaluation.
The LINQ method 'Where' is using a Func<> delegate on an IQueryable source.
This causes System.Linq.Enumerable.Where to be used, so the predicate is evaluated on the client instead of being translated to SQL.
Use Expression<Func<T, bool>> or an inline lambda expression to allow EF Core to translate the query.
Suggested fix:
Expression<Func<Employee, bool>> predicate = e => e.Salary > 3000;
var employees = context.Employees
.Where(predicate)
.ToList();
or:
var employees = context.Employees
.Where(e => e.Salary > 3000)
.ToList();
The analyzer could initially target common LINQ operators such as Where, Select, OrderBy, Any, Count, FirstOrDefault, and similar methods when the selected method comes from System.Linq.Enumerable and the source is an IQueryable.
To reduce false positives, the analyzer should probably not warn when the developer explicitly switches to client-side evaluation, for example:
context.Employees
.AsEnumerable()
.Where(predicate);
or after materialization:
context.Employees
.ToList()
.Where(predicate);
This proposal is specifically about analyzer support. EF Core itself may not be able to produce a runtime warning in this scenario because the Func<> predicate is not part of the expression tree that EF Core receives.
What problem are you trying to solve?
I would like to report a subtle issue where an EF Core query can accidentally switch from System.Linq.Queryable to System.Linq.Enumerable when a Func<> delegate is used instead of an Expression<Func<...>>.
This can happen when a predicate is stored in a variable typed as Func<T, bool> and then used in a query against a DbSet / IQueryable source.
Minimal example:
Func<Employee, bool> predicate = e => e.Salary > 3000;
var employees = context.Employees
.Where(predicate)
.ToList();
In this case, the call resolves to:
System.Linq.Enumerable.Where(
IEnumerable source,
Func<TSource, bool> predicate)
instead of:
System.Linq.Queryable.Where(
IQueryable source,
Expression<Func<TSource, bool>> predicate)
Since DbSet implements IQueryable but is also enumerable, the code compiles successfully. However, the predicate is not part of the EF Core expression tree and therefore cannot be translated to SQL.
As a result, a developer may incorrectly assume that the predicate is translated and executed in the database, while the filtering actually happens on the client side after the data is enumerated.
Currently, no warning is produced for this scenario.
This can be especially confusing for developers who are learning EF Core, because the query looks like a normal EF Core LINQ query but behaves differently due to overload resolution.
Describe the solution you'd like
I would like EF Core analyzers to produce a warning when a System.Linq.Enumerable LINQ operator is invoked on a source whose type is, or implements, IQueryable.
For example, the analyzer could detect cases like:
IQueryable source = context.Employees;
Func<Employee, bool> predicate = e => e.Salary > 3000;
var employees = source
.Where(predicate)
.ToList();
Suggested warning message:
Possible unintended client-side evaluation.
The LINQ method 'Where' is using a Func<> delegate on an IQueryable source.
This causes System.Linq.Enumerable.Where to be used, so the predicate is evaluated on the client instead of being translated to SQL.
Use Expression<Func<T, bool>> or an inline lambda expression to allow EF Core to translate the query.
Suggested fix:
Expression<Func<Employee, bool>> predicate = e => e.Salary > 3000;
var employees = context.Employees
.Where(predicate)
.ToList();
or:
var employees = context.Employees
.Where(e => e.Salary > 3000)
.ToList();
The analyzer could initially target common LINQ operators such as Where, Select, OrderBy, Any, Count, FirstOrDefault, and similar methods when the selected method comes from System.Linq.Enumerable and the source is an IQueryable.
To reduce false positives, the analyzer should probably not warn when the developer explicitly switches to client-side evaluation, for example:
context.Employees
.AsEnumerable()
.Where(predicate);
or after materialization:
context.Employees
.ToList()
.Where(predicate);
This proposal is specifically about analyzer support. EF Core itself may not be able to produce a runtime warning in this scenario because the Func<> predicate is not part of the expression tree that EF Core receives.