Jak naprawić błąd konwersji spoza zakresu datetime2 za pomocą DbContext i SetInitializer?

136

Używam interfejsów API DbContext i Code First wprowadzonych w Entity Framework 4.1.

Model danych wykorzystuje podstawowe typy danych, takie jak stringi DateTime. Jedyna adnotacja danych, której używam w niektórych przypadkach, to [Required], ale nie ma jej w żadnej z DateTimewłaściwości. Przykład:

public virtual DateTime Start { get; set; }

DbContext podklasą jest również prosty i wygląda następująco:

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

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

W initializer zestawy dat w modelu do wartości sensownych w obu tego roku lub w przyszłym roku.

Jednak po uruchomieniu inicjatora pojawia się ten błąd pod adresem context.SaveChanges():

Konwersja typu danych datetime2 na typ danych datetime spowodowała uzyskanie wartości spoza zakresu. Oświadczenie zostało zakończone.

W ogóle nie rozumiem, dlaczego tak się dzieje, ponieważ wszystko jest takie proste. Nie jestem też pewien, jak to naprawić, ponieważ nie ma pliku edmx do edycji.

Jakieś pomysły?

Alex Angas
źródło
2
Czy możesz używać programu SQL Profiler do wyświetlania instrukcji wstawiania / aktualizowania instrukcji SQL? Trudno powiedzieć, co się tutaj dzieje - nie widzimy Twojego inicjatora ani jednostek. SQL Profiler bardzo pomoże Ci zlokalizować problem.
Ladislav Mrnka
1
W moim przypadku dodałem pole do tabeli i formularza edycji, zapomniałem zaktualizować Bind Includes i moje pole było ustawione na NULL. Więc błąd pomógł naprawić mój niedopatrzenie.
strattonn

Odpowiedzi:

186

Musisz upewnić się, że Start jest większy lub równy SqlDateTime.MinValue (1 stycznia 1753) - domyślnie Start jest równy DateTime.MinValue (1 stycznia 0001).

