为啥 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&lt;PlayerUpdateInfo&gt; 【参考方案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()上的用户定义字段?

为啥 WCF 删除了我的回复消息中的 wsa:To 标头?

带有 XML 数据库字段的 Linq-to-SQL —— 为啥会这样?

链接器多重定义错误:为啥 <thread> 似乎定义了我的函数?

为啥我的 sqlite 数据库中的 rawquery 给了我错误的人?