表达式访问者访问表示局部变量的参数的属性

问题描述 投票:0回答:1

尝试编写一个可以重写查询的 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);
}

c# entity-framework-core expression expressionvisitor
1个回答
0
投票

所以你从调用者传递一个表达式开始;

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 为常量的任何成员访问。然后您可以使用反射来访问该值并将其作为常量返回。

© www.soinside.com 2019 - 2024. All rights reserved.