ChangeTracker.StateChanged 调用 .Entry() 时触发 DbContext

ChangeTracker.StateChanged of DbContext gets triggered when calling .Entry()

关于如何通过 EF Core 保存数据修改创建和更新日期,我实施了类似的解决方案,正如此处所建议的那样

void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
    if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity)
        entity.LastModified = DateTime.Now;
}

起初我以为只有在调用SaveChanges()时才会触发这个。但显然它也在 Entry()

上被调用
// Get entity
var student = _dbContext.Students.Find(studentId);

// Modify student object
student.Name = "New student name";

// Called Entry(), trigger ChangeTracker.StateChanged
var entry = _dbContext.Entry(student);

// Doesn't trigger ChangeTracker.StateChanged
_dbContext.SaveChanges();

我发现调用_dbContext.Entry(student)时会触发ChangeTracker.StateChanged。然后它不会在调用 _dbContext.SaveChanges() 时再次触发。而且也通过了上面的条件if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity).

我的假设为什么在调用SaveChanges()时没有再次触发,因为调用Entity()后实体没有新的更新。

这导致在调用 .Entry(student) 时分配 LastModified 属性,而不是在调用 .SaveChanges() 时分配。

有没有办法在上面的场景中调用 SaveChanges 时只更新 LastModified 属性 一次?

我建议您可以覆盖 dbContext 中的 SaveChanges 方法。你可以参考下面我常用的代码。

public class ForumContext : DbContext
{
    public ForumContext(DbContextOptions<ForumContext> options) : base(options)
    {

    }
    //other settings
    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        foreach (var entry in ChangeTracker.Entries())
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    ((BaseEntity)entry.Entity).AddedDate = DateTime.Now;
                    ((BaseEntity)entry.Entity).LastModified = DateTime.Now;
                    break;

                case EntityState.Modified:
                    ((BaseEntity)entry.Entity).LastModified = DateTime.Now;
                    break;

                case EntityState.Deleted:
                    entry.State = EntityState.Modified;
                    entry.CurrentValues["IsDeleted"] = true;
                    break;
            }
        }
        return base.SaveChanges(acceptAllChangesOnSuccess);
    }

我想你可能想知道为什么你会得到你在问题中看到的事件。

当您执行行 student.Name = "New student name"; 时,默认情况下不会发生任何事情,因为 EF Core 尚未调用 ChangeTracker.DetectChanges 方法,因此它不知道有任何更改。

但是对 var entry = _dbContext.Entry(student); 的调用随后会运行 ChangeTracker.DetectChanges 的一个版本 - 请参阅下面摘自 EF Core 代码的代码。

public virtual EntityEntry<TEntity> Entry<TEntity>([NotNull] TEntity entity) where TEntity : class
{
    Check.NotNull<TEntity>(entity, nameof (entity));
    this.CheckDisposed();
    EntityEntry<TEntity> entityEntry = this.EntryWithoutDetectChanges<TEntity>(entity);
    //My comment - this runs a version of the DetectChanges method. 
    this.TryDetectChanges((EntityEntry) entityEntry);
    return entityEntry;
}

EF Core 的 Entry 方法执行此操作是因为您可能需要实体的 State,因此它必须调用 DetectChanges 以确保它是最新的。

现在,事实证明,如果您执行以下操作

student.Name = "New student name";
_dbContext.SaveChanges();

然后(在 EF Core 5 预览中,但我认为它在 EF Core 3.1 中是相同的)你得到两个事件。

  1. OldState.EntityState == 未更改,newState.EntityState == 已修改 - 由调用 DetectChanges.
  2. 触发
  3. OldState.EntityState == 已修改,newState.EntityState == 未更改 - 当 SaveChanges 将状态设置为表示数据库与实体 class 匹配时触发。

如果您执行以下操作

student.Name = "New student name";
var entry = _dbContext.Entry(student);
_dbContext.SaveChanges();

然后你会得到同样的事件。 DetectChanges 将被调用两次(一次被 Entry 调用,一次被 SaveChanges 调用),但是在第二次调用 DetectChanges[=28= 时状态没有变化]

您可以在 my unit tests in the repo I am writing to support my book Entity Framework Core in Action 中看到它。我正在写关于这些事件的部分,发现了你的问题,但我会回答它。

我希望它能帮助您理解正在发生的事情,但我应该说建议覆盖 SaveChanges 的其他答案是比使用这些事件更好的解决方案。