Bagaimana cara memperbaiki kesalahan konversi di luar rentang datetime2 menggunakan DbContext dan SetInitializer?

138

Saya menggunakan DbContext dan Code First API yang diperkenalkan dengan Entity Framework 4.1.

Model data menggunakan tipe data dasar seperti stringdan DateTime. Satu-satunya anotasi data yang saya gunakan dalam beberapa kasus adalah [Required], tetapi itu tidak ada di DateTimeproperti mana pun. Contoh:

public virtual DateTime Start { get; set; }

The DbContext subclass juga sederhana dan terlihat seperti:

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

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

The initializer set tanggal dalam model dengan nilai-nilai yang masuk akal baik tahun ini atau tahun depan.

Namun ketika saya menjalankan penginisialisasi, saya mendapatkan kesalahan ini di context.SaveChanges():

Konversi tipe data datetime2 ke tipe data datetime menghasilkan nilai di luar rentang. Pernyataan tersebut telah dihentikan.

Saya tidak mengerti mengapa ini terjadi sama sekali karena semuanya sangat sederhana. Saya juga tidak yakin bagaimana cara memperbaikinya karena tidak ada file edmx untuk diedit.

Ada ide?

Alex Angas
sumber
3
Dapatkah Anda menggunakan SQL Profiler untuk melihat penyisipan / pembaruan pernyataan SQL? Cukup sulit untuk mengatakan apa yang terjadi di sini - kami tidak melihat penginisialisasi atau entitas Anda. SQL Profiler akan banyak membantu Anda untuk melokalkan masalah.
Ladislav Mrnka
1
Dalam kasus saya, saya telah menambahkan bidang ke tabel dan mengedit formulir, lupa memperbarui Bind Includes dan bidang saya disetel ke NULL. Jadi, kesalahan tersebut membantu memperbaiki pengawasan saya.
strattonn

Jawaban:

190

Anda harus memastikan bahwa Mulai lebih besar dari atau sama dengan SqlDateTime.MinValue (1 Januari 1753) - secara default Mulai sama dengan DateTime.MinValue (1 Januari 0001).

