如何防止插入 DateTimeOffset 的默认值?

问题描述

我使用 Entity Framework Core 3.1.7 并创建了一个名为 Event 的实体,我将其设置如下:

public class Event
{
    public long Id { get; set; }
    public DateTimeOffset FirstOccurred { get; set; }
}

实体配置如下所示:

builder.Property(e => e.FirstOccurred)
    .Isrequired();

我使用我的 dbContext 来持久化实体,如下所示:

await dbContext.Events.AddAsync(new Event());

在这种情况下,我错误地预期会在数据库级别抛出异常,因为该值不能为空。

实际发生的是:实体很高兴地将 FirstOccurred 设置为 0001-01-01T00:00:00+00:00

这是有道理的,因为使用了认值 DateTimeOffset

现在我的问题是:如何改进我的设计以防止插入此认值?

我已经有了一些想法:

  1. 保持上面的代码不变,但请确保在使用实体的任何地方,我都正确设置了值。缺点:不能保证这会随着时间的推移在团队中始终如一地应用。@H_502_42@
  2. 使 DateTimeOffset 可以为空,这在上面的 AddAsync() 调用中实际上会导致 sql 异常。缺点:乍一看,DateTimeOffset? FirstOccurred 可能令人困惑,因为实际的数据库约束不允许 null@H_502_42@
  3. 移除 set;FirstOccurred 并创建一个需要设置此属性的构造函数,例如new Event(DateTimeOffset.Now) @H_502_42@

解决方法

我认为你的最后一个想法是正确的。

移除 set;FirstOccurred 并创建一个需要设置此属性的构造函数,例如new Event(DateTimeOffset.Now)

在没有时间戳的情况下跟踪事件没有意义,使用时间戳的默认值当然也没有意义。

将模型更改为需要时间戳值可确保您不会将默认数据写入记录,并防止在相应表列不可为空时看到可空模型字段而造成混淆。

public class Event {
    public Event (DateTimeOffset firstOccurred) { FirstOcurred = firstOcurred; }
    public long Id { get; set; }
    public DateTimeOffset FirstOccurred { get; set; }
}

只是 documentation 中的一个注释,不要删除 set; 访问器,如果您不想在构造后更改值,只需将其标记为 private

一旦通过构造函数设置了属性,就可以将其中的一些设置为只读。 EF Core 支持这一点,但需要注意以下几点:

没有 setter 的属性不是按惯例映射的。 (这样做往往会映射不应映射的属性,例如计算属性。) 使用自动生成的键值需要一个可读写的键属性,因为键值需要在插入新实体时由键生成器设置。 避免这些事情的一个简单方法是使用私有 setter。

当然,您也可以通过覆盖属性的默认值来保持无参数构造函数的灵活性。

public class Event {
    public long Id { get; set; }
    public DateTimeOffset FirstOccurred { get; set; } = DateTimeOffset.Now;
}