Как сравнить только компоненты даты из DateTime в EF?

116

У меня есть два значения даты, одно из которых уже хранится в базе данных, а другое выбрано пользователем с помощью DatePicker. Вариант использования - поиск определенной даты в базе данных.

Значение, ранее введенное в базу данных, всегда имеет компонент времени 12:00:00, тогда как дата, введенная из средства выбора, имеет другой компонент времени.

Меня интересуют только компоненты даты и я хотел бы игнорировать компонент времени.

Как можно провести это сравнение в C #?

Кроме того, как это сделать в LINQ?

ОБНОВЛЕНИЕ: в LINQ to Entities отлично работает следующее.

e => DateTime.Compare(e.FirstDate.Value, SecondDate) >= 0
pencilslate
источник
1
Вы также можете взглянуть на этот вопрос SO: stackoverflow.com/questions/683037/how-to-compare-dates-in-c/…
Quintin Robinson

Ответы:

121

ПРИМЕЧАНИЕ: на момент написания этого ответа EF-отношение было неясным (это было отредактировано в вопрос после того, как это было написано). Для правильного подхода с EF отметьте ответ Mandeeps .


Вы можете использовать DateTime.Dateсвойство для сравнения только по дате.

DateTime a = GetFirstDate();
DateTime b = GetSecondDate();

if (a.Date.Equals(b.Date))
{
    // the dates are equal
}
Фредрик Мёрк
источник
34
Сравнить дату легко, но вопрос связан с LINQ to Entities, который не может преобразовать свойство .Date в SQL.
Michaël Carpentier
1
@ MichaëlCarpentier: хороший аргумент. По-видимому, это все еще решило проблему OP.
Fredrik Mörk
6
Это не запрашивает базу данных, а обрабатывает данные на уровне CLR / приложения постфактум. Реальное решение - использовать функцию EntityFunctions.TruncateTime (..), как указано в ответе ниже, поскольку она отправляет запрос в базу данных и позволяет выполнять обработку на уровне хранения. Без этого вы не смогли бы использовать логику сравнения дат в предложениях Where / Count, а затем выполнить дальнейший запрос отфильтрованных данных, так как вам пришлось бы сначала вытащить частичные результаты на уровень приложения, что может нарушить условия сделки в сценариях, которые обрабатывать большие массивы данных.
Марч
6
@Marchy Да, EntityFunctions.TruncateTimeопределенно, похоже, это правильный путь в наши дни (он стал доступен в .NET 4, который был выпущен через год после того, как был задан этот вопрос).
Фредрик Мерк,
1
используйте метод System.Data.Entity.DbFunctions.TruncateTime (). Вам нужно добавить ссылку на EntityFramework
adeel41
132

Используйте класс EntityFunctionsдля обрезки временной части.

using System.Data.Objects;    

var bla = (from log in context.Contacts
           where EntityFunctions.TruncateTime(log.ModifiedDate) ==  EntityFunctions.TruncateTime(today.Date)
           select log).FirstOrDefault();

Источник: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/84d4e18b-7545-419b-9826-53ff1a0e2a62/

ОБНОВИТЬ

Начиная с EF 6.0 и более поздних версий EntityFunctions заменяется DbFunctions .

Мандип Джанджуа
источник
37
Просто примечание EntityFunctionsустарело в пользу System.Data.Entity.DbFunctions(по крайней мере) EF6. Возможно, это было раньше.
pquest 02
4
Я бы не стал сразу переходить к этому решению, так как оно действительно медленное, подробнее: stackoverflow.com/questions/22776843/…
pajics
Кажется, не работает с базой данных SQLite. Я получаю сообщение «Ошибка логики SQL или отсутствует база данных, такой функции нет: TruncateTime».
shadowsora
24

Думаю, это могло бы вам помочь.

Я сделал расширение, так как мне нужно сравнивать даты в репозиториях, заполненных данными EF, и поэтому .Date не был вариантом, поскольку он не реализован в переводе LinqToEntities.

