efcore: Expression.Constant reference to DbSet causes translation failure
The following query works perfectly fine on EFCore 2.2, but fails on EFCore 3.0: (running against MSSQL 2017, or Azure SQL)
var query = from p1 in context.MyEntities
where (p1.MasterCIRId == userContext.CIRId)
&& (p1.IsActive == true)
&& !context.MyEntities.Any(p2 => p2.EntityKey == p1.EntityKey)
select p1;
Running the query on 2.2 translates nicely into a ‘where exists …’ clause in the SQL, whilst 3.0 creates a where clause, passing in a list of DbMyEntity classes which logically cannot be translated into valid SQL.
The entity class is pretty straightforward:
public class DbMyEntity()
{
public virtual ICollection<DbConditionEntityList> ConditionEntityLists { get; set; }
[ForeignKey("MasterCIRId")]
public virtual DbCIR MasterCIR { get; set; }
[Column("MasterCIRId", Order = 1, TypeName = "int")]
[Required]
public int MasterCIRId { get; set; }
[Column("IsActive", Order = 2, TypeName = "bit")]
[Required]
public bool IsActive { get; set; }
[Column("EntityKey", Order = 3, TypeName = "int")]
[Required]
public int EntityKey { get; set; }
}
About this issue
- Original URL
- State: open
- Created 5 years ago
- Comments: 15 (10 by maintainers)
Took another look at this, and indeed there seems to be a regression from 2.2. tl;dr, if you replace
Expression.Constant(others)
withExpression.Property(Expression.Constant(ctx), "Entities"),
the code will start working.@smitpatel, here’s a minimal that passes on 2.2 but fails on 3.0:
Basically ctx.Customers remains a InternalDbSet constant in the tree and is never converted into an EntityQueryable, and so makes NavigationExpandingEV fail. If, instead of taking the DbSet as a constant, we pass access it as a property on a context constant, everything works.
@smitpatel Thanks, that’s even better!
I had to make a little change. The property Expression is an direct interface-implementation, so I had to cast it to an IQueryable:
But after that, everything works as expected.
@GerardSmit - Just call context.Set<>.Expression, and use that instead of creating constant of queryable. So code you posted can be replaced with
Edit: Updated after @gerardsmit pointed out a mistake.
Ugh, I just cleaned my machine of all preview bits 😕
Confirmed, test passes in preview5.
Your expression tree generation seems to have a bug. Without digging too much into it, the entire filter expression seems like it could be replaced with:
Note the redundant double comparison of masterCirId, so this could be simplified to yield the following in your Program.cs, which works:
If you need this filter in a separate method for reuse, you can simply redefine
Bottom line, the compiler is much better at writing expression trees than us humans 😃 Manual expression general, as you’ve done, does make sense when the query structure itself varies as a result of some value, but that doesn’t seem to be the case here.
One more important note: in your manual expression tree logic,
masterCirId
was being included as a constant. This means that for each value of that variable, a different query is internally compiled and cached, yielding in very bad perf. WhenmasterCirId
is parameterized (as it is in the examples I’ve given), only one query is compiled and reused, and the different values of the variable are sent as parameters (so SQL Server also reuses the same plan).The issue did not repro for me. However, your model above is incomplete (no key, no definitions DbCIR, DbConditionEntityList…) so there could be a discrepancy that explains this. Please submit a full runnable code sample similar to the below which demonstrates the issue.
My attempted repro:
The following SQL is produced: