Entity Framework позволяет выполнять поиск данных с помощью Linq (интегрированный языковой запрос). Это означает, что оператор Sql будет сгенерирован на основе указанного запроса, который был написан в коде.

Свидания могут быть забавными

Любой из нас, сравнивающих данные по датам или ищущих данные между двумя датами, может не так беспокоиться о конкретном времени. Однако тип данных, используемый для хранения дат в .NET, DateTime, который также просто хранит время, поскольку он название предполагает.

sample date: 01/08/2019 12:08:33.790

Приведенный выше пример даты показывает, как сервер Microsoft SQL хранит даты с точностью до миллисекунды, и именно так работает типичное значение DateTime value. Такой уровень точности может быть очень проблематичным для человека, которого на самом деле не волнует, какое время привязано к дате. Обычно даты, переданные в запрос для поиска записи с показанной выше выборочной датой, будут выглядеть следующим образом: 01/08/2019 00:00:00.000 , что означает, что запрос для получения записи, основанной на этой дате без времени, скорее всего, не вернет никаких данных.

Покажем код

  1. Создайте новое консольное приложение .NET 4.8.

2. Добавьте DbContext

public class DateQueryDbContext : DbContext
{
        
   public DateQueryDbContext() : base(nameOrConnectionString: "Data     Source=localhost;Initial Catalog=DateQuery;Integrated Security=true;"){ }
 
        
       
}

3. Добавьте тип User для наших тестовых целей.

public class User
{
   public int Id { get; set; }
   public string Name { get; set; }
   public DateTime AddedDate { get; set; }
}

4. Добавьте public DbSet<User> Users { get; set; } к DbContext , чтобы он выглядел следующим образом:

5. Включите миграции и обновите базу данных.

Чтобы включить миграции в проекте с первым кодом, мы запускаем enable-migrations в консоли диспетчера пакетов, доступной по адресу: Инструменты - ›Диспетчер пакетов библиотеки -› Консоль диспетчера пакетов

затем инициализируйте новую миграцию, запустив add-migration Initial , где «Начальная» - это имя миграции. Наконец, запустите update-database, чтобы выполнить сгенерированные миграции для базы данных.

6. Добавьте образец данных по сути ниже:
https://gist.github.com/duhowise/96c53a216067a8160689c100add7510b

Затем мы запускаем необработанный запрос для поиска даты выборки, показанной в примере выше.

Используя конкретную дату и время:

SELECT * FROM Users u where u.AddedDate =’2019–08–01 12:08:33.790'

result: [
{“Id”:”35",”Name”:”Aiko2002",”AddedDate”:”01/08/2019 12:08:33"}]

Использование даты без времени:

SELECT * FROM Users u where u.AddedDate =’2019–08–01 00:00:00.000'

result: []

Мы видим, что запрос не возвращает результата, поскольку оба раза не совпадают.

Как справиться с этим с помощью SQL?

Однако у Sql Server есть тип, специфичный только для дат (без времени), который называется DATE, , это позволяет нам преобразовать источник DateTime в DATE перед сравнением и предоставляет нам способ запрашивать поля даты без учета их прикрепленного времени следующим образом:

SELECT * FROM Users u WHERE cast (u.AddedDate AS DATE) =’2019–08–01 00:00:00.000'

result: [
{“Id”:”35",”Name”:”Aiko2002",”AddedDate”:”01/08/2019 12:08:33"}]

Как насчет Entity Framework?

Используя конкретный DateTime:

результат:

Использование даты без времени

Это возвращает пустой результат, как ожидалось

Как справиться с этим с помощью entity framework?

Совсем недавно я начал управлять довольно старой базой кода и увидел несколько способов «управления» этой проблемой, включая расширение диапазона BETWEEN запросов для элементов между двумя диапазонами дат путем вызова AddDays(1) перед передачей параметров, которые должны быть выполнены. Естественно, мне не понравился обходной путь, и я попытался исправить его, что привело меня к пониманию «поведения поиска DateTime».

После нескольких поисков я нашел DbFunctions в System.Data.Entity

DbFunctions имеет ряд статических методов, среди которых TruncateTime(Nullable<DateTime>), который позволяет вам: «вызывать каноническую функцию EDM TruncateTime для возврата заданной даты с очищенной частью времени».

Как работает Truncate Time?

приведенный выше запрос почти такой же, как и приведенный выше, без конкретных дат, за исключением использования TuncateTime. Результат:

Так что же на самом деле делает TruncateTime?

Давайте посмотрим, добавив строку кода context.Database.Log = Console.WriteLine; перед запросом, чтобы мы могли просмотреть сгенерированный SQL.

Без TruncateTime:

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[AddedDate] AS [AddedDate]
    FROM [dbo].[Users] AS [Extent1]
    WHERE [Extent1].[AddedDate] = @p__linq__0
-- p__linq__0: '01/08/2019 00:00:00' (Type = DateTime2, IsNullable = false)

С TruncateTime:

SELECT
    [Extent1].[Id] AS [Id],
    [Extent1].[Name] AS [Name],
    [Extent1].[AddedDate] AS [AddedDate]
    FROM [dbo].[Users] AS [Extent1]
    WHERE (cast(cast([Extent1].[AddedDate] as date) as datetime2)) = @p__linq__0
-- p__linq__0: '01/08/2019 00:00:00' (Type = DateTime2, IsNullable = false)

Заключительные примечания

Хорошо видно, что запрос дат без использования соответствующих типов может привести к неверным результатам. Приведение к типу DATE для необработанных запросов и использование TruncateTime действительно может помочь упростить ваши линг-запросы. Вы можете найти исходные файлы по адресу: https://github.com/duhowise/EntityFrameworkDateQuery