Como comparar apenas os componentes de data do DateTime no EF?


116

Estou tendo dois valores de data, um já armazenado no banco de dados e outro selecionado pelo usuário usando DatePicker. O caso de uso é pesquisar uma data específica no banco de dados.

O valor previamente inserido no banco de dados sempre tem componente de tempo de 12:00:00, sendo que assim como a data inserida no selecionador tem componente de tempo diferente.

Estou interessado apenas nos componentes de data e gostaria de ignorar o componente de tempo.

Quais são as maneiras de fazer essa comparação em C #?

Além disso, como fazer isso no LINQ?

ATUALIZAÇÃO: No LINQ to Entities, o seguinte funciona bem.

e => DateTime.Compare(e.FirstDate.Value, SecondDate) >= 0

1
Você também pode dar uma olhada nesta pergunta do SO: stackoverflow.com/questions/683037/how-to-compare-dates-in-c/…
Quintin Robinson

Respostas:


121

NOTA: no momento em que escrevi esta resposta, a relação EF não era clara (que foi editada na pergunta depois que ela foi escrita). Para uma abordagem correta com EF, verifique a resposta do Mandeeps .


Você pode usar a DateTime.Datepropriedade para realizar uma comparação apenas de data.

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

if (a.Date.Equals(b.Date))
{
    // the dates are equal
}

34
É fácil comparar a data, mas a questão está relacionada ao LINQ to Entities, que não consegue converter a propriedade .Date em SQL.
Michaël Carpentier

1
@ MichaëlCarpentier: bom ponto. Aparentemente, ainda resolveu o problema do OP.
Fredrik Mörk

6
Isso não consulta o banco de dados, mas sim processa os dados no CLR / camada de aplicativo após o fato. A verdadeira solução é utilizar a função EntityFunctions.TruncateTime (..) conforme especificado na resposta abaixo, pois ela envia a consulta ao banco de dados e permite que o processamento seja feito na camada de armazenamento. Sem isso, você não poderia usar a lógica de comparação de datas nas cláusulas Where / Count e, em seguida, consultar mais os dados filtrados, já que primeiro teria que puxar os resultados parciais para a camada do aplicativo, o que pode ser um quebra-negócio em cenários que processar grandes conjuntos de dados.
Março,

6
@Marchy Sim, EntityFunctions.TruncateTimecertamente parece ser o caminho a percorrer hoje em dia (tornou-se disponível no .NET 4, que foi lançado um ano após essa pergunta ser feita).
Fredrik Mörk,

1
use o método System.Data.Entity.DbFunctions.TruncateTime (). Você precisa adicionar uma referência a EntityFramework
adeel41

132

Use a classe EntityFunctionspara aparar a parte do tempo.

using System.Data.Objects;    

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

Fonte: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/84d4e18b-7545-419b-9826-53ff1a0e2a62/

ATUALIZAR

A partir do EF 6.0 e posterior, EntityFunctions foi substituído por DbFunctions .


37
Apenas uma observação EntityFunctionsfoi substituída em favor de System.Data.Entity.DbFunctions(pelo menos) EF6. Pode ter sido antes disso.
pquest

4
Eu não seria rápido em pular para esta solução, pois ela é muito lenta, mais informações: stackoverflow.com/questions/22776843/…
pajics

Não parece funcionar com um banco de dados SQLite. Recebo "Erro de lógica SQL ou banco de dados ausente sem tal função: TruncateTime".
sombrasora

24

Eu acho que isso pode te ajudar.

Fiz uma extensão, pois tenho que comparar datas em repositórios preenchidos com dados EF e, portanto, .Date não era uma opção, pois não é implementado na tradução de LinqToEntities.

Aqui está o código:

        /// <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);
    }

então você pode usá-lo desta forma.

 var today = DateTime.Now;
 var todayPosts = from t in turnos.Where(IsSameDate<Turno>(t => t.MyDate, today))
                                      select t);

10

Se você usar a Datepropriedade para Entidades de banco de dados, obterá exceção:

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

Você pode usar algo assim:

  DateTime date = DateTime.Now.Date;

  var result = from client in context.clients
               where client.BirthDate >= date
                     && client.BirthDate < date.AddDays(1)
               select client;

8

Fazê-lo em LINQ to Entities, você tem que usar métodos suportados :

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

Feio, mas funciona e é feito no servidor de banco de dados.


8

Esta é uma maneira diferente de fazer isso, mas só é útil se SecondDate for uma variável que você está passando:

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

Acho que deve funcionar


