Wie kann der Konvertierungsfehler von datetime2 außerhalb des Bereichs mithilfe von DbContext und SetInitializer behoben werden?

136

Ich verwende die mit Entity Framework 4.1 eingeführten APIs DbContext und Code First.

Das Datenmodell verwendet grundlegende Datentypen wie stringund DateTime. Die einzige Datenanmerkung, die ich in einigen Fällen verwende, ist [Required], aber das gilt nicht für eine der DateTimeEigenschaften. Beispiel:

public virtual DateTime Start { get; set; }

Die DbContext-Unterklasse ist ebenfalls einfach und sieht folgendermaßen aus:

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

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

Der Initialisierer setzt die Daten im Modell entweder in diesem oder im nächsten Jahr auf sinnvolle Werte.

Wenn ich jedoch den Initialisierer ausführe, wird folgende Fehlermeldung angezeigt context.SaveChanges():

Die Konvertierung eines Datetime2-Datentyps in einen Datetime-Datentyp führte zu einem Wert außerhalb des Bereichs. Die Anweisung wurde beendet.

Ich verstehe nicht, warum das überhaupt passiert, weil alles so einfach ist. Ich bin mir auch nicht sicher, wie ich das beheben soll, da keine edmx-Datei zum Bearbeiten vorhanden ist.

Irgendwelche Ideen?

Alex Angas
quelle
2
Können Sie SQL Profiler verwenden, um das Einfügen / Aktualisieren von SQL-Anweisungen anzuzeigen? Es ist ziemlich schwer zu sagen, was hier vor sich geht - wir sehen Ihren Initialisierer oder Ihre Entitäten nicht. SQL Profiler hilft Ihnen sehr bei der Lokalisierung von Problemen.
Ladislav Mrnka
1
In meinem Fall hatte ich ein Feld zu einer Tabelle hinzugefügt und ein Formular bearbeitet, vergessen, die Bind Includes zu aktualisieren, und mein Feld wurde auf NULL gesetzt. Der Fehler hat mir also geholfen, mein Versehen zu korrigieren.
Strattonn

Antworten:

186

Sie müssen sicherstellen, dass Start größer oder gleich SqlDateTime.MinValue (1. Januar 1753) ist - standardmäßig ist Start gleich DateTime.MinValue (1. Januar 0001).

