合并多个Linq表达式

5

我正在重构一些代码,试图使其更加自说明。当前的代码有一个针对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(即现在处于成功或失败状态),MessageIsInboundResponseNotYetSent(响应日期为空)。 我可以像下面这样将它们与多个where语句结合起来:

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

问题三

有没有一种好的方法可以在另一个表达式定义中组合表达式,例如下面的代码(只是可以编译的版本)?

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个回答

8
关于第一个问题-就使用Linq to Entities来说,我认为无论是将查询条件分成多个还是将所有条件放在一起,EF都会创建相同的查询。即使不是这样,由于SQL数据库有自己的优化器,可能仍然会产生相同的查询执行计划。
关于其他问题,我们正在使用一个基于以下博客文章的帮助类:http://www.albahari.com/nutshell/predicatebuilder.aspx。请注意,保留了HTML标签。
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);
  }
}

这样做非常好,因为它可以帮助您轻松地组合表达式。如果您想要合并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;
    }
}

太棒了,谢谢。发布后不久,我发现了OOTB的“And”和“Or”表达式方法(https://msdn.microsoft.com/en-us/library/system.linq.expressions.expression_methods(v=vs.110).aspx),以为我解决了自己的问题;然后意识到它们不是通用的,所以对我没用...你的解决方案给了我他们的效果,但是可以工作;谢谢。 - JohnLBevan
@JohnLBevan FYI,我找到了我们使用解决方案的原始博客文章- http://www.albahari.com/nutshell/predicatebuilder.aspx - 或许这里面还有更多相关的内容。 - peter
谢谢提供链接。顺便说一下,关于我上次提到的构建表达式的部分(即不仅仅将它们应用于IQueryable),我假设我在问题末尾添加的代码是正确的(如果正确,则是答案的一部分;如果错误,请见谅)。 - JohnLBevan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接