2

My problem derived from complex reusable logical specifications.

I have the following Expression:

Expression<Func<User, bool>> userExp =
            user => user.UserInRoles.Any(userInRole => userInRole.RoleId == SystemRoles.SysAdmin.Id);

And I need to get the ability of:

new CommonContext().Set<Estate>().Where(estate => userExp.WithParameter(estate.CreatorUser)).ToList();

So how can I pass the Creator of Estate entity into the expression which accepts a User entity and finally use the final expression in linq to sql ?

The problem is : WithParameter

EDIT:

This one works but its not efficient:

new CommonContext().Set<Estate>().ToList().Where(estate => userExp.Compile()(estate.CreatorUser)).ToList()

And the following is not the answer because the Invoke method can not be translated to store expression:

Expression<Func<User, bool>> userExp =
            user => user.UserInRoles.Any(userInRole => userInRole.RoleId == SystemRoles.SysAdmin.Id);

Expression<Func<Estate, User>> propAccessor = estate => estate.ApprovedByUser;


var estateParam = Expression.Parameter(typeof(Estate));
var userParam = Expression.Invoke(propAccessor, estateParam);
var translatedExp = Expression.Invoke(userExp, userParam);
var result = (Expression<Func<Estate, bool>>)Expression.Lambda(translatedExp, estateParam);

var exceptionProvider = new CommonContext().Set<Estate>().Where(result).ToList();

But I need something which can be translated into Store Expression maybe the final solution is decomposing and then recomposing the expression , and if so ,, how can I encapsulate it for reusing it in similar situations? (as this is what i'm trying to do)

casperOne
  • 73,706
  • 19
  • 184
  • 253
Mohsen
  • 4,000
  • 8
  • 42
  • 73
  • If you are trying to create filters to reuse, you can have a look at https://entityrestsdk.codeplex.com/ , in this SDK, we are setting expressions all at once, however, the way you are doing it, it will be difficult to debug and maintain as you cannot easily visualize your filters. As these rules will not be rewritten dynamically, they stay constant throughout the life, so its better to write as they appear easy to use and test. – Akash Kava Oct 24 '14 at 05:54

2 Answers2

1

One alternative solution can be to query first the users and select all the estates:

Expression<Func<User, bool>> userExp =
            user => user.UserInRoles.Any(userInRole => userInRole.RoleId == SystemRoles.SysAdmin.Id);

new CommonContext().Set<User>().Where(userExp).SelectMany(u => u.Estates).ToList();
Peter Kiss
  • 9,309
  • 2
  • 23
  • 38
  • My Main problem is passing the expression , and this is not the answer to my question , i'm in more complex problem and this is just a part of that (I need to reuse specifications). – Mohsen Oct 19 '14 at 09:13
1

You will need to rewrite the expression. I made an extension method for WithParameter.

First we are going to need to borrow the ParameterVisitor class from this answer https://stackoverflow.com/a/5431309/1798889 and tweak it a bit. We don't want to just replace the parameter in one expression with a different parameter we want to remove the parameter and replace it with a new expression.

public class ParameterVisitor : ExpressionVisitor
{
    private readonly ReadOnlyCollection<ParameterExpression> from;
    // Changed from ReadOnlyCollection of ParameterExpression to Expression 
    private readonly Expression[] to;

    public ParameterVisitor(ReadOnlyCollection<ParameterExpression> from, params Expression[] to)
    {
        if (from == null) throw new ArgumentNullException("from");
        if (to == null) throw new ArgumentNullException("to");
        if (from.Count != to.Length)
            throw new InvalidOperationException(
                "Parameter lengths must match");
        this.from = from;
        this.to = to;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        for (int i = 0; i < from.Count; i++)
        {
            if (node == from[i]) return to[i];
        }
        return node;
    }
}

Notice I changed the To parameter to just be an expression and not a ParameterExpression.

Now we are going to create the WithParameter

public static class QueryableExtensions
{
    public static Expression<Func<TResult, bool>> WithParameter<TResult, TSource>(this Expression<Func<TSource, bool>> source, Expression<Func<TResult, TSource>> selector)
    {
        // Replace parameter with body of selector
        var replaceParameter = new ParameterVisitor(source.Parameters, selector.Body);
        // This will be the new body of the expression
        var newExpressionBody = replaceParameter.Visit(source.Body);
        return Expression.Lambda<Func<TResult, bool>>(newExpressionBody, selector.Parameters);
    }
}

In this we take the selector of how we know what property we want and replace the parameter of the Expression> with that property.

You use it like

new CommonContext().Set<Estate>().Where(userExp.WithParameter<Estate, User>(estate => estate.CreatorUser)).ToList();

Word of warning I don't know Linq to SQL and didn't test it against that and I didn't test it against IQueryable but it does work for IEnumberable. I don't see why it wouldn't work as the debug view of the two expression at the end look the same.

Expression<Func<Estate, bool>> estateExp = estate => estate.CreatorUser.UserInRoles.Any(userInRole => userInRole.RoleId ==  SystemRoles.SysAdmin.Id);

is the same expression tree as

userExp.WithParameter(estate => estate.CreatorUser)
Community
  • 1
  • 1
CharlesNRice
  • 3,219
  • 1
  • 16
  • 25