andygjp
sumber
14
Saya telah meninggalkan beberapa objek penginisialisasi saya tanpa tanggal yang ditetapkan, jadi itu akan menjadi default ke DateTime.MinValue.
Alex Angas
Saya melakukan itu juga ^. Menambahkan bidang tanggal khusus ke objek ApplicationUser identitas asp.net lalu lupa menginisialisasinya ke sesuatu yang masuk akal. : (
Mike Devenney
Dalam kasus saya, masalahnya ada pada tanggal min, 01/01/0001 menghasilkan kesalahan.
Machado
bagaimana saya dapat memeriksa dateTime.minvalue?
Anabeil
1
Agak terlambat, tapi @Anabeil Anda seharusnya bisa hanya Console.WriteLine (DateTime.MinValue) di jendela langsung VS / linqpad
SIRHAMY
22

Sederhana. Pada kode Anda terlebih dahulu, setel jenis DateTime ke DateTime ?. Jadi Anda bisa bekerja dengan tipe DateTime nullable dalam database. Contoh entitas:

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
sumber
6
itu tidak selalu terjadi. {1/1/0001 12:00:00 AM} juga membuat kesalahan ini muncul
eran otzap
20

Dalam beberapa kasus, DateTime.MinValue(atau setara, default(DateTime)) digunakan untuk menunjukkan nilai yang tidak diketahui.

Metode penyuluhan sederhana ini dapat membantu menangani situasi seperti itu:

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

Pemakaian:

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

Anda dapat membuat kolom menjadi null, jika itu sesuai dengan masalah pemodelan spesifik Anda. Tanggal nol tidak akan dipaksakan ke tanggal yang tidak berada dalam rentang jenis SQL DateTime seperti nilai default. Pilihan lainnya adalah memetakan secara eksplisit ke jenis yang berbeda, mungkin dengan,

.HasColumnType("datetime2")
Brian Kretzler
sumber
12

Meskipun pertanyaan ini cukup lama dan sudah ada jawaban yang bagus, saya pikir saya harus mengajukan satu lagi yang menjelaskan 3 pendekatan berbeda untuk menyelesaikan masalah ini.

Pendekatan Pertama

Memetakan DateTimeproperti secara eksplisit public virtual DateTime Start { get; set; }ke datetime2dalam kolom yang sesuai dalam tabel. Karena secara default EF akan memetakannyadatetime .

Ini dapat dilakukan dengan API atau anotasi data yang lancar.

  1. API Lancar

    Di kelas DbContext, overide OnModelCreatingdan konfigurasi properti Start(untuk alasan penjelasan, ini adalah properti kelas 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. Anotasi data

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

Pendekatan ke-2

Inisialisasi Startke nilai default dalam konstruktor EntityClass. Ini bagus karena jika karena alasan tertentu nilai Starttidak disetel sebelum menyimpan entitas ke dalam database mulai akan selalu memiliki nilai default. Pastikan nilai default lebih besar dari atau sama dengan SqlDateTime.MinValue (dari 1 Januari 1753 hingga 31 Desember 9999)

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

Pendekatan ke-3

Buat Startmenjadi tipe nullable DateTime -catatan ? setelah DateTime-

public virtual DateTime? Start { get; set; }

Untuk penjelasan lebih lanjut baca posting ini

saya
sumber
8

Jika DateTimeproperti Anda nullable dalam database, pastikan untuk menggunakan DateTime?properti objek terkait atau EF akan meneruskan DateTime.MinValueuntuk nilai yang tidak ditetapkan yang berada di luar rentang yang dapat ditangani oleh tipe datetime SQL.

Christopher King
sumber
8

Solusi saya adalah mengalihkan semua kolom datetime ke datetime2, dan menggunakan datetime2 untuk kolom baru. Dengan kata lain, buat EF menggunakan datetime2 secara default. Tambahkan ini ke metode OnModelCreating pada konteks Anda:

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

Itu akan mendapatkan semua DateTime dan DateTime? properti di semua entitas Anda.

Ogglas
sumber
3

menginisialisasi properti Start di konstruktor

Start = DateTime.Now;

Ini bekerja untuk saya ketika saya mencoba untuk menambahkan beberapa bidang baru ke tabel Pengguna ASP .Net Identity Framework (AspNetUsers) menggunakan Kode Pertama. Saya memperbarui Class - ApplicationUser di IdentityModels.cs dan saya menambahkan field lastLogin jenis 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
sumber
2

Berdasarkan jawaban pengguna @ andygjp, lebih baik jika Anda mengganti basis Db.SaveChanges() metode dan menambahkan fungsi untuk mengganti tanggal mana pun yang tidak berada di antara SqlDateTime.MinValue dan SqlDateTime.MaxValue.

Berikut ini contoh kode

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

Diambil dari komentar pengguna @ sky-dev di https://stackoverflow.com/a/11297294/9158120

Hamza Khanzada
sumber
1

Saya memiliki masalah yang sama dan dalam kasus saya, saya mengatur tanggal ke DateTime () baru, bukan DateTime. Sekarang

sam
sumber
0

Dalam kasus saya ini terjadi ketika saya menggunakan entitas dan tabel sql memiliki nilai default dari datetime == getdate (). jadi apa yang saya lakukan untuk menetapkan nilai ke bidang ini.

Ahmad Moayad
sumber
0

Saya menggunakan Database First dan ketika kesalahan ini terjadi pada saya, solusi saya adalah memaksa ProviderManifestToken = "2005" di file edmx (membuat model kompatibel dengan SQL Server 2005). Tidak tahu apakah sesuatu yang serupa dimungkinkan untuk Code First.

Sérgio Azevedo
sumber
0

Satu baris memperbaiki ini:

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

Jadi, dalam kode saya, saya menambahkan:

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

Menambahkan satu baris itu ke subclass DBContext menimpa bagian OnModelCreating void seharusnya berfungsi.

Howie Krauth
sumber
0

Dalam kasus saya, setelah beberapa refactoring di EF6, pengujian saya gagal dengan pesan kesalahan yang sama seperti poster asli tetapi solusi saya tidak ada hubungannya dengan bidang DateTime.

Saya baru saja kehilangan bidang wajib saat membuat entitas. Setelah saya menambahkan bidang yang hilang, kesalahannya hilang. Entitas saya memang memiliki dua DateTime? bidang tapi itu bukan masalah.

GrayDwarf
sumber