无法将节点“值”格式化为SQL以执行

时间:2011-05-04 21:38:33

标签: linq-to-sql

我偶然发现了一个非常奇怪的LINQ to SQL行为/ bug,我无法理解。

我们以下表为例:客户 - >订单 - >详细信息。
每个表都是上一个表的子表,具有常规的主 - 外键关系(1到多个)。

如果我执行以下查询:

var q = from c in context.Customers
        select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();

然后我得到一个例外:无法将节点'Value'格式化为执行SQL

但是以下查询不会抛出异常:

var q = from c in context.Customers
        select (c.Orders.FirstOrDefault() ?? new Order()).OrderDateTime;
var q = from c in context.Customers
        select (new Order()).Details.Count();

如果我按如下方式更改主要查询,则不会出现例外情况:

var q = from r in context.Customers.ToList()
        select (c.Orders.FirstOrDefault() ?? new Order()).Details.Count();

现在我可以理解最后一个查询是有效的,因为以下逻辑:
由于没有“new Order()”到SQL的映射(我在这里猜测),我需要在本地列表上工作。

但我无法理解为什么其他两个查询有效?!

我可能会接受使用 context.Customers.ToList()的“本地”版本,但如何加快查询速度?
例如,在上一个查询示例中,我非常确定每个select都会导致执行新的SQL查询以检索Orders。现在我可以通过使用 DataLoadOptions 来避免延迟加载,但是我会无缘无故地检索数千个Order行(我只需要第一行)...
如果我可以在一个SQL语句中执行整个查询(我的第一个查询示例),那么SQL引擎本身就足够聪明,只能为每个客户检索一个Order行...

是否有一种方法可以重写原始查询,使其按预期工作并由SQL服务器一举执行?

修改
(阿图罗的答案更长)
我提供的查询纯粹是出于示例目的。我知道他们自己没有意义,我只是想展示一个简单的例子。

您的示例有效的原因是您避免一起使用“new Order()”。如果我稍微修改你的查询仍然使用它,那么我仍然得到一个例外:

var results = from e in (from c in db.Customers
                         select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
              select new { e.CustomerID, Count = (e.FirstOrder != null ? e.FirstOrder : new Order()).Details().Count() }

虽然这次异常略有不同 - 无法将节点'ClientQuery'格式化为SQL执行 如果我在该查询中使用 ?? 语法而不是(x?y:z),我会得到与我原来相同的异常。

在我的实际查询中,我不需要 Count(),我需要从最后一个表中选择一些属性(在我之前的示例中将是Details)。基本上我需要合并每个表中所有行的值。为了给出一个更重要的例子,我首先要重申我的表格:

模型 - > ModelCategoryVariations< - CategoryVariations - > CategoryVariationItems - > ModelModuleCategoryVariationItemAmounts - > ModelModuleCategoryVariationItemAmountValueChanges

- > 符号代表 1 - >许多关系。请注意,有一个标志反过来......

我的真实查询会是这样的:

var q = from m in context.Models
        from mcv in m.ModelCategoryVariations
        ... // select some more tables
        select new
        {
           ModelId = m.Id,
           ModelName = m.Name,
           CategoryVariationName = mcv.CategoryVariation.Name,
           ..., // values from other tables
           Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
                         let mmcvia = cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId) ?? new ModelModuleCategoryVariationItemAmount()
                         select new
                         {
                            cvi.Id,
                            Amount = (mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.FirstOrDefault() ?? new ModelModuleCategoryVariationItemAmountValueChange()).Amount
                            ... // select some more properties
                         }
         }

此查询在让mmcvia = 行爆炸 如果我没记错的话,通过使用 let mmcvia = new ModelModuleCategoryVariationItemAmount(),查询将在下一个 ?? 操作数处爆炸,该操作数位于 Amount = < / strong>即可。
如果我在context.Models.ToList()
中使用从m开始查询,那么一切正常......

1 个答案:

答案 0 :(得分:3)

为什么不选择与客户相关的任何内容,只查看个人数量。

您可以执行以下操作。

var results = from e in 
                  (from c in db.Customers
                   select new { c.CustomerID, FirstOrder = c.Orders.FirstOrDefault() })
              select new { e.CustomerID, DetailCount = e.FirstOrder != null ? e.FirstOrder.Details.Count() : 0 };

修改

好的,我认为你的查询过于复杂。 问题是您在查询中使用new WhateverObject(),T-SQL不知道任何相关信息; T-SQL知道硬盘中的记录,你正在抛出不存在的东西。只有C#知道这一点。除了在外部最重要的声明中,不要在{QUERIES中使用new,因为这是C#将接收的内容,而C#知道如何创建新的对象实例。

当然,如果你使用ToList()方法会起作用,但性能会受到影响,因为现在你的应用程序主机和sql server一起工作以给你结果,它可能需要多次调用你的数据库而不是一个

请改为尝试:

Categories = (from cvi in mcv.CategoryVariation.CategoryVariationItems
              let mmcvia = 
                   cvi.ModelModuleCategoryVariationItemAmounts.SingleOrDefault(
                        mmcvia2 => mmcvia2.ModelModuleId == m.ModelModuleId)
              select new
              {
                   cvi.Id,
                   Amount = mmcvia != null ?
                      (mmcvia.ModelModuleCategoryVariationItemAmountValueChanges.Select(
                           x => x.Amount).FirstOrDefault() : 0
                   ... // select some more properties
              }

使用Select()方法可以获得第一个金额或其默认值。我只使用“0”作为示例,我不知道Amount的默认值是什么。