9

我正在尝试将多个查询聚合在一起,以便使用通用数据结构为我的用户提供最新的更新显示以提供联合支持。在我的第一个函数中,我有以下选择子句:

.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 中解决这个问题的方法?

4

2 回答 2

3

This is an updated answer - it would seem that in 3.5 the optimization cannot be switched off. I've tried numerous things in LinqPad, and as soon as there's a C# expression that's not derived from a column, the query compiler takes it out of the SQL.

So we need something that'll be derived from a column expression but which we can force always to be null.

Here's one solution I've been able to get to work - write a conditional expression that returns null when a column value is equal to itself; thus always returning null. It's not going to be pretty, because Linq to Sql in 3.5 appears to be very aggressive in optimizing a query (we have to use unique comparisons for each null we want, e.g. use == in one, then != on the next and so on); and it'll get uglier when we modify it to deal with nulls if you have to do this hack on a nullable column:

I'm just picking out column names that I can see from your code - but you might want to use different ones:

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
};

In LinqPad I get a column expression in the generated SQL for each of the statements where I use a ? :. The "" on the other side of the expression is because SQL moans about CASE statements having more than one null.

Clearly - this solution, apart from being ugly as sin, is bounded by how many columns are available for you to do these fake comparisons on, and indeed how many unique comparisons are mapped by L2S.

于 2012-06-25T22:44:01.123 回答
0
  1. Team、UpdateComments、State 为空字符串或空值。所以你应该为这两种情况给出相同的值。
  2. 如果您使用默认比较器,则 UpdateMessage 不同,因此对象也不同。所以你可以连接而不是联合。
于 2012-06-25T22:55:22.847 回答