Skip to content

Instantly share code, notes, and snippets.

@progmars

progmars/Program.cs

Last active Aug 7, 2019
Embed
What would you like to do?
Issue with applying Where conditions from generic classes after Select()
using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;
namespace ProjectionCriteria
{
public interface ISomeable
{
string Some { get; set; }
}
public class MyFullEntity : ISomeable
{
public int Id { get; set; }
public string Some { get; set; }
public string SomethingElse { get; set; }
}
public class MySimpleEntity : ISomeable
{
public int Id { get; set; }
public string Some { get; set; }
}
public class Program
{
public static Expression<Func<T, bool>> GetCriteria<T>() where T : class, ISomeable
{ return e => (e.Some == "Hello"); }
public static void Main()
{
SetupDatabase();
using (var db = new ProjectionCriteriaContext())
{
Expression<Func<MySimpleEntity, bool>> someCriteria = e => (e.Some == "Hello");
Expression<Func<MySimpleEntity, bool>> someCriteria2 = GetCriteria<MySimpleEntity>();
var query = db.Entities
.Select(s => new MySimpleEntity { Id = s.Id, Some = s.Some });
// if this Select is removed and MySimpleEntity in both expressions replaced with MyFullEntity,
// the issue disappears
// this succeeds
// var filteredQueryResults = query.Where(someCriteria).ToList();
// this fails: why is it evaluated locally and not in SQL? <-------------------------------
var filteredQueryResults = query.Where(someCriteria2).ToList();
// someCriteria2 is set to the same e => (e.Some == "Hello");
// debugger shows that both someCriteria and someCriteria2 are essentially the same,
// however, Linq somehow knows about generic roots of someCriteria2 and treats it differently
/*
* 'Microsoft.EntityFrameworkCore.Query.QueryClientEvaluationWarning:
* The LINQ expression 'where (new MySimpleEntity() {Id = [s].Id, Some = [s].Some}.Some == "Hello")'
* could not be translated and will be evaluated locally.'.
*/
foreach (var r in filteredQueryResults)
{
Console.WriteLine($"{r.Id} {r.Some}");
// expect "1 Hello"
}
}
}
private static void SetupDatabase()
{
using (var db = new ProjectionCriteriaContext())
{
if (db.Database.EnsureCreated())
{
db.Entities.Add(
new MyFullEntity { Some = "Hello", SomethingElse = "Hello SomethingElse" });
db.Entities.Add(
new MyFullEntity { Some = "World", SomethingElse = "World SomethingElse" });
db.SaveChanges();
}
}
}
}
#region EF init stuff
public class ProjectionCriteriaContext : DbContext
{
private static readonly ILoggerFactory _loggerFactory
= new LoggerFactory().AddConsole((s, l) => l == LogLevel.Information && !s.EndsWith("Connection"));
public DbSet<MyFullEntity> Entities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=ProjectionCriteriaSandbox;Trusted_Connection=True;ConnectRetryCount=0;")
.UseLoggerFactory(_loggerFactory);
// https://saebamini.com/the-dangerous-ef-core-feature-automatic-client-evaluation/
// disable dangerous auto-client-eval fallback
optionsBuilder.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entb = modelBuilder
.Entity<MyFullEntity>().ToTable("Entities");
entb.Property(v => v.Id).HasColumnName("Id");
entb.Property(v => v.Some).HasColumnName("Some");
entb.Property(v => v.SomethingElse).HasColumnName("SomethingElse");
}
}
#endregion
}
@progmars

This comment has been minimized.

Copy link
Owner Author

@progmars progmars commented Aug 7, 2019

The program is completely self contained, using Microsoft LocalDB.
NuGet dependencies:
Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.6"
Microsoft.Extensions.Logging" Version="2.2.0"
Microsoft.Extensions.Logging.Console" Version="2.2.0"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment