【问题标题】:Set entity's UpdatedAt property when using EF Core and DDD使用 EF Core 和 DDD 时设置实体的 UpdatedAt 属性
【发布时间】:2019-05-20 20:03:24
【问题描述】:

我的实体有这个:

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:卸载到 db 提供程序或 db 本身

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

问题:不适用于所有提供程序(不适用于我,因为我使用的是不支持计算列的 Npgsql)。这也将问题移入基础设施并移出域。所以我觉得我又要打破 DDD 了。

那么……有什么我忘记了吗?如果你在做 DDD,你如何处理这个?

【问题讨论】:

  • 为什么需要公共设置器?
  • @Matthiee 我知道阴影属性...但我不确定它们在这种情况下会有什么帮助?
  • 但是你可以使用 e.SetUpdatedAt() 来代替,并保持属性设置器私有。
  • @DavidG 我不确定我是否遵循你的想法......但这不仅仅是关于属性设置器是否是公共的,或者我是否将它隐藏在公共方法后面 - 这里的 DDD 很重要是 1) 除了实体本身之外,任何代码都不应该更改它,以及 2) 只有当某些东西 *实际改变了

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


【解决方案1】:

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

选项 2 也可能违反 DDD 原则,因为您可以修改您的订单并且在您点击该特定方法之前没有更改您的更新日期。

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

还有第 4 个选项,您可以使用反射和代理类,但这对于您的要求来说是一个很长的机会......

【讨论】:

  • 感谢您的帮助。我从选项 4 开始......你认为现在“最好”的是什么?同意你关于从上下文中调用 updateTimeStamp() 方法的想法,这就是我最初的做法,但感觉很不对(而且它与仅将属性设置为 public 并没有太大区别)。我也考虑过反射的想法,但似乎是一种完成它的方法(我现在要把它添加到列表中)。我认为这是 DDD 变得不切实际的情况之一! :(
  • 如果你真的需要坚持DDD原则,我猜你必须为每个属性创建一个私有属性,将公共属性绑定到私有并调用setter中的更新方法...您应该看看 mvvm 模式如何绑定属性,也许您可​​以在那里找到适合您的东西。最后,我认为你不应该把自己锁在单一的做事方式上。有些设计没有针对某个特定问题的简单解决方案,您必须做很多工作才能尝试遵循它。有时为了简单和可读性而做出让步是可以的。
  • 同意务实。
猜你喜欢
  • 1970-01-01
  • 2020-04-16
  • 1970-01-01
  • 2021-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-15
相关资源
最近更新 更多