我正在重构一些代码,试图让它更自我记录。当前代码对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
(响应日期为空)。
我可以将它们与多个where语句结合起来:
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;
}
}
}
答案 0 :(得分:4)
关于#1 - 就使用Linq to Entities的EF而言,我希望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;
}
}