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)
You will need to rewrite the expression. I wrote 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
extension method:
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<Func<User, bool>> 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 IEnumerable
. 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)