Объединить несколько выражений Linq [дубликаты]

2

Я занимаюсь рефакторингом некоторого кода, пытаясь сделать его более самодокументирующимся. В текущем коде есть запрос по службе OData, которая выглядит так:

return context.MessageLog.Where
(
    x => 
    (
        x.Status == MessageStatus.Success 
        || x.Status == MessageStatus.Failure
    ) 
    && x.Direction == MessageDirection.Inbound 
    && x.ResponseDate == new DateTimeOffset(new DateTime(1900, 01, 01))
);

Я надеюсь изменить это, чтобы использовать выражения Linq.
Я мог бы переместить всю логику в одно выражение и запустить код context.MessageLog.Where(MessageIsPendingResponse);. Однако я хотел бы создавать выражения для разных условий: MessageIsProcessed (т.е. Теперь при успехе или состоянии отказа), MessageIsInbound и ResponseNotYetSent (дата ответа равна нулю). Я мог бы объединить их с несколькими примерами, такими как:

return context.MessageLog
    .Where(MessageLogExpression.MessageIsProcessed)
    .Where(MessageLogExpression.MessageIsInbound)
    .Where(MessageLogExpression.ResponseNotYetSent);

(MessageLogExpression является классом, который я использую для хранения этих предопределенных выражений).

Вопрос 1

Является ли это наилучшим способом комбинирования предложений или он сначала фильтрует риски в неправильном поле (например, Linq объединяет все условия в один запрос и позволяет механизму запросов (в терминах SQL) определять наилучшее выполнение план, или мы вынуждаем его сначала фильтровать поле состояния?

Вопрос 2

Вышеприведенное замечательно для сценариев, в которых мы имеем AND, соединяющие наши выражения; но как мы будем делать OR? Я предполагаю, что есть способ сочетать их, но не мог найти ничего очевидного. Я подозреваю, что подобное существует?

return context.MessageLog.Where(new OrExpression(MessageIsSuccess,MessageIsFailure));

Вопрос 3

Есть ли хороший способ комбинировать выражения в другом выражении. например что-то вроде приведенного ниже кода (только для компиляции версии)?

public static Expression<Func<MessageLogRecord, bool>> MessageIsPendingResponse
{
    get
    {
        Expression<Func<MessageLogRecord, bool>> expr = x => 
            MessageIsProcessed(x) 
            && MessageIsInbound(x) 
            && ResponseNotYetSent(x);
        return expr;
    }
}

Приложение: Код для этих выражений, описанных выше:

public class MessageLogExpression
{
    public static Expression<Func<MessageLogRecord, bool>> MessageIsProcessed
    {
        get
        {
            Expression<Func<MessageLogRecord, bool>> expr = x => 
            (
                x.Status == MessageStatus.Success 
                || x.Status == MessageStatus.Failure
            );
            return expr;
        }
    }
    public static Expression<Func<MessageLogRecord, bool>> MessageIsInbound
    {
        get
        {
            Expression<Func<MessageLogRecord, bool>> expr = x => 
                x.Direction == MessageDirection.Inbound;
            return expr;
        }
    }
    static readonly DateTimeOffset NullDate = new DateTimeOffset(new DateTime(1900, 01, 01));
    public static Expression<Func<MessageLogRecord, bool>> ResponseNotYetSent
    {
        get
        {
            Expression<Func<MessageLogRecord, bool>> expr = x => 
                x.ResponseDate == NullDate; //todo: test if this works or if I have to define the value within the expression
            return expr;
        }
    }
}
Теги:
linq
odata
linq-expressions

1 ответ

4
Лучший ответ

О # 1 - в терминах EF с Linq to Entities. Я ожидал бы, что EF создаст тот же запрос, независимо от того, разделил ли я его на несколько условий, или все в одном. Даже если этого не произойдет - база данных SQL может по-прежнему выдавать один и тот же план выполнения запросов, поскольку у него есть собственный оптимизатор.

По другим вопросам мы используем вспомогательный класс, основанный на этом сообщении в блоге: http://www.albahari.com/nutshell/predicatebuilder.aspx

public static class PredicateBuilder
{
  public static Expression<Func<T, bool>> True<T>()
  {
    return (Expression<Func<T, bool>>) (input => true);
  }

  public static Expression<Func<T, bool>> False<T>()
  {
    return (Expression<Func<T, bool>>) (input => false);
  }

  public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
  {
    InvocationExpression invocationExpression = Expression.Invoke((Expression) expression2, expression1.Parameters.Cast<Expression>());
    return Expression.Lambda<Func<T, bool>>((Expression) Expression.OrElse(expression1.Body, (Expression) invocationExpression), (IEnumerable<ParameterExpression>) expression1.Parameters);
  }

  public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
  {
    InvocationExpression invocationExpression = Expression.Invoke((Expression) expression2, expression1.Parameters.Cast<Expression>());
    return Expression.Lambda<Func<T, bool>>((Expression) Expression.AndAlso(expression1.Body, (Expression) invocationExpression), (IEnumerable<ParameterExpression>) expression1.Parameters);
  }
}

Это работает очень хорошо, так как это помогает легко сочетать выражения. Вы можете начать с PredicateBuilder.True<YourEntityHere>().And(... expression1 ...).And(...)..., если вы хотите объединить выражения OR, которые вы делаете аналогичным образом, начиная с false: PredicateBuilder.False<YourEntityHere>().Or(...)...

Это означает, что для Q3 вы можете:

public static Expression<Func<MessageLogRecord, bool>> MessageIsPendingResponse
{
    get
    {
        Expression<Func<CCI_Int_ExportLog, bool>> expr = PredicateBuilder.True<MessageLogRecord>()
            .And(MessageIsProcessed)
            .And(MessageIsInbound)
            .And(ResponseNotYetSent)
        ;
        return expr;
    }
}
  • 0
    Это потрясающе; благодарю вас. Через несколько минут после публикации я наткнулся на методы выражения OOTB And и Or ( msdn.microsoft.com/en-us/library/… ) и подумал, что решил свой собственный вопрос; потом понял, что они не были общими, поэтому не работали для меня ... Ваше решение дает мне то, что они сделали, но таким образом, что это работает; благодарю вас.
  • 0
    @JohnLBevan FYI Я нашел оригинальное сообщение в блоге, в котором мы нашли решение - albahari.com/nutshell/predicatebuilder.aspx - может быть, есть что-то еще написанное об этом
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню