为啥 Linq-to-sql 错误地删除了我的联合中的字段?
Posted
技术标签:
【中文标题】为啥 Linq-to-sql 错误地删除了我的联合中的字段?【英文标题】:Why is Linq-to-sql incorrectly removing fields in my union?为什么 Linq-to-sql 错误地删除了我的联合中的字段? 【发布时间】:2012-06-27 05:21:33 【问题描述】:我正在尝试将多个查询聚合在一起,以便使用通用数据结构为我的用户提供最新的更新显示以支持联合。在我的第一个函数中,我有以下选择子句:
.Select(x => new PlayerUpdateInfo
FirstName = x.PodOptimizedSearch.FirstName,
LastName = x.PodOptimizedSearch.LastName,
RecruitId = x.RecruitId,
Date = x.Date,
UpdateMessage = x.IsAddedAction
? "Player was added to this recruiting board by " + x.Person.FirstName
+ " " + x.Person.LastName
: "Player was removed from this recruiting board by " +
x.Person.FirstName + " " + x.Person.LastName,
// Defaults for union support
Team = string.Empty,
UpdateComments = x.Comments,
TeamId = 0,
State = string.Empty
);
当正确调用它时,会生成一个返回 9 个字段的查询。第二种方法的选择是:
select new PlayerUpdateInfo
FirstName = recruit.FirstName,
LastName = recruit.LastName,
RecruitId = recruit.RecruitId,
Date = asset.CreateDate,
UpdateMessage = "New Full Game Added",
// Defaults for union support
Team = null,
UpdateComments = null,
TeamId = 0,
State = null
;
当此查询自行运行时,它会正确返回 9 个值。但是,当我尝试这样做时
var query = GetFirstQuery();
query = query.union(GetSecondQuery());
我得到一个查询失败的 sql 异常,因为所有查询都没有相同数量的字段。调查生成的 SQL 表明第一个查询是正确的,但 Linq 正在生成第二个(联合)查询,只有 7 个字段。经过测试,Linq 似乎正在“优化”空值,因此它只返回一个空列而不是 3,从而导致不匹配。
为什么 Linq-to-sql 会错误地生成联合,我该如何解决这个问题?
编辑:
好的,这个问题似乎与 .Net 3.5 的 Linq-to-Sql 中的联合链接有关。这显然不会发生在 4 中。使用以下代码:
protected IQueryable<PlayerUpdateInfo> Test1()
return PodDataContext.Assets
.Select(x => new PlayerUpdateInfo
Date = DateTime.Now,
FirstName = x.Title,
LastName = string.Empty,
RecruitId = 0,
State = string.Empty,
Team = string.Empty,
TeamId = 0,
UpdateComments = string.Empty,
UpdateMessage = string.Empty
);
protected IQueryable<PlayerUpdateInfo> Test2()
return PodDataContext.SportPositions
.Select(x => new PlayerUpdateInfo
Date = DateTime.Now,
FirstName = string.Empty,
LastName = x.Abbreviation,
RecruitId = 0,
State = string.Empty,
Team = string.Empty,
TeamId = 0,
UpdateComments = string.Empty,
UpdateMessage = string.Empty
);
然后我通过联合链:var q2 = Test1().Union(Test2()).Union(Test1());
在 .Net 3.5 中,我得到以下 sql,它不匹配并且失败
SELECT [t4].[value] AS [RecruitId], [t4].[Title] AS [FirstName], [t4].[value2] AS [LastName], [t4].[value22] AS [Team], [t4].[value3] AS [Date]
FROM (
SELECT [t2].[value], [t2].[Title], [t2].[value2], [t2].[value2] AS [value22], [t2].[value3]
FROM (
SELECT @p0 AS [value], [t0].[Title], @p1 AS [value2], @p2 AS [value3]
FROM [dbo].[Assets] AS [t0]
UNION
SELECT @p3 AS [value], @p4 AS [value2], [t1].[Abbreviation], @p5 AS [value3]
FROM [dbo].[SportPositions] AS [t1]
) AS [t2]
UNION
SELECT @p6 AS [value], [t3].[Title], @p7 AS [value2], @p8 AS [value3]
FROM [dbo].[Assets] AS [t3]
) AS [t4]
在 .net 4 中生成以下代码:
SELECT [t4].[value] AS [RecruitId], [t4].[Title] AS [FirstName], [t4].[value2] AS [LastName], [t4].[value3] AS [Team], [t4].[value4] AS [TeamId], [t4].[value5] AS [State], [t4].[value6] AS [UpdateMessage], [t4].[value7] AS [UpdateComments], [t4].[value8] AS [Date]
FROM (
SELECT [t2].[value], [t2].[Title], [t2].[value2], [t2].[value3], [t2].[value4], [t2].[value5], [t2].[value6], [t2].[value7], [t2].[value8]
FROM (
SELECT @p0 AS [value], [t0].[Title], @p1 AS [value2], @p2 AS [value3], @p3 AS [value4], @p4 AS [value5], @p5 AS [value6], @p6 AS [value7], @p7 AS [value8]
FROM [dbo].[Assets] AS [t0]
UNION
SELECT @p8 AS [value], @p9 AS [value2], [t1].[Abbreviation], @p10 AS [value3], @p11 AS [value4], @p12 AS [value5], @p13 AS [value6], @p14 AS [value7], @p15 AS [value8]
FROM [dbo].[SportPositions] AS [t1]
) AS [t2]
UNION
SELECT @p16 AS [value], [t3].[Title], @p17 AS [value2], @p18 AS [value3], @p19 AS [value4], @p20 AS [value5], @p21 AS [value6], @p22 AS [value7], @p23 AS [value8]
FROM [dbo].[Assets] AS [t3]
) AS [t4]
这是有效的 sql 并且有效。由于各种原因,我们无法将生产系统升级到 .net 4,有谁知道我可以在 .net 3.5 中解决这个问题的方法吗?
【问题讨论】:
有趣的问题。您是否尝试过制作一个超级简单的最小示例?你能在简化的例子中重现同样的问题吗? 尝试使用带有 9 个参数的构造函数。也许 LINQ 的行为会有所不同。 你能在 Anons 上联合,并在“联合”之后投射到 PlayerUpdateInfo(这不是答案,但我很想知道这是否是创建“优化”的选择“as”PlayerUpdateInfo "。 我无法在一个简单的示例中得到错误,但它看起来像 null 和 string.select 中的空文字的处理方式有些不同('select null as [field]' 用于空值,'select @p0 as [field]' 带有 string.Empty 的参数)。我猜 GetFirstQuery 和 GetSecondQuery 都返回 IQueryable? 刚到家,我看看能不能举一个简单的例子来说明这种情况。是的,这两个函数都返回IQueryable<PlayerUpdateInfo>
【参考方案1】:
这是一个更新的答案 - 在 3.5 中似乎无法关闭优化。我在 LinqPad 中尝试了很多东西,只要有一个不是从列派生的 C# 表达式,查询编译器就会将其从 SQL 中取出。
所以我们需要从列表达式派生的东西,但我们可以强制它始终为null
。
这是我能够开始工作的一个解决方案 - 编写一个条件表达式,当列值等于自身时返回 null
;因此总是返回null
。这不会很漂亮,因为 3.5 中的 Linq to Sql 在优化查询方面似乎非常积极(我们必须对我们想要的每个 null
使用唯一的比较,例如使用 ==
in一个,然后是!=
,以此类推);如果您必须对可空列执行此 hack,那么当我们对其进行修改以处理空值时,它会变得更加丑陋:
我只是从你的代码中挑选出我可以看到的列名 - 但你可能想要使用不同的:
select new PlayerUpdateInfo
FirstName = recruit.FirstName,
LastName = recruit.LastName,
RecruitId = recruit.RecruitId,
Date = asset.CreateDate,
UpdateMessage = "New Full Game Added",
// Defaults for union support
Team = recruit.FirstName = recruit.FirstName ? null : "",
UpdateComments = recruit.FirstName != recruit.FirstName ? "" : null,
TeamId = recruit.LastName = recruit.LastName ? null : "",
State = recruit.LastName != recruit.LastName ? "" : null
;
在 LinqPad 中,我在生成的 SQL 中为我使用 ? :
的每个语句获得了一个列表达式。表达式另一侧的 ""
是因为 SQL 抱怨 CASE
语句有多个 null。
很明显 - 这个解决方案除了丑陋之外,还受制于有多少列可供您进行这些虚假比较,以及实际上有多少独特的比较被 L2S 映射。
【讨论】:
是的,这就是我知道它是与常量相关的优化的原因,因为在同一个 select 语句中使它们不同会改变字段的数量。问题是它需要我做一些事情,比如让其中一个字段成为空格,这会改变查询的含义 哦错过了,抱歉。我会试一试 nullstring 技巧不起作用,它仍在优化中:( 该死,我们必须尝试不同的值 @KallDrexx - 更新了我的答案。 Linq to Sql 3.5 查询编译器的优化非常激进。我想我找到了解决方案,但它太脏了。【参考方案2】:-
Team、UpdateComments、State 为空字符串或空值。因此,您应该为这两种情况提供相同的值。
如果您使用默认比较器,UpdateMessage 会有所不同,因此对象也会有所不同。所以你可以 concat 而不是 union。
【讨论】:
以上是关于为啥 Linq-to-sql 错误地删除了我的联合中的字段?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Linq-to-SQL 会添加不必要的 COUNT()?
如何在LINQ-To-SQL中排除Contex.InsertOnSubmit()上的用户定义字段?
带有 XML 数据库字段的 Linq-to-SQL —— 为啥会这样?