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

Mar*_*rko 9 linq-to-sql

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

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

如果我执行以下查询:

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

然后我得到一个例外:无法将节点'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();
Run Code Online (Sandbox Code Playgroud)

如果我按如下方式更改主查询,则不会出现异常:

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

现在我可以理解最后一个查询是有效的,因为以下逻辑:
由于没有"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() }
Run Code Online (Sandbox Code Playgroud)

虽然这次异常略有不同 - 无法将节点'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
                         }
         }
Run Code Online (Sandbox Code Playgroud)

这个查询在mmcvia =爆炸.
如果我没记错的话,通过使用let mmcvia = new ModelModuleCategoryVariationItemAmount(),查询会在下一次爆炸吗?操作数,在Amount =.
如果我在context.Models.ToList()中使用m开始查询,那么一切正常......

Art*_*nez 4

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

您可以执行以下操作。

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 };
Run Code Online (Sandbox Code Playgroud)

编辑:

好吧,我认为你的查询过于复杂化了。问题是你new WhateverObject()在查询中使用了,T-SQL对此一无所知;T-SQL 知道你硬盘上的记录,你正在抛出一些不存在的东西。只有 C# 知道这一点。不要new在最外层 SELECT 语句以外的查询中使用,因为这是 C# 将收到的内容,并且 C# 知道如何创建对象的新实例。

当然,如果您使用 ToList() 方法,它会起作用,但性能会受到影响,因为现在您的应用程序主机和 sql 服务器一起工作来为您提供结果,并且可能需要对数据库进行多次调用,而不是一次。

试试这个:

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
              }
Run Code Online (Sandbox Code Playgroud)

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