数据修改之后EntityState.Unchanged还是true

背景说明:我使用的后端是ABP,里面的数据库操作用的是EF Core.
我希望每次的数据变化,比如新增、修改、删除都记录到一个表(Audit)里面去.
之前已经实现效果了,但是这两天发现对数据的修改,根本不会写入到Audit表。
不知道问题出在哪里。数据保存是没有问题,但是就是无法记录修改部分。
大致的代码如下

private readonly IRepository<User, Guid> _userRepository;
。
。
。
User user = ObjectMapper.Map<User>(userDto);
//修改用户表中的某条记录
await _userRepository.UpdateAsync(user);
//使用DbContext().SaveChanges(),触发数据库变化事件
_userRepository.GetDbContext().SaveChanges();

然后在DBContext类中会接收数据变化事件

//数据变化,都会被这个方法拦截
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    AuditList = new List<Audit>();
    var auditEntries = OnBeforeSaveChanges();
    var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
    await OnAfterSaveChanges(auditEntries);
    return result;
}
private List<AuditEntry> OnBeforeSaveChanges()
{
    ChangeTracker.DetectChanges(); //捕获各个表数据的变化
    var auditEntries = new List<AuditEntry>();
    foreach (var entry in ChangeTracker.Entries())
    {
        //如果变数据没有发生实际的变化,则会continue; 但是目前的情况是,
        //尽管数据发生了变化,entry.State依然是Unchanged,所以导致无法记录修改内容
        if (entry.Entity is Audit || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
            continue;

        var auditEntry = new AuditEntry(entry);
        auditEntry.TableName = entry.Metadata.GetTableName();
        auditEntries.Add(auditEntry);

        foreach (var property in entry.Properties)
        {
            。。。 //对变化属性值的处理
        }
    }

    // Save audit entities that have all the modifications
    foreach (var auditEntry in auditEntries.Where(_ => !_.HasTemporaryProperties))
    {
        Audits.Add(auditEntry.ToAudit());
    }

    // keep a list of entries where the value of some properties are unknown at this step
    return auditEntries.Where(_ => _.HasTemporaryProperties).ToList();
}

问题就出现在
if (entry.Entity is Audit || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
continue;
中,数据修改之后EntityState.Unchanged还是true,正常情况是false才对,这样才能继续往下走,
继而记录数据变化部分

我的提问:会是什么原因导致,数据实际变化了,但是却捕获不到呢?有什么解决方案吗?
恳请大家帮忙指点,感谢!

出现这种情况的原因可能是因为EF Core的Change Tracker没有正确地跟踪实体的状态变化,导致无法正确地检测到实体的修改。这可能与一些因素有关,例如:

可能在修改实体后没有调用DbContext.SaveChanges()方法,导致EF Core无法正确跟踪实体状态的变化。
可能实体的状态被手动更改为EntityState.Unchanged,从而导致EF Core无法正确检测到实体的修改。
可能在修改实体时,只是修改了导航属性,而没有修改实体本身的属性,从而导致EF Core无法正确检测到实体的修改。
为了解决这个问题,你可以尝试以下方法:

确保在修改实体后,调用DbContext.SaveChanges()方法,以便让EF Core正确地跟踪实体状态的变化。
在修改实体时,不要手动更改实体的状态为EntityState.Unchanged,让EF Core自动跟踪实体状态的变化。
如果实体的修改只涉及到导航属性的修改,而没有修改实体本身的属性,可以尝试手动更改实体的状态,以便EF Core正确地跟踪实体状态的变化。例如:

// 修改实体的导航属性
user.Role = newRole;

// 手动将实体状态设置为Modified
_userRepository.GetDbContext().Entry(user).State = EntityState.Modified;

// 保存修改
_userRepository.GetDbContext().SaveChanges();

这样,即使实体的修改只涉及到导航属性的修改,EF Core也能正确地检测到实体状态的变化,并记录到Audit表中。

另外,还可以在修改实体时,使用DbContext.Entry()方法获取实体的状态信息,以便更好地跟踪实体状态的变化。例如:

// 获取实体的状态信息
var entry = _userRepository.GetDbContext().Entry(user);
entry.State = EntityState.Modified;



// 保存修改
_userRepository.GetDbContext().SaveChanges();

这样,即使实体的状态发生了变化,EF Core也能正确地检测到实体状态的变化,并记录到Audit表中。

方案二:

在 EF Core 中,当您执行更新操作时,EF Core 会自动检测哪些属性已更改,并将其标记为 "Modified"。但是在某些情况下,这种检测可能无法正常工作,导致 EF Core 将实体状态标记为 "Unchanged",即使实体的属性已更改。

这种情况可能发生在以下情况下:

某些属性的值未更改,但是它们的状态已更改为 "Modified"。

在更改实体属性之前,另一个操作已更改了与该实体相关的数据,因此 EF Core 无法正确检测到属性的更改。

解决此问题的一种方法是手动将实体状态标记为 "Modified"。您可以尝试在更新操作之前手动标记实体状态。例如:

csharp
Copy
_userRepository.GetDbContext().Entry(user).State = EntityState.Modified;
这将强制 EF Core 将实体状态标记为 "Modified",即使属性的状态未更改。

另外,您可以尝试使用 DbContext.Update() 方法替代 _userRepository.UpdateAsync() 方法,因为 DbContext.Update() 方法可以自动将实体状态标记为 "Modified"。例如:

csharp
Copy
_userRepository.GetDbContext().Update(user);
这将自动将实体状态标记为 "Modified",并且在调用 SaveChanges() 方法时,EF Core 将正确检测到实体属性的更改,并将其记录到 Audit 表中。

如果您仍然遇到问题,您可以尝试在 SaveChanges() 方法中打印出所有实体的状态,以便更好地了解 EF Core 如何处理它们。例如:

csharp
Copy
foreach (var entry in ChangeTracker.Entries())
{
Console.WriteLine($"{entry.Entity.GetType().Name} - {entry.State}");
}
这将输出每个实体的类型和状态。如果某个实体的状态不符合预期,请检查该实体的更改是否正确地标记为 "Modified",或者是否有其他操作已更改了该实体的状态。

当调用SaveChanges方法的时候,EF会根据EntityState这个枚举检测到实体的状态,然后执行相应的增/删/改操作。它们的具体意思分别为:

Detached:对象存在,但未由对象服务跟踪。在创建实体之后、但将其添加到对象上下文之前,该实体处于此状态;
Unchanged:自对象加载到上下文中后,或自上次调用 System.Data.Objects.ObjectContext.SaveChanges() 方法后,此对象尚未经过修改;
Added:对象已添加到对象上下文,但尚未调用 System.Data.Objects.ObjectContext.SaveChanges() 方法;
Deleted:使用 System.Data.Objects.ObjectContext.DeleteObject(System.Object) 方法从对象上下文中删除了对象;
Modified:对象已更改,但尚未调用 System.Data.Objects.ObjectContext.SaveChanges() 方法。

您好,在您的代码中,您捕获了所有状态为 Unchanged 的实体。EF Core 实际上不会将更改保存到数据库中,除非您明确地调用 SaveChanges 方法或将状态设置为 Modified。因此,当状态为 Unchanged 时,您不会记录任何内容。如果您想记录所有实体的更改,您需要使用以下方法:

if (entry.Entity is Audit || entry.State == EntityState.Detached) 
{ 
    continue; 
}

1.未正确跟踪实体的状态:Entity Framework会自动跟踪实体的状态。如果您手动修改了实体属性,但是没有把实体对象添加到DbContext实例中,则实体的状态不会被正确跟踪,可能导致实体状态没有变化。

2.属性修改后的值和原值相等:在Entity Framework中,如果实体的属性修改后的值和原值相等,则实体的状态不会被变更为EntityState.Modified。这是因为Entity Framework假设实体没有发生任何更改,因此不需要将实体更新到数据库中。

3.其他原因:有时候,实体状态没有变化,可能是由于其他一些错误导致的,例如原始实体没有正确设置主键值等。

针对这个问题,建议您进行以下处理步骤:

  1. 确认修改属性后的值和原值不等,确保实体的状态正确变更到EntityState.Modified。
  2. 确认实体是否已经正确添加到DbContext实例中,如果没有添加到DbContext实例中,请使用DbContext.Add()方法将实体添加到DbContext实例中。
  3. 检查实体的主键是否设置正确,如果没有正确设置实体的主键,则可能导致实体的状态没有正确变化。

从您提供的代码来看,将一个 User 对象通过映射赋值给了 userDto,然后在修改 userDto 过程中并没有对 Audit 记录进行修改,因此不会有数据写入到 Audit 表中。

如果您使用的是 EF Core,可以考虑使用 Change Tracker 在对象被修改时记录 Audit 记录。您可以在 SaveChanges 之前订阅 Change Tracker 的事件,以在每次保存更改时记录 Audit 记录。示例代码示例如下:

using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;

public class MyDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Audit> Audits { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // configure Audit entity
    }

    public override int SaveChanges()
    {
        // get changed entities
        var changedEntities = ChangeTracker.Entries().Where(e => e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted).ToList();

        // get current timestamp
        var timestamp = DateTime.UtcNow;

        foreach (var entityEntry in changedEntities)
        {
            // create audit entity based on change type
            var audit = new Audit
            {
                TableName = entityEntry.Metadata.Name,
                ChangeType = entityEntry.State.ToString(),
                Timestamp = timestamp,
                UserId = // get current user id
            };

            // set audit properties based on entity state
            switch (entityEntry.State)
            {
                case EntityState.Added:
                    // set audit properties for added entity
                    break;

                case EntityState.Modified:
                    // set audit properties for modified entity
                    break;

                case EntityState.Deleted:
                    // set audit properties for deleted entity
                    break;
            }

            // add audit entity to context
            Audits.Add(audit);
        }

        // save changes to database
        return base.SaveChanges();
    }
}

以上代码使用 Change Tracker 监视实体更改,然后在 SaveChanges 方法中添加 Audit 记录。

希望这些代码能为您提供帮助!

该答案引用了chitgpt部分

看起来您的问题可能与EF Core的Change Tracking机制有关。

在EF Core中,Change Tracking机制是用来跟踪实体对象的状态变化的,以便在调用SaveChanges()方法时将这些变化保存到数据库中。但是,在某些情况下,EF Core可能无法正确识别实体对象的状态变化,导致无法将这些变化保存到数据库中。

对于您的情况,您可以尝试使用EF Core的Change Tracking机制来跟踪实体对象的状态变化,并确保在修改操作完成后调用SaveChanges()方法以将这些变化保存到Audit表中。

如果您已经使用了Change Tracking机制但是仍然无法将修改操作保存到Audit表中,您可以尝试使用EF Core的事件来处理状态变化。例如,在SaveChanges()方法调用之前,您可以订阅SavingChanges事件,在事件处理程序中手动将修改操作保存到Audit表中。

最后,如果您仍然无法解决问题,建议您检查数据库连接和配置是否正确,并确保EF Core版本符合要求。

感谢大家的指点,原来是自己搞出的问题,
_userRepository.GetDbContext().SaveChanges();这条语句已经将记录设置成已完成修改了,
故不能在变更事件中捕获