谓词生成器嵌套OR子句,导致大型谓词嵌套过深

本文关键字:嵌套 谓词 大型 子句 OR | 更新日期: 2023-09-27 18:23:55

摘要:我正在使用PredicateBuilder将多个表达式一起Or(),然后将组合后的表达式发送到OrmLite的Select()方法。但是,生成的SQL有一个WHERE子句,该子句中嵌套的括号太多,以至于SQL Server会抛出错误。我能做些什么来解决这个问题?

详细信息:我有一个表Foo,它有两列,BarBaz。如果我有一个Bar/Baz值的集合,并且我想找到所有匹配的行,那么我可能(例如)发出以下SQL:

SELECT * FROM Foo WHERE (Bar=1 AND Baz=1) OR (Bar=2 AND Baz=3) OR ...

由于我使用的是OrmLite,所以我使用PredicateBuilder为我生成一个where子句:

var predicate = PredicateBuilder.False<Foo>();
foreach (var nextFoo in fooList)
    predicate = predicate.Or(foo => nextFoo.Bar == foo.Bar && 
                                    nextFoo.Baz == foo.Baz);
Db.Select(predicate);

如果我用列表中的3Foo执行此操作,生成的SQL看起来是这样的(为了简洁起见,已清理,但有意保留在一行以表明观点):

SELECT Bar, Baz FROM Foo WHERE ((((1=0) OR ((1=Bar) AND (1=Baz))) OR ((2=Bar) AND (3=Baz))) OR ((2=Bar) AND (7=Baz)))

注意到前面的括号了吗?PredicateBuilder在添加下一个表达式之前不断地将现有表达式加括号,从而使x->(x) or y->((x) or y) or z

我的问题:当我有几十或几百个项目要查找时,生成的SQL有几十或数百个嵌套的括号,SQL Server会用SqlException:将其踢回

SQL语句的某些部分嵌套太深。重写查询或将其分解为更小的查询。

那么我该怎么办呢?如果我想避免嵌套异常,我需要对生成的SQL的WHERE子句进行扁平化(就像上面的示例查询一样)。我知道我可以动态生成自己的SQL,并将其发送到OrmLite的SqlList方法,但被迫这样做会使OrmLite失去一半的值。

谓词生成器嵌套OR子句,导致大型谓词嵌套过深

由于SQL不会使ORs短路,因此可以转换如下所示的表达式树

OR
 '
 OR
  '
  OR
   '
   OR

到一个看起来像这样的表达式树:

        OR
      /    '
     /      '
    /        '
   OR        OR
 /   '     /    '
OR   OR   OR    OR

这只是一个变通办法:理想情况下,框架应该能够处理这样的情况。

构造这样的树的一种方法是递归地将列表一分为二,递归地从每一半中构造一个"OR-树",然后将两个"OR-树"与另一个OR:组合

Predicate ToOrTree(List<Foo> fooList) {
    if (fooList.Count > 2) {
        var firstHalf = fooList.Count / 2;
        var lhs = ToOrTree(fooList.Take(firstHalf).ToList());
        var rhs = ToOrTree(fooList.Skip(firstHalf).ToList());
        return lhs.Or(rhs);
    }
    Predicate res = PredicateBuilder.Create<Foo>(
        foo => fooList[0].Bar == foo.Bar &&  fooList[0].Baz == foo.Baz
    );
    if (fooList.Count == 2) {
        res = res.Or(
            foo => fooList[1].Bar == foo.Bar &&  fooList[1].Baz == foo.Baz
        );
    }
    return res;
}