DbFunctions.TruncateTime在不同服务器时区的行为是否不同?

时间:2015-10-05 02:36:43

标签: vb.net visual-studio-2015 datetimeoffset dbfunctions

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

以下是一些测试数据的快照:

enter image description here

这是我的代码:

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行。但是 - 对2015年8月14日日期的查询产生没有。如果重要的话,我现在在-04:00时区。如果我将行数据 中的时区[edit] 更改为-04:00,则可以正确查询2 08/14/2015行。

我用谷歌搜索试图找到答案,但已经干了。有人可以权衡我的问题吗?

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

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)

它有点"硬壳",但它有效。不过,我当然想知道为什么时区与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)

2 个答案:

答案 0 :(得分:2)

一些事情:

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

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

  • 并非所有人都同时观察相同的日历日期。你需要问问自己,“我要求约会对象?”

    • 如果是进行查询的人的日期,那么您的输入值应该反映出来。而不是根据本地时间将VB DateSystem.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 Server数据库中执行其他工作。

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

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

  • 一般情况下,我会避免在where子句中使用DbFunctions.TruncateTime。当用于datetimeoffset字段时,它会转换为效率相当低的SQL,如下所示:

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

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

答案 1 :(得分:0)

这是对马特·约翰逊(Matt Johnson)的回应。上面的查询不一定是不可保留的,它取决于索引。当您使用索引作为字段函数参数时,它变得不可保留。 :)