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 的 DateTime
或 DateTimeOffset
。请记住,您需要执行范围查询,因此您将计算其中的 对 作为半开区间。换句话说:
// 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。 :)
我不确定我的问题标题是否完美 - 所以请允许我进一步解释一下。
下面是一些测试数据的截图:
这是我的代码:
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 的DateTime
或DateTimeOffset
。请记住,您需要执行范围查询,因此您将计算其中的 对 作为半开区间。换句话说:// 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。 :)