问题描述
我使用 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
。
现在我的问题是:如何改进我的设计以防止插入此默认值?
我已经有了一些想法:
- 保持上面的代码不变,但请确保在使用实体的任何地方,我都正确设置了值。缺点:不能保证这会随着时间的推移在团队中始终如一地应用。@H_502_42@
- 使
DateTimeOffset
可以为空,这在上面的AddAsync()
调用中实际上会导致 sql 异常。缺点:乍一看,DateTimeOffset? FirstOccurred
可能令人困惑,因为实际的数据库约束不允许 null@H_502_42@ - 移除
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;
}