PredicateBuilder嵌套OR子句,导致大型谓词的嵌套问题太深

ean*_*533 4 c# predicatebuilder ormlite-servicestack

简介:我将PredicateBuilderOr()几个表达式一起使用,然后将该组合表达式发送到OrmLiteSelect()方法.但是,生成的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 ...
Run Code Online (Sandbox Code Playgroud)

由于我正在使用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);
Run Code Online (Sandbox Code Playgroud)

如果我在列表中使用3个Foos执行此操作,则生成的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)))
Run Code Online (Sandbox Code Playgroud)

请注意主要括号?PredicateBuilder在添加下一个表达式之前,不断地将现有表达式括起来,以便x- > (x) or y- > ((x) or y) or z等.

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

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

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

das*_*ght 7

由于SQL不会使ORs 短路,因此您可以转换看起来像这样的表达式树

OR
 \
 OR
  \
  OR
   \
   OR
Run Code Online (Sandbox Code Playgroud)

到一个如下所示的表达式树:

        OR
      /    \
     /      \
    /        \
   OR        OR
 /   \     /    \
OR   OR   OR    OR
Run Code Online (Sandbox Code Playgroud)

这只是一种解决方法:理想情况下,框架应该能够处理这种情况.

构建这样的树的一种方法是将列表递归地分成两半,OR递归地从每一半构造一个" -tree",然后将两个" OR-tree"与另一个组合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;
}
Run Code Online (Sandbox Code Playgroud)

  • @Tyrsius你不能用.NET LINQ表达式的当前实现来做到这一点,因为`OR`是作为二元运算符实现的,而不是`N`-ary.这就是为什么在一个两个链中,或者一个子树将最终进入另一个子树内,无论你走哪条路都迫使嵌套.我在几年前在LINQ Expressions上编写的一个小框架中遇到了这个问题.我最终以上述方式解决问题,解决了手头的问题. (2认同)