Combining expression trees

2019-01-29 00:18发布

问题:

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.

回答1:

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



回答2:

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.