Я занимаюсь рефакторингом некоторого кода, пытаясь сделать его более самодокументирующимся. В текущем коде есть запрос по службе 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
является классом, который я использую для хранения этих предопределенных выражений).
Является ли это наилучшим способом комбинирования предложений или он сначала фильтрует риски в неправильном поле (например, Linq объединяет все условия в один запрос и позволяет механизму запросов (в терминах SQL) определять наилучшее выполнение план, или мы вынуждаем его сначала фильтровать поле состояния?
Вышеприведенное замечательно для сценариев, в которых мы имеем AND
, соединяющие наши выражения; но как мы будем делать OR
?
Я предполагаю, что есть способ сочетать их, но не мог найти ничего очевидного. Я подозреваю, что подобное существует?
return context.MessageLog.Where(new OrExpression(MessageIsSuccess,MessageIsFailure));
Есть ли хороший способ комбинировать выражения в другом выражении. например что-то вроде приведенного ниже кода (только для компиляции версии)?
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;
}
}
}
О # 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;
}
}
And
иOr
( msdn.microsoft.com/en-us/library/… ) и подумал, что решил свой собственный вопрос; потом понял, что они не были общими, поэтому не работали для меня ... Ваше решение дает мне то, что они сделали, но таким образом, что это работает; благодарю вас.