使用匿名类型构建表达式

Building an Expression with an anonymous type

假设我通过 LINQ 查询创建了一些匿名类型:

var query = from Person in db.People
            join Pet in Pets on Person.ID equals Pet.PersonID
            join Thingy in Thingies on Person.ID equals Thingy.PersonID
            select new { 
                Person.ID, 
                PetName = Pet.Name,
                Thing = Thingy.Thing,
                OtherThing = Thingy.OtherThing
            };

现在我想通过动态构建谓词将一些复杂的表达式应用于查询。我一直在为已知类型做的是这样的:

var predicate = PredicateBuilder.False<MyType>();
predicate = predicate.Or(myType => myType.Flag);
if (someCondition) {
    var subPredicate = PredicateBuilder.True<MyType>();
    subPredicate = subPredicate.And(myType => myType.Thing == someThing);
    subPredicate = subPredicate.And(myType => myType.OtherThing == someOtherThing);
    predicate = predicate.Or(subPredicate);
}
query = query.Where(predicate);

是否可以对匿名类型做类似的事情?我还没有找到将匿名类型与 PredicateBuilder 一起使用的方法,而且我不熟悉任何其他可以让我动态构建这样的表达式的构造。如果不是,在为查询动态生成嵌套表达式时是否应该采用另一种方法?

假设你只需要 PredicateBuilder.False<T>PredicateBuilder.True<T> 匿名类型,你可以这样做:

private static Expression<Func<T,bool>> MakeTrue<T>(IQueryable<T> ignored) {
    return PredicateBuilder.True<T>();
}
private static Expression<Func<T,bool>> MakeFalse<T>(IQueryable<T> ignored) {
    return PredicateBuilder.False<T>();
}

现在您可以重写代码而无需显式使用匿名类型:

var predicate = MakeFalse(query);
predicate = predicate.Or(v => v.ID > 10000);
query = query.Where(predicate);

我们的想法是让编译器为您进行类型推断。不幸的是,您最终得到了一个未使用的方法参数。

一般情况下,这里用PredicateBuilder可能比较尴尬;直接使用 Expression API 可能更容易。您需要获取表示匿名类型的 Type,构建表达式 without a T,然后转入泛型代码以构建最终的 lambda。幸运的是,不太棘手:

var p = Expression.Parameter(GetQueryType(query));
var body = Expression.And(
    Expression.Equal(Expression.PropertyOrField(p, "ID"), Expression.Constant(123)),
    Expression.Equal(Expression.PropertyOrField(p, "PetName"), Expression.Constant("Jim"))
);

var filtered = ApplyPredicate(query, body, p);

与:

private static Type GetQueryType<T>(IQueryable<T> query) => typeof(T);
private static IQueryable<T> ApplyPredicate<T>(IQueryable<T> query,
    Expression body, params ParameterExpression[] parameters)
    => query.Where(Expression.Lambda<Func<T, bool>>(body, parameters));

附加:如果您不喜欢成员名称的字符串文字:

var example = Example(query);
var p = Expression.Parameter(GetQueryType(query));
var body = Expression.And(
    Expression.Equal(Expression.PropertyOrField(p, nameof(example.ID)),
        Expression.Constant(123)),
    Expression.Equal(Expression.PropertyOrField(p, nameof(example.PetName)),
        Expression.Constant("Jim"))
);

与:

private static T Example<T>(IQueryable<T> query) => default(T);