Como corrigir o erro de conversão fora de intervalo datetime2 usando DbContext e SetInitializer?

136

Estou usando as APIs DbContext e Code First introduzidas no Entity Framework 4.1.

O modelo de dados usa tipos de dados básicos, como stringe DateTime. A única anotação de dados que estou usando em alguns casos é [Required], mas isso não está em nenhuma das DateTimepropriedades. Exemplo:

public virtual DateTime Start { get; set; }

A subclasse DbContext também é simples e se parece com:

public class EventsContext : DbContext
{
    public DbSet<Event> Events { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Event>().ToTable("Events");
    }
}

O inicializador define datas no modelo para valores sensíveis neste ano ou no próximo ano.

No entanto, quando executo o inicializador, recebo este erro em context.SaveChanges():

A conversão de um tipo de dados datetime2 para um tipo de dados datetime resultou em um valor fora do intervalo. A instrução foi encerrada.

Não entendo por que isso está acontecendo, porque tudo é muito simples. Também não tenho certeza de como corrigi-lo, pois não há arquivo edmx para editar.

Alguma ideia?

Alex Angas
fonte
2
Você pode usar o SQL Profiler para ver inserir / atualizar instruções SQL? É muito difícil dizer o que está acontecendo aqui - não vemos seu inicializador ou entidades. O SQL Profiler ajudará muito a localizar o problema.
Ladislav Mrnka
1
No meu caso, adicionei um campo a uma tabela e editei o formulário, esqueci de atualizar o Bind Include e meu campo estava sendo definido como NULL. Portanto, o erro ajudou a corrigir minha supervisão.
strattonn

Respostas:

186

Você deve garantir que Start seja maior ou igual a SqlDateTime.MinValue (1 de janeiro de 1753) - por padrão, Start é igual a DateTime.MinValue (1 de janeiro de 0001).

andygjp
fonte
14
Eu havia deixado alguns dos meus objetos inicializadores sem uma data definida, portanto, ele teria como padrão DateTime.MinValue.
Alex Angas
Eu também fiz isso ^. Adicionado um campo de data personalizado ao objeto ApplicationUser de identidade do asp.net e esqueceu de inicializá-lo para algo que fazia sentido. : (
Mike Devenney
No meu caso, o problema estava na data mínima, 01/01/0001 estava gerando o erro.
Machado
como posso verificar dateTime.minvalue?
Anabeil 03/04
1
Um pouco atrasado, mas @Anabeil, você deve conseguir apenas Console.WriteLine (DateTime.MinValue) na janela imediata do VS / linqpad
SIRHAMY
22

Simples. No seu código primeiro, defina o tipo de DateTime como DateTime? Então você pode trabalhar com o tipo DateTime anulável no banco de dados. Exemplo de entidade:

public class Alarme
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        public DateTime? DataDisparado { get; set; }//.This allow you to work with nullable datetime in database.
        public DateTime? DataResolvido { get; set; }//.This allow you to work with nullable datetime in database.
        public long Latencia { get; set; }

        public bool Resolvido { get; set; }

        public int SensorId { get; set; }
        [ForeignKey("SensorId")]
        public virtual Sensor Sensor { get; set; }
    }
Ulisses Ottoni
fonte
6
nem sempre é esse o caso. {1/1/0001 12:00:00} também faz esse erro emergir
eran otzap
20

Em alguns casos, DateTime.MinValue(ou equivalente default(DateTime)) , é usado para indicar um valor desconhecido.

Este método de extensão simples pode ajudar a lidar com essas situações:

public static class DbDateHelper
{
    /// <summary>
    /// Replaces any date before 01.01.1753 with a Nullable of 
    /// DateTime with a value of null.
    /// </summary>
    /// <param name="date">Date to check</param>
    /// <returns>Input date if valid in the DB, or Null if date is 
    /// too early to be DB compatible.</returns>
    public static DateTime? ToNullIfTooEarlyForDb(this DateTime date)
    {
        return (date >= (DateTime) SqlDateTime.MinValue) ? date : (DateTime?)null;
    }
}

Uso:

 DateTime? dateToPassOnToDb = tooEarlyDate.ToNullIfTooEarlyForDb();
Kjartan
fonte
13

Você pode tornar o campo anulável, se isso atender às suas preocupações específicas de modelagem. Uma data nula não será coagida para uma data que não esteja dentro do intervalo do tipo SQL DateTime da mesma forma que um valor padrão. Outra opção é mapear explicitamente para um tipo diferente, talvez com,

.HasColumnType("datetime2")
Brian Kretzler
fonte
12

Embora essa pergunta seja bastante antiga e já haja ótimas respostas, pensei em colocar mais uma que explique três abordagens diferentes para resolver esse problema.

1ª Abordagem

Mapeie explicitamente a DateTimepropriedade public virtual DateTime Start { get; set; }para datetime2na coluna correspondente da tabela. Porque, por padrão, a EF fará o mapeamento para datetime.

Isso pode ser feito por API fluente ou anotação de dados.

  1. API fluente

    Na classe DbContext, substitua OnModelCreatinge configure a propriedade Start(por motivos de explicação, é uma propriedade da classe EntityClass).

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        //Configure only one property 
        modelBuilder.Entity<EntityClass>()
            .Property(e => e.Start)
            .HasColumnType("datetime2");
    
       //or configure all DateTime Preperties globally(EF 6 and Above)
        modelBuilder.Properties<DateTime>()
            .Configure(c => c.HasColumnType("datetime2"));
    }
  2. Anotação de dados

    [Column(TypeName="datetime2")]
    public virtual DateTime Start { get; set; }