andygjp
źródło
14
Pozostawiłem niektóre z moich obiektów inicjalizujących bez ustawionej daty, więc domyślnie byłby to DateTime.MinValue.
Alex Angas
Ja też to zrobiłem ^. Dodano niestandardowe pole daty do obiektu ApplicationUser tożsamości asp.net, a następnie zapomniałem zainicjować go do czegoś, co miało sens. : (
Mike Devenney
W moim przypadku problem tkwił w dacie min, 01/01/0001 generował błąd.
Machado
jak mogę sprawdzić dateTime.minvalue?
Anabeil
1
Trochę późno, ale @Anabeil powinieneś być w stanie po prostu Console.WriteLine (DateTime.MinValue) w bezpośrednim oknie VS / linqpad
SIRHAMY
22

Prosty. W kodzie najpierw ustaw typ DateTime na DateTime ?. Możesz więc pracować z typem DateTime dopuszczającym wartość null w bazie danych. Przykład jednostki:

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
źródło
6
nie zawsze tak jest. Również ten błąd pojawia się w przypadku {1/1/0001 12:00:00 AM}
eran otzap
20

W niektórych przypadkach DateTime.MinValue(lub równoważniedefault(DateTime) ) służy do wskazania nieznanej wartości.

Ta prosta metoda rozszerzenia może pomóc w takich sytuacjach:

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

Stosowanie:

 DateTime? dateToPassOnToDb = tooEarlyDate.ToNullIfTooEarlyForDb();
Kjartan
źródło
13

Możesz ustawić wartość pola na wartość null, jeśli odpowiada to Twoim konkretnym problemom związanym z modelowaniem. Data pusta nie zostanie wymuszona na datę, która nie mieści się w zakresie typu SQL DateTime, tak jak byłaby to wartość domyślna. Inną opcją jest jawne odwzorowanie na inny typ, na przykład za pomocą

.HasColumnType("datetime2")
Brian Kretzler
źródło
12

Chociaż to pytanie jest dość stare i istnieją już świetne odpowiedzi, pomyślałem, że powinienem podać jeszcze jedno, które wyjaśnia 3 różne podejścia do rozwiązania tego problemu.

Podejście 1

Jawnie przypisz DateTimewłaściwość public virtual DateTime Start { get; set; }do datetime2w odpowiedniej kolumnie w tabeli. Ponieważ domyślnie EF zamapuje go na datetime.

Można to zrobić za pomocą płynnego interfejsu API lub adnotacji danych.

  1. Fluent API

    W klasie DbContext nadpisanie OnModelCreatingi skonfigurowanie właściwości Start(ze względów wyjaśnienia jest to właściwość klasy 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. Adnotacja danych

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

Podejście 2

Zainicjuj Startdo wartości domyślnej w konstruktorze EntityClass.To jest dobre, jakby z jakiegoś powodu wartość Startnie została ustawiona przed zapisaniem jednostki do bazy danych start zawsze będzie miał wartość domyślną. Upewnij się, że wartość domyślna jest większa lub równa SqlDateTime.MinValue (od 1 stycznia 1753 do 31 grudnia 9999)

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

Trzecie podejście

Make Startto be of type nullable DateTime -note ? after DateTime-

public virtual DateTime? Start { get; set; }

Więcej wyjaśnień znajdziesz w tym poście

cel
źródło
8

Jeśli DateTimewłaściwości dopuszczają wartość null w bazie danych, pamiętaj, aby użyć DateTime?dla skojarzonych właściwości obiektu lub EF zostanie przekazany DateTime.MinValuedla nieprzypisanych wartości, które są poza zakresem tego, co może obsłużyć typ daty / godziny SQL.

Christopher King
źródło
8

Moim rozwiązaniem było przełączenie wszystkich kolumn z datą i godziną na datetime2 i użycie datetime2 dla wszystkich nowych kolumn. Innymi słowy, spraw, aby EF domyślnie używał datetime2. Dodaj to do metody OnModelCreating w swoim kontekście:

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

To dostanie wszystkie DateTime i DateTime? właściwości wszystkich Twoich podmiotów.

Ogglas
źródło
3

zainicjuj właściwość Start w konstruktorze

Start = DateTime.Now;

To zadziałało, gdy próbowałem dodać kilka nowych pól do tabeli użytkowników ASP .Net Identity Framework (AspNetUsers) przy użyciu Code First. Zaktualizowałem Class - ApplicationUser w IdentityModels.cs i dodałem pole lastLogin typu 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
źródło
2

Opierając się na odpowiedzi użytkownika @ andygjp, lepiej jest zastąpić podstawę Db.SaveChanges() metodę i dodać funkcję nadpisującą dowolną datę, która nie mieści się między SqlDateTime.MinValue i SqlDateTime.MaxValue.

Oto przykładowy kod

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

Zaczerpnięte z komentarza użytkownika @ sky-dev na https://stackoverflow.com/a/11297294/9158120

Hamza Khanzada
źródło
1

Miałem ten sam problem iw moim przypadku ustawiałem datę na new DateTime () zamiast DateTime.

sam
źródło
0

W moim przypadku stało się tak, gdy użyłem encji, a tabela sql ma domyślną wartość datetime == getdate (). więc co zrobiłem, aby ustawić wartość w tym polu.

Ahmad Moayad
źródło
0

Używam Database First i kiedy pojawił się ten błąd, moim rozwiązaniem było wymuszenie ProviderManifestToken = "2005" w pliku edmx (dzięki czemu modele są zgodne z SQL Server 2005). Nie wiem, czy coś podobnego jest możliwe w przypadku Code First.

Sérgio Azevedo
źródło
0

Jedna linia rozwiązuje ten problem:

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

Tak więc w moim kodzie dodałem:

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

Dodanie tego wiersza do podklasy DBContext przesłania void OnModelCreating sekcja powinno działać.

Howie Krauth
źródło
0

W moim przypadku po refaktoryzacji w EF6 moje testy zakończyły się niepowodzeniem z tym samym komunikatem o błędzie, co oryginalny plakat, ale moje rozwiązanie nie miało nic wspólnego z polami DateTime.

Po prostu brakowało mi wymaganego pola podczas tworzenia encji. Po dodaniu brakującego pola błąd zniknął. Mój podmiot ma dwie daty i godziny? pola, ale to nie był problem.

GrayDwarf
źródło