6

Let's say I've created some anonymous type via a LINQ query:

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
            };

Now I want to apply some complex expressions to the query by dynamically building a predicate. What I have been doing for known types is something like this:

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);

Is it possible to do something similar with an anonymous type? I haven't found a way of using anonymous types with PredicateBuilder and I'm not familiar with any other constructs which would let me dynamically build an expression like this. If not, is there another approach I should take when dynamically generating nested expressions for a query?

Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
  • I'm not sure how you would indend to apply your predicate in this case; the anonymous type *only has* an `ID` and `PetName` - what are `Thing` and `OtherThing` ? – Marc Gravell May 19 '17 at 16:27
  • @MarcGravell Sorry, it's just some example code. The properties aren't important. It's just that I have an anonymous type which I want to use with `PredicateBuilder`. The real case involves comparing a number of properties against some filter pre-sets and extracting additional data from the database to further limit the results. I edited the original query to produce all of the correct properties for the example. – Mike Cluck May 19 '17 at 16:30
  • I would suggest upgrading to the latest LINQKit that doesn't need `.True` or `.False` any more. It is on GitHub. – NetMage May 19 '17 at 21:43

2 Answers2

6

Assuming that all you need is PredicateBuilder.False<T> and PredicateBuilder.True<T> for an anonymous type, you can do it like this:

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>();
}

Now you can rewrite your code without the need to use the anonymous type explicitly:

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

The idea is to have the compiler do the type inference for you. Unfortunately, you end up with an unused method parameter.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • I like to use the underscore variable for ignored parameters. Fortunately in the latest LINQKit you don't need to use `.True` and `.False` at all. – NetMage May 19 '17 at 21:42
  • 1
    That works thanks! And this works with LINQKit's PredicateBuilder.New() too. I thought maybe @NetMage had a way to initialize ExpressionStarter without this round about method. – Jason Honingford Jun 29 '20 at 17:40
4

In the general case, it may be awkward to use PredicateBuilder here; it may be easier to use the Expression API directly. You'll need to get the Type representing the anonymous type, build the expression without a T, then flip into generic code to build the final lambda. Fortunately, not too tricky:

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);

with:

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));

Additional: if you don't like the string literals for the member names:

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"))
);

with:

private static T Example<T>(IQueryable<T> query) => default(T);
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Interesting and versatile approach. This does break out of static typing at compile time though, right? So if I typoed `Expression.PropertyOrField(p, "ID")` and wrote `Expression.PropertyOrField(p, "Id")` it would be a run-time error, correct? – Mike Cluck May 19 '17 at 16:41
  • @MikeC unfortunately yes, there's no easy way to get the name there - *unless* you do: `var example = Example(query);`, and use `nameof(example.ID)` and `nameof(example.PetName)`, where `Example` is: `private static T Example(IQueryable query) => default(T);` (added to answer) – Marc Gravell May 19 '17 at 16:48