用匿名类型创建表达式

6

假设我通过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,也不熟悉其他任何构造来动态构建这样的表达式。 如果不能,那么在动态生成查询的嵌套表达式时,我应该采取另一种方法吗?


我不确定你打算如何在这种情况下应用你的谓词;匿名类型仅有IDPetName - ThingOtherThing是什么? - Marc Gravell
@MarcGravell 抱歉,这只是一些示例代码。属性并不重要。我有一个匿名类型,想要与PredicateBuilder一起使用。实际情况涉及将多个属性与某些过滤器预设进行比较,并从数据库中提取其他数据以进一步限制结果。我编辑了原始查询以生成所有正确的属性供示例使用。 - Mike Cluck
我建议升级到最新的LINQKit,它不再需要.True.False。它可以在GitHub上找到。 - NetMage
2个回答

6
假设您仅需要针对匿名类型使用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);

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


我喜欢使用下划线变量来表示被忽略的参数。幸运的是,在最新的LINQKit中,你根本不需要使用.True.False - NetMage
1
可以,谢谢!使用LINQKit的PredicateBuilder.New<T>()也可以工作。我想@NetMage可能有一种初始化ExpressionStarter的方法,而不需要这个迂回的方法。 - Jason Honingford

4

通常情况下,在这里使用PredicateBuilder可能会很棘手;直接使用Expression API可能更容易。您需要获取表示匿名类型的Type,构建表达式时不需要一个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);

有趣且多才多艺的方法。但这会在编译时打破静态类型,对吧?所以如果我输错了 Expression.PropertyOrField(p, "ID") 并写成了 Expression.PropertyOrField(p, "Id"),那么它将是一个运行时错误,对吗? - Mike Cluck
很不幸,@MikeC,除非你这样做:var example = Example(query); 并使用 nameof(example.ID)nameof(example.PetName),否则没有简单的方法来获取那里的名称。其中 Example 是:private static T Example<T>(IQueryable<T> query) => default(T);(已添加到答案)。 - Marc Gravell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接