【问题标题】:Building an Expression with an anonymous type使用匿名类型构建表达式
【发布时间】:2017-05-19 16:19:31
【问题描述】:

假设我通过 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 是什么?
  • @MarcGravell 抱歉,这只是一些示例代码。属性并不重要。只是我有一个匿名类型,我想与PredicateBuilder 一起使用。真实案例涉及将一些属性与一些过滤器预设进行比较,并从数据库中提取额外数据以进一步限制结果。我编辑了原始查询以生成示例的所有正确属性。
  • 我建议升级到不再需要 .True.False 的最新 LINQKit。它在 GitHub 上。

标签: c# linq linq-to-sql predicatebuilder


【解决方案1】:

假设您只需要 PredicateBuilder.False&lt;T&gt;PredicateBuilder.True&lt;T&gt; 来表示匿名类型,您可以这样做:

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
  • 这行得通,谢谢!这也适用于 LINQKit 的 PredicateBuilder.New() 。我想也许@NetMage 有一种方法来初始化 ExpressionStarter 没有这个回合的方法。
【解决方案2】:

一般情况下,这里使用PredicateBuilder可能会比较尴尬;直接使用Expression API 可能更容易。您需要获取代表匿名类型的Type,构建表达式without 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") 这将是一个运行时错误,对吗?
  • @MikeC 很遗憾,是的,没有简单的方法可以在那里获得名称 - 除非你这样做:var example = Example(query);,并使用nameof(example.ID)nameof(example.PetName),其中@ 987654334@ 是:private static T Example&lt;T&gt;(IQueryable&lt;T&gt; query) =&gt; default(T);(添加到答案中)
猜你喜欢
  • 2011-04-14
  • 1970-01-01
  • 2012-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多