2ª Abordagem

Inicialize Startcom um valor padrão no construtor EntityClass. Isso é bom como se, por algum motivo, o valor de Startnão estivesse definido antes de salvar a entidade no início do banco de dados sempre terá um valor padrão. Verifique se o valor padrão é maior ou igual a SqlDateTime.MinValue (de 1 de janeiro de 1753 a 31 de dezembro de 9999)

public class EntityClass
{
    public EntityClass()
    {
        Start= DateTime.Now;
    }
    public DateTime Start{ get; set; }
}

3ª Abordagem

Tornar Startpara ser do tipo anulável DateTime -note ? após DateTime-

public virtual DateTime? Start { get; set; }

Para mais explicações leia este post

alvo
fonte
8

Se suas DateTimepropriedades forem anuláveis ​​no banco de dados, certifique-se de usar DateTime?as propriedades do objeto associado ou o EF passará DateTime.MinValuepor valores não atribuídos que estão fora do intervalo com o qual o tipo de data e hora do SQL pode manipular.

Christopher King
fonte
8

Minha solução foi alternar todas as colunas datetime para datetime2 e usar datetime2 para novas colunas. Em outras palavras, faça com que o EF use datetime2 por padrão. Adicione isso ao método OnModelCreating no seu contexto:

modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));

Isso receberá todos os DateTime e DateTime? propriedades em todas as suas entidades.

Ogglas
fonte
3

inicialize a propriedade Start no construtor

Start = DateTime.Now;

Isso funcionou para mim quando eu estava tentando adicionar alguns novos campos à tabela Usuários do ASP .Net Identity Framework (AspNetUsers) usando o Code First. Atualizei o Class - ApplicationUser no IdentityModels.cs e adicionei um campo lastLogin do tipo DateTime.

public class ApplicationUser : IdentityUser
    {
        public ApplicationUser()
        {
            CreatedOn = DateTime.Now;
            LastPassUpdate = DateTime.Now;
            LastLogin = DateTime.Now;
        }
        public String FirstName { get; set; }
        public String MiddleName { get; set; }
        public String LastName { get; set; }
        public String EmailId { get; set; }
        public String ContactNo { get; set; }
        public String HintQuestion { get; set; }
        public String HintAnswer { get; set; }
        public Boolean IsUserActive { get; set; }

        //Auditing Fields
        public DateTime CreatedOn { get; set; }
        public DateTime LastPassUpdate { get; set; }
        public DateTime LastLogin { get; set; }
    }
Abhinav Bhandawat
fonte
2

Com base na resposta do usuário @ andygjp, é melhor substituir o Db.SaveChanges()método base e adicionar uma função para substituir qualquer data que não esteja entre SqlDateTime.MinValue e SqlDateTime.MaxValue.

Aqui está o código de exemplo

public class MyDb : DbContext
{
    public override int SaveChanges()
    {
        UpdateDates();
        return base.SaveChanges();
    }

    private void UpdateDates()
    {
        foreach (var change in ChangeTracker.Entries().Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified)))
        {
            var values = change.CurrentValues;
            foreach (var name in values.PropertyNames)
            {
                var value = values[name];
                if (value is DateTime)
                {
                    var date = (DateTime)value;
                    if (date < SqlDateTime.MinValue.Value)
                    {
                        values[name] = SqlDateTime.MinValue.Value;
                    }
                    else if (date > SqlDateTime.MaxValue.Value)
                    {
                        values[name] = SqlDateTime.MaxValue.Value;
                    }
                }
            }
        }
    }
}

Retirado do comentário do usuário @ sky-dev em https://stackoverflow.com/a/11297294/9158120

Hamza Khanzada
fonte
1

Eu tive o mesmo problema e, no meu caso, estava definindo a data como novo DateTime () em vez de DateTime.Now

sam
fonte
0

No meu caso, isso aconteceu quando usei a entidade e a tabela sql tem o valor padrão de datetime == getdate (). Então, o que eu fiz para definir um valor para este campo.

Ahmad Moayad
fonte
0

Estou usando o Database First e, quando esse erro ocorreu, minha solução foi forçar o ProviderManifestToken = "2005" no arquivo edmx (tornando os modelos compatíveis com o SQL Server 2005). Não sei se algo semelhante é possível para o Code First.

Sérgio Azevedo
fonte
0

Uma linha corrige isso:

modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));

Então, no meu código, adicionei:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Properties<DateTime>().Configure(c => c.HasColumnType("datetime2"));
}

Adicionar essa linha à seção de substituição da subclasse DBContext void OnModelCreating deve funcionar.

Howie Krauth
fonte
0

No meu caso, após algumas refatorações no EF6, meus testes falharam com a mesma mensagem de erro do pôster original, mas minha solução não teve nada a ver com os campos DateTime.

Estava faltando um campo obrigatório ao criar a entidade. Depois de adicionar o campo ausente, o erro desapareceu. Minha entidade tem dois DateTime? campos, mas eles não eram o problema.

GrayDwarf
fonte