ormlite-servicestack,predicatebuilder,C#,ormlite Servicestack,Predicatebuilder" /> ormlite-servicestack,predicatebuilder,C#,ormlite Servicestack,Predicatebuilder" />

C# PredicateBuilder嵌套或子句,导致大型谓词嵌套太深

C# PredicateBuilder嵌套或子句,导致大型谓词嵌套太深,c#,ormlite-servicestack,predicatebuilder,C#,ormlite Servicestack,Predicatebuilder,摘要:我正在使用to或()将多个表达式组合在一起,然后将该组合表达式发送到的Select()方法。但是,生成的SQL有一个WHERE子句,其中嵌套了太多的括号,以致SQL Server抛出错误。我能做些什么来解决这个问题 详细信息:我有一个表Foo,有两列,Bar和Baz。如果我有一组Bar/Baz值,并且我想找到所有匹配的行,那么我可能(例如)发出以下SQL: SELECT * FROM Foo WHERE (Bar=1 AND Baz=1) OR (Bar=2 AND Baz=3) OR .

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

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

SELECT * FROM Foo WHERE (Bar=1 AND Baz=1) OR (Bar=2 AND Baz=3) OR ...
因为我使用了,所以我使用生成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);
注意前面的括号?在添加下一个表达式之前,
PredicateBuilder
会不断地将现有表达式括起来,以便
x
->
(x)或y
->
((x)或y)或z
,等等

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

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


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

因为SQL不会短路
s,所以可以转换如下所示的表达式树

OR
 \
 OR
  \
  OR
   \
   OR
到如下所示的表达式树:

        OR
      /    \
     /      \
    /        \
   OR        OR
 /   \     /    \
OR   OR   OR    OR
这只是一种变通方法:理想情况下,框架应该能够处理类似的情况

这样构造树的一种方法是将列表递归地分成两半,从每一半递归地构造一个“
-tree”,然后将这两个“
-tree”与另一个“
相结合:

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;
}
谓词ToOrTree(列表列表){
如果(傻瓜数>2){
var firstHalf=傻瓜数/2;
var lhs=ToOrTree(愚人主义者.Take(上半部分).ToList());
var rhs=ToOrTree(愚人主义者.Skip(上半部分).ToList());
返回左侧或(右侧);
}
谓词res=PredicateBuilder.Create(
foo=>doulist[0]。Bar==foo.Bar&&doulist[0]。Baz==foo.Baz
);
如果(傻瓜数==2){
res=res.或(
foo=>doulist[1]。Bar==foo.Bar&&doulist[1]。Baz==foo.Baz
);
}
返回res;
}

但这不会完全停止嵌套,它只会管理嵌套。对于足够大的集合,如果列表足够大,您可能仍然会遇到嵌套太深的
Sql
错误。@Tyrsius从
N
嵌套级别到
Log2(N)
是一个相当大的跳跃,但是:如果原始解决方案需要30个嵌套级别才能达到极限,解决方案需要超过10亿项才能实现!诚然,我在学术上对消除嵌套很感兴趣。@Tyrsius您不能用当前的.NET LINQ表达式实现嵌套,因为
是作为二进制运算符实现的,而不是作为
N
-ary运算符实现的。这就是为什么在一个由两个
组成的链中,一个子树最终会在另一个子树中结束,无论你走哪条路,都会强制嵌套。我在几年前在LINQ表达式之上编写的一个小框架中面对这个问题。我最终以我上面描述的方式解决了这个问题,解决了眼前的问题。考虑到局限性,这是一个创造性的答案。完美地解决了我的问题。SQL Server的嵌套限制是32,因此我必须避免一次查询超过2^32个实体(糟糕,糟糕)。
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;
}