1
Excelente. Funcionou para mim. Era o explícito DateTime = x.Date;que estava faltando. Se eu usei var, ou teve o valor embutido na comparação, ele falhou com a exceção relatada. Obrigado.
Tim Croydon

Que bom que funcionou, Tim. Desculpe pela demora em responder - eu não tenho logado no SO há algum tempo.
John Kaster

1
Se você mudar e.FirstDate.Value <= endDatepara, e.FirstDate.Value < endDatepoderá remover o .AddTicks(-1).
Marco de Zeeuw

@MarcodeZeeuw você está certo, isso definitivamente funcionaria também. A expressão condicional mostrada se destina a comparações de datas inclusivas de datas exatas de início e término (assumindo que os valores de intervalo de datas seriam passados ​​para a condição em vez de configurados em um fragmento de código). IOW, a condicional é considerada separada dos valores de data e hora .
John Kaster

6

Você também pode usar isto:

DbFunctions.DiffDays(date1, date2) == 0


4

você pode usar o método DbFunctions.TruncateTime () para isso.

e => DbFunctions.TruncateTime(e.FirstDate.Value) == DbFunctions.TruncateTime(SecondDate);

3

Basta sempre comparar a propriedade Date de DateTime, em vez da data e hora completa.

Ao fazer sua consulta LINQ, use date.Date na consulta, ou seja:

var results = from c in collection
              where c.Date == myDateTime.Date
              select c;

10
Estou recebendo o erro "O membro do tipo especificado 'Date' não tem suporte no LINQ to Entities. Somente inicializadores, membros de entidade e propriedades de navegação de entidade são compatíveis.". Alguma ideia?
pencilslate

Sim - seu provedor não controla a propriedade .Date diretamente. Você terá que retirá-lo e comparar as datas mais tarde.
Reed Copsey

.Date não pode ser usado no Linq To Entities, infelizmente. Esperançosamente, a MS adicionará esse suporte de sobrecarga em breve
John Kaster

1
Sempre compare a propriedade Date? Eu pesquisei este comentário porque me perguntei se essa é a melhor prática, ou seja, para sempre usar a propriedade Date, mesmo quando for algo parecido candidate.Date >= base.Date. Teoricamente, o candidate.Datetempo deve ser> = 12:00:00, então usar a propriedade Date é redundante, mas vou seguir o conselho de Reed.
Stephen Hosking,

3

É assim que eu faço isso.

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

// Nota para usuários / codificadores do Linq

Isso deve fornecer a comparação exata para verificar se uma data está dentro do intervalo ao trabalhar com a entrada de um selecionador de data do usuário, por exemplo:

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

onde startdate e enddate são valores de um selecionador de data.


1

Sem tempo, tente assim:

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

Você pode usar o link abaixo para comparar 2 datas sem tempo:

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;
        }

a função Compare retorna 3 valores diferentes: -1 0 1 que significa dt1> dt2, dt1 = dt2, dt1


Por que você simplesmente não retorna DateTime.Compare (dt1.Date, dt2.Date)? Isso faz com que tudo que você precisa.
Johnny Graber

0

Tente isto ... Funciona bem comparar propriedades de Date entre dois tipos de DateTimes:

PS. É uma solução paliativa e uma prática muito ruim, nunca deve ser usada quando você sabe que o banco de dados pode trazer milhares de registros ...

query = query.ToList()
             .Where(x => x.FirstDate.Date == SecondDate.Date)
             .AsQueryable();

1
PS: Eu costumo usar esta forma quando DateTimes tem valor Time e quero comparar apenas a Date.
Raskunho

2
esta é uma solução muito ruim, a consulta obterá todos os registros e só então filtrará as datas. se o banco de dados tiver milhões de registros, isso irá capturar todos eles e só então filtrar as datas. PRÁTICA MUITO RUIM.
Demência de

1
É uma solução paliativa e uma prática muito ruim, nunca deve ser usada quando você sabe que o banco de dados pode trazer milhares de registros.
Raskunho

se você adicionar seu comentário à sua resposta, removerei meu voto negativo. deve ficar claro para quem visita esta página que a solução que você propôs é ruim, sem ter que ler os comentários.
Demência de

Embora seja uma má ideia em geral, essa abordagem resulta em um desempenho extremamente aprimorado para pequenos conjuntos de registros (<1000 registros ou mais), devido à maneira idiota de EF traduzir comparações de datas em SQL. Tenho visto consultas que vão de mais de um minuto a menos de um segundo apenas fazendo a comparação de datas na memória, em vez de em qualquer SQL EF que gerar.
Extragorey
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.