可能的原因
一般来说,XPO 在大多数情况下都不支持“自由连接”。它被明确地写在他们的知识库或问答网站的某个地方。如果我再次点击那篇文章,我会附上它的链接。
在您的原始代码示例中,您试图在 INNER 查询中执行“自由联接”。'WHERE' 子句是按键连接的,可能是导航的,但它还包含一个由“修饰符”组成的额外过滤器,这可能不是关系定义的一部分。
此外,该查询尝试IQueryable<PRICE> o
在内部查询中重用 - 实际上似乎受到 XPO 的支持 - 但如果您将任何预过滤('where')添加到顶级 'o',它再次中断的可能性很高。
文档声明 XPO 仅支持导航连接,沿着由 XPObjects 中定义的属性和/或 xpcollections 形成的路径。这适用于整个 XPO,所以 XPQuery 也是如此。所有其他类型的连接都称为“自由连接”,并且:
- XPO 通过获取相关对象、从中提取键值并将查询重写为具有一系列部分查询的多次往返,这些部分查询使用 WHERE-id-IN-(@p0,@p1,@p2, ...) - 但这只发生在一些最简单的情况下
- 或“不完全支持”,这意味着它们会抛出异常并要求您手动拆分查询或改写它
可能的直接解决方案
如果 ITEM_ID 是 PRICE 类中的关系和 XPCollection,那么您可以重写查询,以便它获取 PRICE对象,然后构建结果对象并使用 PRICE对象的属性初始化其字段。就像是:
return new ItemPricesViewModel()
{
Source = (from o in XpoSession.Query<PRICE>().AsEnumerable()
select new ItemPriceViewModel()
{
ID = o.ITEM_ID.ITEM_ID,
ItemCod = o.ITEM_ID.ITEM_COD,
....
ItemModifierID = o.ITEM_MODIFIER_ID.ITEM_MODIFIER_ID,
ItemPrices = (from d in o
where d.ITEM_ID.ITEM_ID == ....
select new Price()
.... .... ....
};
请注意中断查询并确保首先获取 PRICE 对象而不是仅仅尝试翻译查询的“AsEnumerable”。这很可能会“正常工作”。
此外,将查询拆分为显式阶段有时有助于 XPO 对其进行分析:
return new ItemPricesViewModel()
{
Source = (from o in XpoSession.Query<PRICE>()
select new
{
id = o.ITEM_ID.ITEM_ID,
itemcod = o.ITEM_ID.ITEM_COD,
....
}
).AsEnumerable()
.Select(temp =>
select new ItemPriceViewModel()
{
ID = temp.id
ItemCod = temp.itemcod,
....
ItemPrices = (from d in XpoSession.Query<PRICE>()
where d.ITEM_ID.ITEM_ID == ....
select new Price()
.... .... ....
};
在这里,请注意,我首先从服务器获取项目数据,然后在“客户端”上构建项目,然后构建所需的分组。请注意,我不能再引用该变量o
了。在这些精确的案例和示例中,不出所料,第二个(拆分的)可能会比第一个慢,因为它会获取所有 PRICE,然后通过额外的查询重新获取分组,而第一个只会获取所有 PRICE 和然后将根据已经获取的 PRICE 计算内存中的组。这不是我懒惰的副作用,但在重写 LINQ 查询时这是一个常见的陷阱,所以我将它作为警告包括在内:)
对于您的情况,不建议使用这两个代码示例,因为它们的性能可能很差,特别是如果您的表中有很多 PRICE,这很有可能。我将它们包括在内,仅作为一个示例,说明如何重写查询以简化其结构,以便 XPO 可以在不窒息的情况下吃掉它。但是,您必须非常小心并注意小细节,因为您很容易破坏性能。
观察和真正的解决方案
但是,值得注意的是,它们并不比您的原始查询差多少。它本身就很差,因为它试图从表中执行接近 O(N^2) 行的操作,只是为了按“ITEM_ID”对 te 行进行分组,然后将结果格式化为单独的对象。如果做得好,它将类似于 O(N lg N)+O(N),因此无论是否支持,您使用 GroupBy 的替代尝试肯定是一个更好的方法,我真的很高兴您自己找到了它.
很多时候,当您像我在上面所做的那样尝试拆分/简化 XPQuery 表达式时,您会隐含地重新考虑问题并找到一种更简单、更简单的方法来表达最初不受支持或刚刚崩溃的查询。
不幸的是,您的查询实际上非常简单。对于一个不能“只是改写”的非常复杂的查询,拆分成多个阶段并使一些连接过滤器在“客户端”工作是不可避免的。但同样,在 XPCollections 或 XPViews 上使用 CritieriaOperators 也是不可能的,所以要么我们必须忍受它或使用简单的直接手工 SQL ..
边注:
整个 XPO 存在“自由连接”的问题,它们不仅在 XPQuery 中“不完全支持”,而且在 XPCollection、XPView、CriteriaOperators 等中也没有太多支持。但是,值得注意的是,至少在 DX11 的“我的版本”中,XPQuery 的 LINQ 支持非常差。
我遇到过很多正确的 LINQ 查询是:
- 抛出“NotSupportedException”,主要是在 FreeJoins 中,但也经常使用复杂的 GroupBy 或 Select-with-Projection、GroupJoin 和许多其他 - 有时甚至 Distinct(!) 似乎出现故障
- 在一些适当的类型转换中抛出“NullReferenceExceptions”(XPO 试图解释一个将 INT/NULL 作为对象的列..),我经常不得不编写一些完全奇怪和人为的表达式,
foo!=null && foo.bar!=123
而不是foo = 123
尽管 'foo' 是一个public int Foo {get;set;}
,这一切都是因为 DX 无法正确处理数据库中的 NULL(因为 XPO 为该属性创建了可为空的 INT 列……但这是另一回事)
- 从其他构造中抛出其他随机 ArgumentException/InvalidOperation 异常
甚至不正确地分析查询结构,例如这个通常是有效的:
session.Query<ABC>()
.Where( abc => abc.foo == "somefilter" )
.Select( abc => new { first = abc, b = abc } )
.ToArray();
但像这样的事情通常会抛出:
session.Query<ABC>()
.Select( abc => new { first = abc, b = abc } )
.Where ( temp => temp.first.foo == "somefilter" )
.ToArray();
但这一个是有效的:
session.Query<ABC>()
.Select( abc => new { first = abc, b = abc } )
.ToArray()
.Where ( temp => temp.first.foo == "somefilter" )
.ToArray();
中间代码示例通常抛出一个错误,表明 XPO 层试图在 ABC 类中查找“.first.foo”路径,这显然是错误的,因为此时元素类型ABC
不再是匿名类,而是a'
匿名类。
免责声明
我已经注意到这一点,但让我重复一遍:这些观察结果与 DX11 相关,而且很可能更早。我不知道在 DX12 及更高版本中修复了什么(如果有的话!)。