构建表达式以在对象的子属性(集合)中搜索

Building Expression to search in child properies (collection) of object

我有一个搜索设置,可以根据条件 return 匹配 Record 对象。它根据条件类型构建表达式树,并查询数据库以匹配 return。以前,它只匹配顶级属性,但现在我试图在 Record 的子属性中搜索并构建一个可以在查询中与其他属性组合的表达式。对象看起来像这样:

public class Record
{
    public virtual ICollection<Attribute> Attributes { get; set; }
}

public class Attribute
{
    public Guid AttributeId { get; set; }
    public string Value { get; set; }

    public virtual Record Record { get; set; }
}

可能的条件之一是 AttributeIdValue 的值对,为此我会 return 所有 Record 具有 [=18= 的行] 匹配两个值。例如,如果我只是编写一个简单的 LINQ 查询,它将如下所示:

Guid searchId = Guid.NewGuid();
string searchValue = "asdf";

IQueryable<Record> records = GetSomeQueryToRecords();
IEnumerable<Record> recordsThatMatch = records
    .Where(r => r.Attribute.Any(a => a.AttributeId == searchId && a.Value == searchValue));

我需要将其构建到我的表达式树中,以便它在内存中和 EF Core 查询中都能工作。由 GetExpression 编辑的表达式 return 最终将与其他使用 AndAlso 的表达式组合,以构建一个大型查询。我试过这个:

ParameterExpression record = Expression.Parameter(typeof(Record));
Expression searchExpression = GetExpression(record, searchId, searchValue);

Expression<Func<Record, bool>> lambda = Expression.Lamda<Func<Record, bool>>(searchExpression, record);
recordsThatMatch = records.Where(lambda);


private Expression GetExpression(ParameterExpression parameter, Guid searchId, string searchValue)
{
    // Get the Record.Attributes Memboer.
    MemberExpression attributes = Expression.Property(parameter, nameof(Record.Attributes));

    // Get the "record.Attributes.Any(predicate)" Method.
    MethodInfo anyMethod = typeof(Enumerable).GetMethods()
        .Where(m => m.Name == nameof(Enumerable.Any))
        .Where(m => m.GetParameters().Length == 2)
        .Single();
    
    // Make Method generic.
    anyMethod = anyMethod.MakeGenericMethod(typeof(Attribute));

    ParameterExpression attribute = Expression.Parameter(typeof(Attribute));

    Expression idEqualExpression = Expression.Equal(
        Expression.Property(attribute, nameof(Attribute.AttributeId)),
        Expression.Constant(searchId)
    );

    Expression valueEqualExpression = Expression.Equal(
        Expression.Property(attribute, nameof(Attribute.Value)),
        Expression.Constant(searchValue)
    );

    Expression attributeMatchExpression = Expression.AndAlso(idEqualExpression, valueEqualExpression);

    return Expression.Call(null, anyMethod, attributes, attributeMatchExpression);
}

不过我得到了 ArgumentExceptionExpression.Call,因为老实说我不知道​​如何将它用于这个特定实例,而且它有很多覆盖。我相信我应该传递 null 因为我实际上想要一个静态方法,因为 .Any() 是一个扩展方法。

您忘记创建 'LambdaExpresson'。还有Expression.Call可以简化。

private Expression GetExpression(ParameterExpression parameter, Guid searchId, string searchValue)
{
    // Get the Record.Attributes Memboer.
    MemberExpression attributes = Expression.Property(parameter, nameof(Record.Attributes));

    ParameterExpression attribute = Expression.Parameter(typeof(Attribute));

    Expression idEqualExpression = Expression.Equal(
        Expression.Property(attribute, nameof(Attribute.AttributeId)),
        Expression.Constant(searchId)
    );

    Expression valueEqualExpression = Expression.Equal(
        Expression.Property(attribute, nameof(Attribute.Value)),
        Expression.Constant(searchValue)
    );

    Expression attributeMatchExpression = Expression.AndAlso(idEqualExpression, valueEqualExpression);

    var matchLambda = Expression.Lambda(attributeMatchExpression, attribute);

    return Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new [] {typeof(Attribute)}, attributes, matchLambda);
}