使用EF Core和DDD时设置实体的UpdatedAt属性

时间:2019-05-20 11:30:11

标签: c# entity-framework-core domain-driven-design entity-framework-core-2.2

我的实体有这个:

public DateTime UpdatedAt { get; private set; }

我想在每次更新实体时都更新该属性,所以我有这个:

private void updateTimeStamp() => UpdatedAt = DateTime.UtcNow;

但是然后我需要记住在使实体发生变异的每种方法中都调用它,例如:

public void addOrder(Order order) {
  //blah blah
  updateTimeStamp();     // <----- musn't forget this!
}

我经常忘记调用该方法,因此我在考虑替代方法。我不知道哪个最好,或者是否有我没有考虑的东西。

选项1:初始化属性

public DateTime UpdatedAt { get; private set; } = DateTime.UtcNow;

问题:即使我不想更新实体,也会每次设置一次。

选项2:属性

[UpdateTimestamp]
public void addOrder(Order order) {
  //blah blah
}

问题:这并不能改善情况,基本上与原始问题相同。

选项3:在context.SaveChanges()

中自动更新
ChangeTracker.Entries()
  .Where(e => e.Entity is MyEntity)
  .Where(e => e.State == EntityState.Modified)
  .Where(e => e.Entity as MyEntity)
  .ToList()
  .ForEach(e => { e.UpdatedAt = DateTime.UtcNow; });

问题:UpdatedAt现在需要一个公共设置器。这违反了DDD原则。这是最简单的选择,但这意味着我必须放弃DDD的“始终有效”。

选项3:与3相同,但使用反射
与选项3相似,即使它具有私有设置程序,我也可以使用反射来更改属性。这很容易做到,而且反射和“慢”的事实并不重要,因为与数据库写操作相比,时间可以忽略不计。

问题:在基础架构而不是域中进行更改只是感觉不对。

否则,这是一个不错的选择。它不是纯DDD,但至少可以让我保护实体的不变式。

选项4:卸载到数据库提供程序或数据库本身

builder.Property(e => e.UpdatedAt).ValueGeneratedOnUpdate();
// or
builder.Property(e => e.UpdatedAt).HasComputedColumnSql("GETDATE()");

问题:不适用于所有提供程序(因为我使用的Npgsql不支持计算列,因此不适用于我)。这也将问题转移到基础架构中并移出了域。所以我觉得我再次打破了DDD。

那么...有什么我忘记的东西吗?如果您正在执行DDD,该如何处理?

1 个答案:

答案 0 :(得分:1)

正如您已经指出的那样,选项1不适合您,因为您可以简单地获得订单而不更新任何内容,并且日期也将发生更改。

选项2可能还会违反DDD原则,因为您可以修改订单,并且在更改特定方法之前不会更改更新日期。

您剩下的只是选项3,考虑到DDD原理,它似乎不太麻烦,而且使用起来也最舒适。 你应该这样走。只需使用您的UpdatedAt属性和updateTimeStamp方法创建一个接口,然后在您的实体上实现即可。您可能应该创建一个基类并实现该方法。然后按照您的建议在SaveChanges方法中获取修改后的条目,并使用该方法而不是属性来避免将setter设置为私有。

有第4个选项,您可以使用反射和代理类,但仅满足您的要求就很困难了。