如何使用 DbContext 和 SetInitializer 修复 datetime2 超出范围的转换错误
本文关键字:范围 转换 错误 datetime2 DbContext 何使用 SetInitializer 修复 | 更新日期: 2023-09-27 17:56:46
我正在使用实体框架 4.1 引入的 DbContext 和 Code First API。
数据模型使用基本数据类型,例如string
和DateTime
。在某些情况下,我使用的唯一数据注释是 [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 文件可供编辑。
有什么想法吗?
大于或等于 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 或数据注释来完成。
-
流利的 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; }
第二种方法
将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 年的日期。 明显的错误,但我花了更多的时间看它,但没有看到它,而不是我应该看到的!