我偶然发现了一个非常奇怪的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开始查询,那么一切正常......
为什么您只查看个人计数而不选择与客户相关的任何内容。
您可以执行以下操作。
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”作为示例,我不知道您的“金额”默认值是多少。