2014-10-16 4 views
1

Моя проблема связана с сложными многоразовыми логическими спецификациями.Передайте выражение в другое выражение как параметр

У меня есть следующие Expression:

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

И мне нужно, чтобы получить возможность:

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

Так как я может передать Creator из Estate объекта в выражение, которое принимает на User и, наконец, использовать окончательное выражение в linq to sql?

Проблема заключается в том: WithParameter

EDIT:

Это один работает, но его не эффективно:

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

И следующий не ответ, потому что Invoke method не может быть переведены в хранилище:

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(); 

Но мне нужно что-то, что можно перевести в Store Expression Возможно, окончательное решение разлагается, а затем перекомпоновывает выражение, и если да, то как я могу его инкапсулировать для повторного использования в подобных ситуациях? (Как это то, что я пытаюсь сделать)

+0

Если вы пытаетесь создать фильтры для повторного использования, вы можете посмотреть на https://entityrestsdk.codeplex.com/, в этом SDK, мы устанавливаем выражения сразу, однако, как вы это делаете, будет сложно отлаживать и поддерживать, поскольку вы не можете легко визуализировать свои фильтры. Поскольку эти правила не будут переписываться динамически, они остаются постоянными на протяжении всей жизни, поэтому лучше писать, поскольку они кажутся простыми в использовании и тестировании. –

ответ

1

Вам нужно будет переписать выражение. Я сделал метод расширения для WithParameter.

Сначала нам понадобится занять класс ParameterVisitor из этого ответа https://stackoverflow.com/a/5431309/1798889 и немного подкорректировать его. Мы не хотим просто заменять параметр в одном выражении другим параметром, который мы хотим удалить, и заменить его новым выражением.

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; 
    } 
} 

Уведомление: Я изменил параметр To только как выражение, а не выражение ParameterExpression.

Теперь мы собираемся создать 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); 
    } 
} 

В этом мы берем селектор, как мы знаем, что свойство, которое мы хотим и заменить параметр Expression> с этим свойством.

Вы можете использовать его как

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

Слово предупреждения я не знаю, Linq к SQL и не проверить его против этого, и я не проверял на IQueryable но он работает для IEnumberable. Я не понимаю, почему это не сработает, так как отладочное представление двух выражений в конце выглядит одинаково.

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

одно и то же выражение, как дерево

userExp.WithParameter(estate => estate.CreatorUser) 
+0

Ты мой герой – Mohsen

1

Одним из вариантов решения может быть для запроса первых пользователей и выбрать все имения:

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(); 
+0

Моя основная проблема заключается в передаче выражения, и это не ответ на мой вопрос, у меня более сложная проблема, и это лишь часть этого (мне нужно повторно использовать спецификации). – Mohsen

Смежные вопросы