【问题标题】:LINQ - Distinct() returning a different value if extension method usedLINQ - 如果使用扩展方法,则 Distinct() 返回不同的值
【发布时间】:2009-09-27 22:05:32
【问题描述】:

我有一个 LINQ 查询,它试图获取表中所有日期的所有不同月份。

我使用 Distinct() 扩展方法进行了这项工作。然后我通过使用扩展方法提取月份使其更具可读性。然后它停止返回不同的结果。

谁能帮我弄清楚这里发生了什么?

顺便说一句,如果有人能告诉我获得不同月份的最佳方法,那也很好。但更重要的是我明白为什么会失败。

这是代码。

static class DcUtils
{
     public static DateTime GetMonth(this Timesheet_Entry entry)
    {
        DateTime dt = new DateTime(
                                     entry.Entry_Start_DateTime.Year,
                                     entry.Entry_Start_DateTime.Month, 
                                     1
                                  );
        return dt;
    }
}



public class Demo
{
    public DemonstrateBug()
    {
        TimesheetDataClassesDataContext dc = new TimesheetDataClassesDataContext();
    /////////////////////////////////
    //// Here are the queries and their behaviours
    var q1 = (
                from ts
                in dc.Timesheet_Entries
                select new DateTime(ts.Entry_Start_DateTime.Year, ts.Entry_Start_DateTime.Month, 1)
             ).Distinct();
    // This returns 3 (which is what I want)
    int lengthQuery1 = q1.Count();  

    // And now for the bug!

    var q2 = (
                 from ts
                 in dc.Timesheet_Entries
                 select ts.GetMonth()
             ).Distinct(); 
        // This returns 236 (WTF?)
        int lengthQuery2 = q2.Count();
    }
}

【问题讨论】:

  • 为什么您期望在第二个查询中得到不同的结果?你没有在任何地方打电话给Distinct
  • 谢谢梅尔达德。这只是复制示例的问题。我现在已经在示例中添加了 distinct。

标签: linq linq-to-sql extension-methods


【解决方案1】:

LINQ to SQL 非常智能,可以将 new DateTime() 表达式从您的初始 lambda 表达式转换为可以在服务器上执行的 SQL 语句。如果将此表达式替换为(扩展)方法,则 LINQ to SQL 只会看到对其一无所知的不透明方法的调用,因此它无法为方法调用生成任何 SQL,并且 SQL 查询的一部分与日期混淆消失。

但这不应该破坏任何东西 - 不能转换成 SQL 的必须在客户端执行。那么会发生什么?由于不透明的方法调用,无法在服务器上计算要对其执行不同操作的日期,因此也无法在服务器上执行不同的操作。但是您从损坏的版本中记录的查询包含DISTINCT 语句。

我不使用 LINQ 查询语法,但我假设您编写的内容并非您真正的意思,或者编译器或 LINQ to SQL 推断出您不想​​要的内容。

context
    .Timesheet_Entries
    .Select(tse => tse.GetMonth())
    .Distinct()

context
    .Timesheet_Entries
    .Distinct()
    .Select(tse => tse.GetMonth())

所以我猜你得到第二个是出于什么原因 - 不同的操作似乎通过选择传播。也许它是Distinct() 与查询语法和包含的不透明方法调用的组合。在没有查询语法的情况下尝试这两个版本,看看你得到了什么以及哪些查询被发送到服务器。您还可以尝试插入 ToList() 调用以强制从 LINQ 到 SQL 到 LINQ 到对象的转换 - 这也可能有助于了解情况。

【讨论】:

  • +1 建议使用 Linq to Objects 来诊断问题是否来自 Linq 到 SQL 的转换。
  • context.Timesheet_Entries.Select(...).Distinct() 返回与 context.Timesheet_Entries.Distinct().Select(...) 相同的结果。 (即使它显然不应该)。在中间插入 ToList() 确实使它按预期工作。
【解决方案2】:

我突然想到通过 SQL Server Profiler 运行它。

这个查询:

var q1 = (                
            from ts                
            in dc.Timesheet_Entries                
            select new DateTime(ts.Entry_Start_DateTime.Year,
                                ts.Entry_Start_DateTime.Month, 
                                1)             
          ).Distinct();

生成以下 SQL。如您所见,它将System.DateTime 调用转换为Transact SQL。

     SELECT DISTINCT 
             [t1].[value]
       FROM (
           SELECT 
                CONVERT(
                          DATETIME, 
                          CONVERT(
                                    NCHAR(2), 
                                    DATEPART(
                                             Month, 
                                             [t0].[Entry_Start_DateTime]
                                             )
                                  ) 
                                    + (''/'' + (CONVERT(NCHAR(2), @p0) 
                                    + (''/'' + CONVERT(NCHAR(4), 
                                                      DATEPART(
                                                           Year, 
                                                           [t0].[Entry_Start_DateTime]
                                                              )
                                                       )
                                       ))), 101
                             ) AS [value]
             FROM [dbo].[Timesheet_Entry] AS [t0]
          ) AS [t1]

但是如果我将月份提取逻辑放在扩展方法中:

    var q2 = (                 
                 from ts                 
                 in dc.Timesheet_Entries                 
                 select ts.GetMonth()             
             ).Distinct(); 

它生成以下 SQL。

SELECT DISTINCT 
      [t0].[Timesheet_Entry_ID], 
      [t0].[Entry_Start_DateTime], 
      [t0].[Entry_End_DateTime], 
      [t0].[Task_Description], 
      [t0].[Customer_ID]
FROM [dbo].[Timesheet_Entry] AS [t0]

因此,它将 DISTINCT 函数移至服务器,但保留日期提取代码直到 DISTINCT 操作之后,这不是我想要的,也不是第一个示例中发生的情况。

我不知道我是否应该将此称为错误或leaky abstraction

【讨论】:

  • 我称之为错误。尝试通过调用 Enumerable.Distinct 扩展方法而不是 Queryable.Distinct 来强制解决问题
  • @David B:我试过这个:( (IEnumerable)(dc.Timesheet_Entries.Select(tse => tse.GetMonth()))).Distinct();它可以正常工作 - 返回 3 个不同的值。
猜你喜欢
  • 1970-01-01
  • 2019-01-31
  • 2015-03-04
  • 2014-07-10
  • 2015-07-11
  • 2014-07-10
  • 2011-06-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多