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

本文关键字:范围 转换 错误 datetime2 DbContext 何使用 SetInitializer 修复 | 更新日期: 2023-09-27 17:56:46

我正在使用实体框架 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()收到此错误:

日期时间2 数据的转换 键入生成的日期时间数据类型 值超出范围。这 声明已终止。

我不明白为什么会发生这种情况,因为一切都那么简单。我也不确定如何解决它,因为没有 edmx 文件可供编辑。

有什么想法吗?

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

必须确保 Start

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

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

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

在某些情况下,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();

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

第一种方法

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

这可以通过流畅的 API 或数据注释来完成。

  1. 流利的 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"));
    }
    
  2. 数据注释

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

第二种方法

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 - 注意 ?DateTime 之后 -

public virtual DateTime? Start { get; set; }

有关更多解释,请阅读此帖子

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

.HasColumnType("datetime2")
如果

DateTime属性在数据库中可为空,请确保对关联的对象属性使用 DateTime?,否则 EF 将传入DateTime.MinValue未分配值,这超出了 SQL 日期时间类型可以处理的范围。

我的解决方案是将所有日期时间列切换到 datetime2,并将 datetime2 用于任何新列。换句话说,默认情况下使 EF 使用 datetime2。将此添加到上下文中的 OnModelCreate 方法中:

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

这将获取所有实体上的所有日期时间和日期时间? 属性。

构造函数中初始化 Start 属性

Start = DateTime.Now;

当我尝试使用代码优先向ASP .Net Identity Framework的用户表(AspNetUsers)添加一些新字段时,这对我有用。我在 IdentityModels 中更新了类 - ApplicationUser.cs并添加了一个类型为 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; }
    }

根据用户@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://stackoverflow.com/a/11297294/9158120 的评论

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

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

我正在使用数据库优先,当此错误发生在我身上时,我的解决方案是在edmx文件中强制ProviderManifestToken="2005"(使模型与SQL Server 2005兼容)。不知道代码优先是否可以使用类似的东西。

一行可以解决此问题:

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

因此,在我的代码中,我添加了:

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

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

就我而言,在 EF6 中进行一些重构后,我的测试失败,出现与原始海报相同的错误消息,但我的解决方案与 DateTime 字段无关。

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

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

相关文章: