结合多个Linq表达式

时间:2018-03-02 17:48:34

标签: c# linq odata linq-expressions

我正在重构一些代码,试图让它更自我记录。当前代码对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));

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

1 个答案:

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