如何使用 DbContext 和 SetInitializer 修复 datetime2 超出范围的转换错误?

Posted

技术标签:

【中文标题】如何使用 DbContext 和 SetInitializer 修复 datetime2 超出范围的转换错误?【英文标题】:How to fix the datetime2 out-of-range conversion error using DbContext and SetInitializer? 【发布时间】:2011-08-28 09:33:48 【问题描述】:

我正在使用 Entity Framework 4.1 引入的 DbContext 和 Code First API。

数据模型使用stringDateTime等基本数据类型。在某些情况下,我使用的唯一数据注释是[Required],但这不在任何DateTime 属性上。示例:

public virtual DateTime Start  get; set; 

DbContext 子类也很简单,看起来像:

public class EventsContext : DbContext

    public DbSet<Event> Events  get; set; 

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

初始化器将模型中的日期设置为今年或明年的合理值。

但是,当我运行初始化程序时,我在context.SaveChanges() 收到此错误:

datetime2 数据的转换 类型为日期时间数据类型导致 在一个超出范围的值。这 语句已终止。

我完全不明白为什么会这样,因为一切都那么简单。我也不确定如何修复它,因为没有要编辑的 edmx 文件。

有什么想法吗?

【问题讨论】:

您可以使用 SQL Profiler 查看插入/更新 SQL 语句吗?很难说这里发生了什么 - 我们看不到您的初始化程序或实体。 SQL Profiler 将极大地帮助您定位问题。 在我的例子中,我在表格中添加了一个字段并编辑了表单,忘记更新绑定包含并且我的字段被设置为 NULL。所以这个错误帮助纠正了我的疏忽。 相关帖子 - Conversion of a datetime2 data type to a datetime data type results out-of-range value 【参考方案1】:

您必须确保 Start 大于或等于 SqlDateTime.MinValue(1753 年 1 月 1 日) - 默认情况下,Start 等于 DateTime.MinValue(0001 年 1 月 1 日)。

【讨论】:

