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.

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Daemon025
  • 87
  • 1
  • 11
  • You're going to have to manually construct the Lambda somewhat, something similar to this: http://stackoverflow.com/questions/4001082/manually-build-linq-expression-for-x-x-child-itemtocompare-child I don't have enough time to give a full answer. – Jesus is Lord Apr 20 '16 at 07:08
  • 1
    You will need to use the solution from [here](http://stackoverflow.com/questions/457316/combining-two-expressions-expressionfunct-bool). And then you'd write something like `t.TaskUsers.Any(UserAccessCheckExpression)` – Rob Apr 20 '16 at 07:09
  • What is your Linq provider? Is it Entity Framework? What's `IsAdmin` and `CurrentUserId` in your expression? – haim770 Apr 20 '16 at 07:19
  • @haim770, it is EF. They are properties of the other class. – Daemon025 Apr 20 '16 at 07:23
  • @jeroenvanlangen it is just a contraint for now. – Daemon025 Apr 20 '16 at 07:24

2 Answers2

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

haim770
  • 48,394
  • 7
  • 105
  • 133
  • Out of interest, does the extension method thus compile the expression only once? – Kolky Apr 20 '16 at 07:39
  • 1
    @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. – haim770 Apr 20 '16 at 07:50
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.

xanadont
  • 7,493
  • 6
  • 36
  • 49