DbFunctions.TruncateTime 在不同服务器时区的表现是否不同?

Does DbFunctions.TruncateTime behave differently in different server time zones?

我不确定我的问题标题是否完美 - 所以请允许我进一步解释一下。

下面是一些测试数据的截图:

这是我的代码:

Function TestDb() As ActionResult
   Dim clientLocId As Integer = 23
   Dim showDate As New Date
   showDate = New Date(2015, 8, 14)
   'showDate = New Date(2015, 9, 22)
   'showDate = New Date(2015, 9, 27)

   Dim orderRecs = db.Orders.Where(Function(x) x.ClientLocationId = clientLocId AndAlso x.OrderNumber IsNot Nothing _
                    AndAlso x.DateCompletedUtc IsNot Nothing _
                    AndAlso DbFunctions.TruncateTime(x.OrderDateLoc) = showDate.Date) _
                    .OrderByDescending(Function(x) x.OrderDateUtc)

   Stop
End Function

所以这是我的问题:

订单日期 09/27/2015 和 09/22/2015 的行使用上述逻辑正确查询 - 为请求的每个日期生成 1 行。但是 - 对 08/14/2015 日期的查询不会产生任何结果。如果重要的话,我现在在 -04:00 时区。如果我将行数据 [结束编辑]中的时区[edit] 更改为-04:00,则2 08/14/2015行查询正确。

我用谷歌搜索试图找到这个问题的答案,但没有找到答案。有人可以权衡我的问题吗?


[更新]:解决方法 这是一个解决方法(目前)基于@PiotrAuguscik this thread 的建议,建议首先将查询转换为列表:

Dim orderRecs = (db.Orders.Where(Function(x) x.ClientLocationId = clientLocId AndAlso x.OrderNumber IsNot Nothing _
                AndAlso x.DateCompletedUtc IsNot Nothing).ToList) _
                .Where(Function(x) x.OrderDateLoc.Value.Date = showDate.Date) _
                .OrderByDescending(Function(x) x.OrderDateUtc)

有点"crusty",但它确实有效。但是,我确实想知道为什么时区与 DbFunctions.TruncateTime().

有任何关系

[更新 #2] 正确的解决方案代码来自 Matt Johnson 的回答

Dim orderRecs = db.Orders.Where(Function(x) x.ClientLocationId = clientLocId AndAlso x.OrderNumber IsNot Nothing _
                          AndAlso x.DateCompletedUtc IsNot Nothing AndAlso
                          (x.OrderDateLoc >= showDateDto AndAlso x.OrderDateLoc < showDateDto.AddDays(1))) _
                          .OrderByDescending(Function(x) x.OrderDateUtc)

几件事:

  • 您的原始查询和解决方法都是 non-sargable。永远不要在 WHERE 子句中操作比较的左侧。如果这样做,数据库将无法使用任何索引,并且您拥有的数据越多,速度就会越来越慢。相反,进行范围查询。

  • 您的 table 中似乎有 datetimeoffset 类型。这些代表特定的时间时刻,因此与两个 datetimeoffset 值的比较是基于它们的 UTC 等效值 - 而不是它们的本地显示时间。值也以这种方式编制索引。

  • 并不是每个人都在同一时间遵守同一个日历日期。你需要问问自己,"who's date am I asking for?"

    • 如果是进行查询的人的日期,那么您的输入值应该反映这一点。不是将本地时间 VB Date(即 System.DateTime)传递给您的查询,而是传递基于 UTC 的 DateTimeDateTimeOffset。请记住,您需要执行范围查询,因此您将计算其中的 作为半开区间。换句话说:

      // this example uses the local time zone, but there are other ways also.
      DateTimeOffset startDto = new DateTimeOffset(showDate.Date)
      DateTimeOffset endDto = new DateTimeOffset(showDate.Date.AddDays(1))
      
      // then in the query...
      ...   x.OrderDateLoc >= startDto && x.OrderDateLoc < endDto
      
    • 如果您希望匹配记录的本地日期,那么您还需要在 SQL 服务器数据库中进行额外的工作。

      • 首先,您需要去除 convert(datetime2, yourDateTimeOffset) 的偏移量,或者只计算 convert(date, yourDateTimeOffset) 的原始本地日期。您应该在 computed column 中执行此操作,这样您还可以在其上创建索引。

      • 然后,您可以使用该计算列来执行范围查询,或者如果您计算到日期,那么您可以对其进行相等比较。

  • 一般来说,我会避免在 where 子句中使用 DbFunctions.TruncateTime。它被转换为一些相当低效的 SQL,当用于 datetimeoffset 字段时看起来像这样:

    convert(datetimeoffset, convert(varchar(255), yourField, 102) + ' 00:00:00 ' + Right(convert(varchar(255), yourField, 121), 6), 102)
    

    本质上,这使用字符串重新构建 datetimeoffset,同时保留偏移量但将时间设置为午夜,这可能不是您真正想要做的。您可以在 SQL Profiler 中自己查看。

这是对马特约翰逊的回应的回应。上面的查询不一定是 non-sargable,它取决于索引。当您使用索引作为字段函数参数时,它将变为 non-sargable。 :)