Trying to write an ExpressionVisitor that would rewrite a query
Starting point
var contractSubjectKey = new ContractSubjectKey(subjectId, rank);
insurance = context.Set<Insurance>()
.FirstOrDefault(i => i.Key.ContractSubjectKey == contractSubjectKey);
After rewrite expected
i => i.Key.ContractSubjectKey.Id == contractSubjectKey.Id
I'm trying to rewrite BinaryExpression
here so it goes and compares properties itself.
The left side of operation I think i got covered. Initial left side of BinaryExpression
is MemberExpression
.
var left = Expression.Property(memberExpression, "Id");
So I just access property itself.
In case i use for right side ConstantExpression
var right = Expression.Constant(1, typeof(int));
so the resulting operation looks like
i.Key.ContractSubjectKey.Id == 1
it gets translated to SQL no problem, and retrieves data.
I do have problem with the right side. Right side is ParameterExpression
. Parameter values I cannot find anywhere in initial Expression
or QueryExpressionEventData
(working with IQueryExpressionInterceptor
). Not sure I understand it fully. It represents a local variable, evaluates during translation, something close to constant?
Anyway, if i try PropertyExpression
var right = Expression.Property(parameterExpression, "Id");
it seems i get proper looking expression, but during translation it fails `The Linq expression .... could not be translated
System.InvalidOperationException : The LINQ expression 'DbSet() .Where(i => EF.Property(EF.Property(i, "Key"), "ContractSubjectKey").Id == __contractSubjectKey_0.Id)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
So problem is somewhere in evaluation value for Id
on local variable?
Tried many things... not gonna waste space here.
One additional piece of context
public record ContractSubjectKey(int Id, int Rank);
//insurance.Key.ContractSubjectKey is actually ContractSubjectKeyInsurance which inherits from ContractSubjectKey
public record ContractSubjectKeyInsurance : ContractSubjectKey
{
public ContractSubjectKeyInsurance(int Id, int Rank) : base(Id, Rank)
{
}
public ContractSubjectKeyInsurance(int Id, int Rank, int InsuranceKeyId) : base(Id, Rank)
{
_insuranceKeyId = InsuranceKeyId;
}
private int _insuranceKeyId;
}
public record InsuranceKey(int Id, ContractSubjectKey ContractSubjectKey)
{
this.Id = Id;
_contractSubjectId = ContractSubjectKey.Id;
_contractSubjectRank = ContractSubjectKey.Rank;
this.ContractSubjectKey =
new ContractSubjectKeyInsurance(ContractSubjectKey.Id, ContractSubjectKey.Rank, Id);
}
EDIT
So, the issue is not the expression rewrite itself. I'm working with EF Core. I want to plug in IQueryExpressionInterceptor
, that would do such expression change for every required query. The issue here is, there is IQueryTranslationPreprocessor
, which extracts and caches the parameters. So the starting query is member access / constant expression, and you can rewrite it with just additional property access, once the query reaches IQueryExpressionInterceptor
it is no longer possible. Link below to ticket that i think can describe the situation.
https://github.com/dotnet/efcore/issues/30208
Possible solution include custom IQueryTranslationPreprocessor/Postprocessor
. Postprocessor
should be able to access the parameter value. Preprocessor you can adjust initial query or caching of parameters. Developers of EF Core warn about adjusting Preprocessor
as this can cause performance pitfalls.