2

I'm not sure that my question Title is perfect - so please allow me to explain a little further.

Here's a snapshot of some test data:

enter image description here

Here's my code:

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

And so here's my problem:

The rows for Order Dates 09/27/2015 and 09/22/2015 query properly with the logic above - yielding 1 row for each date requested. BUT - a query for the 08/14/2015 date yields NOTHING. I am in the -04:00 timezone now, if that matters. If I change the timezone [edit] in the row data [end edit] to -04:00 the 2 08/14/2015 rows query properly.

I have googled to try to find answers to this but have come up dry. Can someone weigh in on my problem?


[UPDATE]: Workaround Here's a workaround (for now) based on a suggestion from this thread by @PiotrAuguscik suggesting to convert the query first to a list:

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)

It's a little "crusty", but it works. I sure would like to know, however, why timezones would have anything to do with DbFunctions.TruncateTime().


[UPDATE #2] Proper solution code derived from answer from 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)

Community
  • 1
  • 1
HumbleBeginnings
  • 1,009
  • 10
  • 22
  • How about putting a breakpoint and see what `DbFunctions.TruncateTime(x.OrderDateLoc)` returns? – Code Different Oct 05 '15 at 03:09
  • @ZoffDino - thanks for your reply. Since the DbFunctions() function can only appear within a LINQ to Entities query, I'm not sure how I would do anything beyond what I'm currently doing. Please let me know if I am grossly overlooking a way to do that. – HumbleBeginnings Oct 05 '15 at 03:51
  • If you're using SQL Server, have you used the Profiler tool to view exactly what SQL is being executed? I'm guessing that the actual values being compared are not exactly what you expect. Something is probably being changed based on time zone. – jmcilhinney Oct 05 '15 at 05:01
  • @jmcilhinney thank you for the possible direction; I am just testing in the development IISExpress - I have never used any Profiler (not sure how but I will research some if it is available). Somehow the timezone IS affecting the logic - I just don't know why or how as I can't find any documentation that suggests that timezone has anything to do with it. As I said in the post (edited) that if I change the data to reflect a -04:00 timezone, THEN the rows appear in the query. – HumbleBeginnings Oct 05 '15 at 05:11
  • See update for temp workaround... – HumbleBeginnings Oct 05 '15 at 05:51
  • IIS Express is irrelevant. SQL Server Profiler is a SQL Server tool. Why would the web server know anything about the SQL code? SQL Server Profiler can be run from SQL Server Management Studio and show you what SQL code is being executed against your database instance. – jmcilhinney Oct 05 '15 at 05:59
  • My apologies - I meant to say SQL Server Express. I will look further into it. Thank you. – HumbleBeginnings Oct 05 '15 at 06:04

2 Answers2

3

A few things:

  • Both your original query and your workaround are non-sargable. You should never manipulate the left side of an comparison in a WHERE clause. If you do, the database can't use any indexes and will get slower and slower the more data you have. Instead, do a range query.

  • It would appear you have datetimeoffset types in your table. These represent specific moments in time, thus comparisons against two datetimeoffset values are done based on their UTC equivalents - not on their local display time. Values are indexed this way as well.

  • Not everyone observes the same calendar date at the same time. You'll need to ask yourself, "who's date am I asking for?"

    • If it's the date of the person making the query, then your input values should reflect that. Instead of passing into your query a VB Date (which is a System.DateTime) in terms of local time, either pass in a UTC-based DateTime, or a DateTimeOffset. Remember, you'll need to do a range query, so you would calculate a pair of them, as a half-open interval. In other words:

      // 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
      
    • If you're looking to match the local date as it is recorded, then you have additional work to do in your SQL Server database.

      • First you'll need to strip away the offset by convert(datetime2, yourDateTimeOffset), or just compute the raw local date by convert(date, yourDateTimeOffset). You should do this in a computed column so you can also create an index on it.

      • Then later, you can use that computed column to do the range query, or if you computed down to the date then you can just do an equality comparison against that.

  • In general, I'd avoid using DbFunctions.TruncateTime in a where clause. It gets converted to some fairly inefficient SQL that looks like this when used against a datetimeoffset field:

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

    Essentially, this uses strings to re-build the datetimeoffset while retaining the offset but setting the time to midnight, which is probably not what you really want to do. You can see this yourself in SQL Profiler.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • @Matt_Johnson - many, many thanks for your detailed explanation. It was VERY helpful and incredibly informative. Sadly, it never occurred to me to do it the way that you suggested... I did and it works PERFECTLY, and all within the guidelines that you mentioned. The code that I used to solve the problem has been edited into my OP. My best to you! – HumbleBeginnings Oct 10 '15 at 03:04
0

This is a response to Matt Johnson's response. The queries above aren't necessarily non-sargable, it depends on the index. It becomes non-sargable when you use an index as a field function parameter. :)

Kevin Taing
  • 51
  • 1
  • 1
  • 4