Вот код:

        /// <summary>
    /// Check if two dates are same
    /// </summary>
    /// <typeparam name="TElement">Type</typeparam>
    /// <param name="valueSelector">date field</param>
    /// <param name="value">date compared</param>
    /// <returns>bool</returns>
    public Expression<Func<TElement, bool>> IsSameDate<TElement>(Expression<Func<TElement, DateTime>> valueSelector, DateTime value)
    {
        ParameterExpression p = valueSelector.Parameters.Single();

        var antes = Expression.GreaterThanOrEqual(valueSelector.Body, Expression.Constant(value.Date, typeof(DateTime)));

        var despues = Expression.LessThan(valueSelector.Body, Expression.Constant(value.AddDays(1).Date, typeof(DateTime)));

        Expression body = Expression.And(antes, despues);

        return Expression.Lambda<Func<TElement, bool>>(body, p);
    }

тогда вы можете использовать его таким образом.

 var today = DateTime.Now;
 var todayPosts = from t in turnos.Where(IsSameDate<Turno>(t => t.MyDate, today))
                                      select t);
jrojo
источник
10

Если вы используете Dateсвойство для сущностей БД, вы получите исключение:

"The specified type member 'Date' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."

Вы можете использовать что-то вроде этого:

  DateTime date = DateTime.Now.Date;

  var result = from client in context.clients
               where client.BirthDate >= date
                     && client.BirthDate < date.AddDays(1)
               select client;
algreat
источник
8

Чтобы сделать это в LINQ to Entities, вы должны использовать поддерживаемые методы :

var year = someDate.Year;
var month = ...
var q = from r in Context.Records
        where Microsoft.VisualBasic.DateAndTime.Year(r.SomeDate) == year 
              && // month and day

Уродливо, но это работает, и это делается на сервере БД.

Крэйг Стунц
источник
8

Вот другой способ сделать это, но он полезен, только если SecondDate - это переменная, которую вы передаете:

DateTime startDate = SecondDate.Date;
DateTime endDate = startDate.AddDays(1).AddTicks(-1);
...
e => e.FirstDate.Value >= startDate && e.FirstDate.Value <= endDate

Я думаю, это должно сработать

Джон Кастер
источник
1
Превосходно. Работал у меня. Это было то, чего DateTime = x.Date;мне не хватало. Если бы я использовал varили имел значение, встроенное в сравнение, это не удалось с сообщением об исключении. Спасибо.
Тим Кройдон
Рад, что это сработало, Тим. Извините за задержку с ответом - я действительно давно не входил в SO.
Джон Кастер,
1
Если вы измените e.FirstDate.Value <= endDateна, e.FirstDate.Value < endDateвы можете удалить .AddTicks(-1).
Марко де Зеув,
@MarcodeZeeuw, ты прав, это тоже сработает. Показанное условное выражение предназначено для инклюзивных сравнений точных дат начала и окончания (при условии, что значения диапазона дат будут переданы в условие, а не установлены во фрагменте кода). IOW, условное выражение считается отдельным от значений даты и времени. ,
Джон Кастер
6

Вы также можете использовать это:

DbFunctions.DiffDays(date1, date2) == 0

user3829854
источник
4

вы можете использовать для этого метод DbFunctions.TruncateTime ().

e => DbFunctions.TruncateTime(e.FirstDate.Value) == DbFunctions.TruncateTime(SecondDate);
Суровый Вьяс
источник
3

Просто всегда сравнивайте свойство Date DateTime, а не полное время.

Когда вы делаете свой LINQ-запрос, используйте date.Date в запросе, то есть:

var results = from c in collection
              where c.Date == myDateTime.Date
              select c;
