Last active
August 7, 2019 09:19
-
-
Save progmars/eeec32a533dbd2e1f85e551db1bc53f8 to your computer and use it in GitHub Desktop.
Issue with applying Where conditions from generic classes after Select()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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"