尝试编写一个可以重写查询的 ExpressionVisitor
起点
var contractSubjectKey = new ContractSubjectKey(subjectId, rank);
insurance = context.Set<Insurance>()
.FirstOrDefault(i => i.Key.ContractSubjectKey == contractSubjectKey);
预计重写后
i => i.Key.ContractSubjectKey.Id == contractSubjectKey.Id
我试图在这里重写
BinaryExpression
,以便它可以自行比较属性。
我想我的操作左侧已经被覆盖了。 BinaryExpression
的初始左侧是 MemberExpression
。
var left = Expression.Property(memberExpression, "Id");
所以我只访问属性本身。
如果我用在右侧
ConstantExpression
var right = Expression.Constant(1, typeof(int));
所以结果操作看起来像
i.Key.ContractSubjectKey.Id == 1
它可以毫无问题地转换为 SQL,并检索数据。
我的右侧确实有问题。右侧是
ParameterExpression
。我在初始 Expression
或 QueryExpressionEventData
中找不到参数值(使用 IQueryExpressionInterceptor
)。不确定我完全理解它。它代表一个局部变量,在翻译过程中求值,接近常量?
PropertyExpression
var right = Expression.Property(parameterExpression, "Id");
看起来我得到了正确的表达,但在翻译过程中它失败了 `Linq 表达式 .... 无法翻译
System.InvalidOperationException:LINQ 表达式 'DbSet() .Where(i => EF.Property(EF.Property(i, "Key"), "ContractSubjectKey").Id == __contractSubjectKey_0.Id)' 无法翻译。以可翻译的形式重写查询,或者通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用来显式切换到客户端评估。
那么问题出在局部变量
Id
的评估值中吗?
尝试了很多东西...不会在这里浪费空间。
另外一条背景信息
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);
}
所以你从调用者传递一个表达式开始;
Expression<Func<T,bool>> lambda = i => i.Key.ContractSubjectKey == contractSubjectKey;
为了捕获变量,C# 编译器将右侧翻译为等价的内容;
public struct Captures{
ContractSubjectKey contractSubjectKey;
}
var rhs = Expression.Field(
Expression.Constant(
new Captures{
contractSubjectKey = contractSubjectKey
},
typeof(Captures)),
nameof(contractSubjectKey));
您可以做的最简单的事情就是用额外的属性访问替换
BinaryExpression
;
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.Left.Type == typeof(C)
&& node.Right.Type == typeof(C)
&& node.Conversion.NodeType == ExpressionType.Equal)
return Expression.Equal(
Expression.Property(node.Left, "Id"),
Expression.Property(node.Right, "Id"));
return base.VisitBinary(node);
}
这样 EF Core 仍会将捕获的变量绑定到 sql 参数。如果您确实想用常量替换 sql 参数,您还可以访问 lhs 为常量的任何成员访问。然后您可以使用反射来访问该值并将其作为常量返回。