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

141

我正在使用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 数据类型转换为 datetime 数据类型时,导致超出范围的值。该语句已终止。

我完全不知道为什么会发生这种情况,因为一切都很简单。而且,由于没有 edmx 文件可供编辑,我也不确定如何修复它。

有任何想法吗?


3
你能否使用SQL Profiler来查看插入/更新SQL语句?很难确定发生了什么 - 我们没有看到你的初始化器或实体。使用SQL Profiler可以帮助你更好地定位问题。 - Ladislav Mrnka
1
在我的情况下,我已经向表和编辑表单中添加了一个字段,忘记更新Bind Includes,导致我的字段被设置为NULL。因此,该错误帮助纠正了我的疏忽。 - strattonn
15个回答

198

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


14
我曾经没有设置一些初始化对象的日期,所以它们默认为DateTime.MinValue。我现在对这些对象进行了更新。 - Alex Angas
我也犯了同样的错误。我给 ASP.NET Identity 的 ApplicationUser 对象添加了一个自定义日期字段,但忘记将其初始化为有意义的值。 :( - Mike Devenney
在我的情况下,问题出在最小日期上,即01/01/0001会生成错误。 - Machado
我怎样可以检查dateTime.minvalue? - Anabeil
1
有点晚了,但是 @Anabeil 你应该可以在 VS/linqpad 的即时窗口中使用 Console.WriteLine(DateTime.MinValue)。 - SIRHAMY

24

简单易懂。 在你的代码中,将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; }
    }

7
这并非总是如此。 同时也会导致这种错误出现。 - eran otzap

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

14

虽然这个问题很老,且已有很好的答案,但我认为应该再提供一个解决这个问题的方法。

第一种方法

DateTime属性public virtual DateTime Start { get; set; }显式地映射到表中相应列的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; }
    

第二种方法

在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-注意:?需在DateTime之后添加-

public virtual DateTime? Start { get; set; }

更多解释请阅读此文章


13

如果符合你的特定建模需求,你可以将字段设置为可空。空日期不会像默认值一样被强制转换为不在 SQL DateTime 类型范围内的日期。另一个选项是显式地映射到不同的类型,例如:

.HasColumnType("datetime2")

8

我的解决方案是将所有的日期时间列转换成 datetime2,并对任何新列使用 datetime2。换句话说,让 EF 默认使用 datetime2。请将以下代码添加到上下文中的 OnModelCreating 方法中:

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

这将获取所有实体上的DateTime和DateTime?属性。


8
如果你的数据库中的DateTime属性可以为空,那么请确保在相关的对象属性中使用DateTime?,否则EF会传递DateTime.MinValue作为未分配值,而这是超出SQL datetime类型所能处理的范围的。

3

根据用户@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://dev59.com/NG445IYBdhLWcg3w9O7R#11297294 的评论:

对于大部分程序员来说,CPU 和内存之间的关系很难理解。

CPU 是用于运行代码的 “中央处理器”,它有很强的计算能力,但只能处理有限的数据。这时,内存就派上用场了。内存是一个更大的存储区域,它可以存储 CPU 处理的数据和代码。

内存的速度比硬盘要快得多,因此将频繁访问的数据存储在内存中会使程序运行得更快。但是,内存的容量是有限的,如果程序使用的内存超出了可用内存的范围,程序就会崩溃。


3

在构造函数中初始化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; }
    }

1

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接