andygjp
quelle
14
Ich hatte einige meiner Initialisierungsobjekte ohne festgelegtes Datum belassen, daher wurde standardmäßig DateTime.MinValue verwendet.
Alex Angas
Das habe ich auch gemacht ^. Es wurde ein benutzerdefiniertes Datumsfeld zum Anwendungsbenutzerobjekt der asp.net-Identität hinzugefügt und dann vergessen, es mit etwas zu initialisieren, das Sinn machte. : (
Mike Devenney
In meinem Fall lag das Problem am Mindestdatum, am 01.01.0001 wurde der Fehler generiert.
Machado
Wie kann ich dateTime.minvalue überprüfen?
Anabeil
1
Ein bisschen spät, aber @Anabeil sollten Sie in der Lage sein, nur Console.WriteLine (DateTime.MinValue) im unmittelbaren Fenster von VS / linqpad
SIRHAMY
22

Einfach. Setzen Sie zuerst in Ihrem Code den Typ von DateTime auf DateTime?. Sie können also mit dem nullbaren DateTime-Typ in der Datenbank arbeiten. Entitätsbeispiel:

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
quelle
6
das ist nicht immer der Fall. {01.01.0001 00:00:00} Dieser Fehler tritt ebenfalls auf
eran otzap
20

In einigen Fällen DateTime.MinValue(oder gleichwertig default(DateTime)) wird ein unbekannter Wert angegeben.

Diese einfache Erweiterungsmethode kann bei solchen Situationen helfen:

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

Verwendung:

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

Sie können das Feld auf Null setzen, wenn dies Ihren spezifischen Modellierungsproblemen entspricht. Ein Nulldatum wird nicht zu einem Datum gezwungen, das nicht im Bereich des SQL DateTime-Typs liegt, wie dies bei einem Standardwert der Fall wäre. Eine andere Möglichkeit besteht darin, explizit einem anderen Typ zuzuordnen, möglicherweise mit,

.HasColumnType("datetime2")
Brian Kretzler
quelle
12

Obwohl diese Frage ziemlich alt ist und es bereits gute Antworten gibt, dachte ich, ich sollte noch eine hinzufügen, die 3 verschiedene Ansätze zur Lösung dieses Problems erklärt.

1. Ansatz

Map Explizit DateTimeEigenschaft public virtual DateTime Start { get; set; }auf datetime2Spalte in der Tabelle in entspricht. Weil EF es standardmäßig zuordnet datetime.

Dies kann durch fließende API- oder Datenanmerkungen erfolgen.

  1. Fließende API

    In der DbContext-Klasse überschreiben OnModelCreatingund konfigurieren Sie die Eigenschaft Start(aus Erklärungsgründen ist sie eine Eigenschaft der EntityClass-Klasse).

    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. Datenanmerkung

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

2. Ansatz

Initialisieren Sie Startim EntityClass-Konstruktor auf einen Standardwert. Dies ist gut, als ob aus irgendeinem Grund der Wert von Startnicht festgelegt wird, bevor die Entität im Datenbankstart gespeichert wird. Dies hat immer einen Standardwert. Stellen Sie sicher, dass der Standardwert größer oder gleich SqlDateTime.MinValue ist (vom 1. Januar 1753 bis 31. Dezember 9999).

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

3. Ansatz

Machen Sie Startvom Typ nullable DateTime -note ? nach DateTime-

public virtual DateTime? Start { get; set; }

Weitere Erklärungen finden Sie in diesem Beitrag

Ziel
quelle
8

Wenn Ihre DateTimeEigenschaften in der Datenbank nullwertfähig sind, müssen Sie sie DateTime?für die zugeordneten Objekteigenschaften verwenden. Andernfalls wird EF DateTime.MinValuefür nicht zugewiesene Werte übergeben, die außerhalb des Bereichs liegen, den der SQL-Datetime-Typ verarbeiten kann.

Christopher King
quelle
8

Meine Lösung bestand darin, alle datetime-Spalten auf datetime2 umzustellen und datetime2 für alle neuen Spalten zu verwenden. Mit anderen Worten, lassen Sie EF standardmäßig datetime2 verwenden. Fügen Sie dies der OnModelCreating-Methode in Ihrem Kontext hinzu:

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

Das wird alle DateTime und DateTime bekommen? Eigenschaften für alle Ihre Entitäten.

Ogglas
quelle
3

Initialisieren Sie die Start-Eigenschaft im Konstruktor

Start = DateTime.Now;

Dies funktionierte für mich, als ich versuchte, mithilfe von Code First einige neue Felder zur Benutzertabelle (AspNetUsers) des ASP .Net Identity Framework hinzuzufügen. Ich habe den Class - ApplicationUser in IdentityModels.cs aktualisiert und ein Feld lastLogin vom Typ DateTime hinzugefügt.

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
quelle
2

Basierend auf der Antwort von user @ andygjp ist es besser, wenn Sie die Basismethode überschreiben Db.SaveChanges()und eine Funktion hinzufügen, um jedes Datum zu überschreiben, das nicht zwischen SqlDateTime.MinValue und SqlDateTime.MaxValue liegt.

Hier ist der Beispielcode

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

Entnommen aus dem Kommentar von user @ sky-dev unter https://stackoverflow.com/a/11297294/9158120

Hamza Khanzada
quelle
1

Ich hatte das gleiche Problem und in meinem Fall habe ich das Datum auf new DateTime () anstelle von DateTime.Now gesetzt

Sam
quelle
0

In meinem Fall geschah dies, als ich eine Entität verwendete und die SQL-Tabelle den Standardwert datetime == getdate () hatte. Also, was ich getan habe, um einen Wert für dieses Feld festzulegen.

Ahmad Moayad
quelle
0

Ich verwende zuerst die Datenbank. Als mir dieser Fehler auftrat, bestand meine Lösung darin, ProviderManifestToken = "2005" in der edmx-Datei zu erzwingen (wodurch die Modelle mit SQL Server 2005 kompatibel wurden). Ich weiß nicht, ob für Code First etwas Ähnliches möglich ist.

Sérgio Azevedo
quelle
0

Eine Zeile behebt dies:

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

Also habe ich in meinem Code hinzugefügt:

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

Das Hinzufügen dieser einen Zeile zur DBContext-Unterklasse überschreibt den Abschnitt void OnModelCreating.

Howie Krauth
quelle
0

In meinem Fall schlugen meine Tests nach einigen Umgestaltungen in EF6 mit derselben Fehlermeldung wie das Originalposter fehl, aber meine Lösung hatte nichts mit den DateTime-Feldern zu tun.

Beim Erstellen der Entität fehlte mir nur ein erforderliches Feld. Nachdem ich das fehlende Feld hinzugefügt hatte, verschwand der Fehler. Meine Entität hat zwei DateTime? Felder, aber sie waren nicht das Problem.

GrayDwarf
quelle