我的一些初始化器对象没有设置日期,所以它会默认为 DateTime.MinValue。 我也这样做了^。向 asp.net 标识 ApplicationUser 对象添加了一个自定义日期字段,然后忘记将其初始化为有意义的内容。 : ( 在我的例子中,问题出在最小日期,01/01/0001 产生了错误。 如何检查 dateTime.minvalue ? 有点晚了,但是@Anabeil 你应该可以在 VS/linqpad 的即时窗口中只使用 Console.WriteLine(DateTime.MinValue) 【参考方案2】:

如果适合您的特定建模问题,您可以使该字段为空。不会像默认值那样将空日期强制转换为不在 SQL DateTime 类型范围内的日期。另一种选择是显式映射到不同的类型,可能是,

.HasColumnType("datetime2")

【讨论】:

【参考方案3】:

在某些情况下,DateTime.MinValue(或等效的default(DateTime))用于表示未知值。

这种简单的扩展方法可以帮助处理这种情况:

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;
    

用法:

 DateTime? dateToPassOnToDb = tooEarlyDate.ToNullIfTooEarlyForDb();

【讨论】:

【参考方案4】:

在构造函数中初始化 Start 属性

Start = DateTime.Now;

当我尝试使用 Code First 向 ASP .Net Identity Framework 的用户表 (AspNetUsers) 添加一些新字段时,这对我有用。 我更新了 IdentityModels.cs 中的类 - ApplicationUser,并添加了一个 DateTime 类型的字段 lastLogin。

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; 
    

【讨论】:

【参考方案5】:

尽管这个问题已经很老了,并且已经有了很好的答案,但我认为我应该再提出一个来解释解决这个问题的 3 种不同方法。

第一种方法

DateTime 属性public virtual DateTime Start get; set; 显式映射到表中相应列中的datetime2。因为默认情况下 EF 会将其映射到 datetime

这可以通过fluent API或数据注释来完成。

    流畅的 API

    在 DbContext 类中覆盖 OnModelCreating 并配置属性 Start(出于解释原因,它是 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"));
    
    

    数据标注

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

第二种方法

在 EntityClass 构造函数中将Start 初始化为默认值。这很好,就好像由于某种原因,在将实体保存到数据库开始之前未设置Start 的值将始终具有默认值。确保默认值大于或等于SqlDateTime.MinValue(从 1753 年 1 月 1 日到 9999 年 12 月 31 日)

public class EntityClass

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

第三种方法

Start 设为可空类型DateTime -note? 之后DateTime-

public virtual DateTime? Start  get; set; 

更多解释请阅读post

【讨论】:

【参考方案6】:

如果您的 DateTime 属性在数据库中可以为空,那么请务必将 DateTime? 用于关联的对象属性,否则 EF 将传入 DateTime.MinValue 以获取超出 SQL 日期时间类型范围的未分配值可以处理。

【讨论】:

【参考方案7】:

简单。 首先在您的代码上,将 DateTime 的类型设置为 DateTime?。 因此,您可以在数据库中使用可为空的 DateTime 类型。 实体示例:

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; 
    

【讨论】:

并非总是如此。 1/1/0001 12:00:00 AM 也会出现此错误【参考方案8】:

在我的情况下,这发生在我使用实体并且 sql 表的默认值为 datetime == getdate() 时。 所以我做了什么来为这个字段设置一个值。

【讨论】:

【参考方案9】:

我遇到了同样的问题,在我的情况下,我将日期设置为 new DateTime() 而不是 DateTime.Now

【讨论】:

【参考方案10】:

我的解决方案是将所有 datetime 列切换到 datetime2,并将 datetime2 用于任何新列。换句话说,让 EF 默认使用 datetime2。将此添加到上下文的 OnModelCreating 方法中:

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

那会得到所有的DateTime和DateTime吗?您所有实体的属性。

【讨论】:

【参考方案11】:

我使用的是 Database First,当这个错误发生在我身上时,我的解决方案是在 edmx 文件中强制 ProviderManifestToken="2005"(使模型与 SQL Server 2005 兼容)。 不知道 Code First 是否有类似的可能。

【讨论】:

【参考方案12】:

根据用户 @andygjp 的回答,如果您覆盖基本 Db.SaveChanges() 方法并添加一个函数来覆盖任何不在 SqlDateTime.MinValue 和 SqlDateTime.MaxValue 之间的日期,效果会更好。

这里是示例代码

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;
                    
                
            
        
    

取自用户@sky-dev 对https://***.com/a/11297294/9158120的评论

【讨论】:

【参考方案13】:

一行解决了这个问题:

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

所以,在我的代码中,我添加了:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

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

将这一行添加到 DBContext 子类覆盖 void OnModelCreating 部分应该可以工作。

【讨论】:

【参考方案14】:

在我的情况下,在 EF6 中进行了一些重构后,我的测试失败并显示与原始海报相同的错误消息,但我的解决方案与 DateTime 字段无关。

我只是在创建实体时缺少一个必填字段。一旦我添加了缺失的字段,错误就消失了。我的实体确实有两个 DateTime?字段,但它们不是问题。

【讨论】:

【参考方案15】:

如果有人像我一样愚蠢,请仔细检查您约会的年份。我正在从 YYMMDD 格式的文本文件中转换日期,因此正在创建一个年份为 0020,而不是 2020 的日期。明显的错误,但我花了更多时间查看它,但没有看到它!

【讨论】:

以上是关于如何使用 DbContext 和 SetInitializer 修复 datetime2 超出范围的转换错误?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 EntityFramework Core 中使用部分类和部分 OnModelCreating 方法扩展 DbContext

如何使用 DbContext 和 SetInitializer 修复 datetime2 超出范围的转换错误?

如何使用 Dbcontext 添加条目

如何在控制器中使用多个 DBContext

我应该如何编写转换数据的实体框架迁移(最好使用 DbContext)?

使用 Lazyloading,如何在处理完其 DBContext 后使用新的 DbContext 加载相关对象?