如何获得 2 种 IQueryable 方法来实现析取

本文关键字:方法 实现 何获得 IQueryable | 更新日期: 2023-09-27 18:36:31

如果这是一个简单的问题,则表示赞赏;假设我有一个具有 2 种方法的 EF 查询对象:

public static IQueryable<Animal> FourLegged(this IQueryable<Animal> query)
{
    return query.Where(r => r.NumberOfLegs == 4);
}
public static IQueryable<Animal> WithoutTail(this IQueryable<Animal> query)
{
    return query.Where(r => !r.HasTail);
}

现在,在我的服务层中,要获得四足且没有尾巴的动物,我可以做到:

_animalService.Query()
.FourLegged()
.WithoutTail();

这将导致如下 sql 查询:

select * from Animal where NumberOfLegs = 4 AND HasTail = 0

如何将 2 种查询方法与 OR 一起使用?我想要四条腿或没有尾巴的动物

select * from Animal where NumberOfLegs = 4 OR HasTail = 0

在 Nhibernate 中,我会使用一个简单的析取,但我在 EF 中找不到它。

谢谢


解决方案:我最终使用了此答案中提到的 LinqKit 谓词。它工作得很好,我也可以重用谓词。

如何获得 2 种 IQueryable<T> 方法来实现析取

当你已经调用query.Where()时,你真的无法做到这一点。那里的谓词已经在IQueryable中收集,并且它们都由AND组合。

为了获得OR您必须进行单个query.Where()调用并传递一个涵盖各种析取谓词的表达式。

在您的情况下,组合谓词将如下所示:

query.Where(r => (r.NumberOfLegs == 4) || (!r.HasTail))

为了使其更加动态,您基本上需要构建一个自定义表达式组合函数,其工作原理如下:

Expression<Func<Animal, bool>> fourLegged = r => r.NumberOfLegs == 4;
Expression<Func<Animal, bool>> withoutTail = r => !r.HasTail;
query = query.Where(CombineDisjunctivePredicates(fourLegged, withoutTail));

因此,让我们编写该CombineDisjunctivePredicates函数:

public Expression<Func<T, bool>> CombineDisjunctivePredicates<T>(params Expression<Func<T, bool>>[] predicates)
{
    Expression current = Expression.Constant(false);
    ParameterExpression param = Expression.Parameter(typeof(T), "obj");
    foreach (var predicate in predicates)
    {
        var visitor = new ReplaceExpressionVisitor(predicate.Parameters[0], param);
        current = Expression.Or(current, visitor.Visit(predicate.Body));
    }
    return Expression.Lambda<Func<T, bool>>(current, param);
}

这基本上需要许多谓词,并通过使用布尔 OR 组合表达式主体来组合它们。由于我们正在组合可能具有不同表达式参数的不同表达式,因此我们还需要确保使用公共参数替换表达式主体中的所有表达式参数引用。我们使用一个简单的ReplaceExpressionVisitor来做到这一点,很容易实现,如下所示:

public class ReplaceExpressionVisitor : ExpressionVisitor
{
    private readonly Expression _original;
    private readonly Expression _replacement;
    public ReplaceExpressionVisitor(Expression original, Expression replacement)
    {
        _original = original;
        _replacement = replacement;
    }
    public override Expression Visit(Expression node)
    {
        return node == _original ? _replacement : base.Visit(node);
    }
}

这就是组合谓词所需的全部内容。您只需要确保现在更改方法,以便它们不会自己调用query.Where而是返回Expression<Func<Animal, bool>>