Рид Копси
источник
10
Я получаю сообщение об ошибке «Указанный член типа Date не поддерживается в LINQ to Entities. Поддерживаются только инициализаторы, элементы сущности и свойства навигации сущности.». Есть предположения?
Pencilslate
Да, ваш провайдер не обрабатывает свойство .Date напрямую. Вам придется вытащить его и сравнить даты позже.
Рид Копси,
К сожалению, дату нельзя использовать в Linq To Entities. Надеюсь, MS скоро добавит поддержку перегрузки
Джон Кастер
1
Всегда сравнивать свойство Date? Я погуглил этот комментарий, потому что мне было интересно, является ли это лучшей практикой, т.е. чтобы всегда использовать свойство Date, даже если это что-то вроде candidate.Date >= base.Date. Теоретически candidate.Dateвремя должно быть> = 12:00:00, поэтому использование свойства Date излишне, но я буду придерживаться совета Рида.
Стивен Хоскинг,
3

Вот как я это делаю.

DateTime date_time_to_compare = DateTime.Now;
//Compare only date parts
context.YourObject.FirstOrDefault(r =>
                EntityFunctions.TruncateTime(r.date) == EntityFunctions.TruncateTime(date_to_compare));
Алехандро дель Рио
источник
2

// Примечание для пользователей / кодеров Linq

Это должно дать вам точное сравнение для проверки, попадает ли дата в диапазон при работе с вводом от пользователя - например, средство выбора даты:

((DateTime)ri.RequestX.DateSatisfied).Date >= startdate.Date &&
        ((DateTime)ri.RequestX.DateSatisfied).Date <= enddate.Date

где startdate и enddate - значения из средства выбора даты.

Лео Ди Солти
источник
1

Без времени попробуйте вот так:

TimeSpan ts = new TimeSpan(23, 59, 59);
toDate = toDate.Add(ts);
List<AuditLog> resultLogs = 
    _dbContext.AuditLogs
    .Where(al => al.Log_Date >= fromDate && al.Log_Date <= toDate)
    .ToList();
return resultLogs;
Налан Мадхесваран
источник
1

Вы можете использовать ссылку ниже, чтобы сравнить 2 даты без времени:

private bool DateGreaterOrEqual(DateTime dt1, DateTime dt2)
        {
            return DateTime.Compare(dt1.Date, dt2.Date) >= 0;
        }

private bool DateLessOrEqual(DateTime dt1, DateTime dt2)
        {
            return DateTime.Compare(dt1.Date, dt2.Date) <= 0;
        }

функция сравнения возвращает 3 разных значения: -1 0 1, что означает dt1> dt2, dt1 = dt2, dt1

Маджид
источник
Почему бы вам просто не вернуть DateTime.Compare (dt1.Date, dt2.Date)? Это все, что вам нужно.
Джонни Грабер,
0

Попробуйте это ... Он отлично подходит для сравнения свойств Date между двумя типами DateTimes:

PS. Это временное решение и действительно плохая практика, никогда не следует использовать, когда вы знаете, что база данных может принести тысячи записей ...

query = query.ToList()
             .Where(x => x.FirstDate.Date == SecondDate.Date)
             .AsQueryable();
Raskunho
источник
1
PS: Я обычно использую этот способ, когда DateTimes имеет значение Time, и я хочу сравнить только Date.
Raskunho
2
это очень плохое решение, запрос получит все записи и только потом отфильтрует даты. если в базе данных есть миллионы записей, она захватит их все и только потом отфильтрует даты. ОЧЕНЬ ПЛОХАЯ ПРАКТИКА.
Dementic
1
Это временное решение и действительно плохая практика, никогда не следует использовать, если вы знаете, что база данных может содержать тысячи записей.
Raskunho
если вы добавите свой комментарий к своему ответу, я сниму свой голос против. всем, кто посещает эту страницу, должно быть ясно, что предложенное вами решение плохое, без необходимости читать комментарии.
Dementic
Хотя в целом это плохая идея, этот подход приводит к значительному повышению производительности для небольших наборов записей (<1000 записей или около того) из-за глупого способа, которым EF переводит сравнения дат в SQL. Я видел, как запросы меняются от одной минуты до секунды, просто выполняя сравнение дат в памяти, а не в том, что генерирует SQL EF.
Extragorey