Combining expression trees
Asked Answered
P

2

1

I have the following expression:

public Expression<Func<T, bool>> UserAccessCheckExpression<T>(int userId) where T : class
{
    return x => (IsAdmin || userId == CurrentUserId || userId == 0);
}

Then I want to apply this filter to several collections (IQueryable) like this one:

return tasks
  .Where(t => t.TaskUsers
     .Any(x => UserAccessCheckExpression<TaskUser>(x.User) && x.SomeBool == true));

I'm getting the following error while doing so:

Error 40 Cannot implicitly convert type System.Linq.Expressions.Expression<System.Func<TaskUser,bool>> to bool

I can't use workaround with interface inheritance (like TaskUser inherits interface with int UserId property (where T : IHasUserId)) since I want to combine logic.

Programme answered 20/4, 2016 at 7:3 Comment(5)
You're going to have to manually construct the Lambda somewhat, something similar to this: #4001582 I don't have enough time to give a full answer.Michellemichels
You will need to use the solution from here. And then you'd write something like t.TaskUsers.Any(UserAccessCheckExpression)Obala
What is your Linq provider? Is it Entity Framework? What's IsAdmin and CurrentUserId in your expression?Llano
@haim770, it is EF. They are properties of the other class.Programme
@jeroenvanlangen it is just a contraint for now.Programme
L
2

The problem is that your UserAccessCheckExpression() method is returning an Expression while the Any() method is expecting a boolean.

Now, you can get your code to compile by compiling the Expression and invoking the method (using UserAccessCheckExpression<TaskUser>(x.User).Compile().Invoke(x.User)) but that would obviously fail on runtime because Linq-to-Entities wouldn't be able to translate your Any() to a store query as it no longer contains an Expression.

LinqKit is aiming to solve this problem using its own Invoke extension method that while letting your code compile, will make sure your Expression will get replaced back to its original form using another extension method named AsExpandable() that is extending the entity set.

Try this:

using LinqKit.Extensions;

return tasks
      .AsExpandable()
      .Where(t => t.TaskUsers.Any(
                       x => UserAccessCheckExpression<TaskUser>(x.User).Invoke(x)
                            && x.SomeBool == true));

More on LinqKit

Llano answered 20/4, 2016 at 7:36 Comment(2)
Out of interest, does the extension method thus compile the expression only once?Montes
@Kolky, Internally, it calls expr.Compile().Invoke(arg1) but it never really gets executed. Its only purpose is letting the Expression be represented as Func at compile time while making sure that at runtime the original Expression will be used.Llano
O
0

Yeah, so, you can't do that. There's a difference between an Expression<> and a Func<>. You're trying to use the UserAccessCheckExpression as a func. I'm not sure what you're trying to do, but you can compile it to a func and then use it sorta like you are:

var expr = UserAccessCheckExpression<TaskUser>(x.User);
var func = expr.Compile();
// Later use it like ...
var result = func();

But I expect you're using this with EF or Linq2Sql? That being the case you'll need to rewrite the expression. It can be done by hand (not easy) or, better, use a tool like PredicateBuilder.

Osborne answered 20/4, 2016 at 7:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.