背景说明:我使用的后端是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.其他原因:有时候,实体状态没有变化,可能是由于其他一些错误导致的,例如原始实体没有正确设置主键值等。
针对这个问题,建议您进行以下处理步骤:
从您提供的代码来看,将一个 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();这条语句已经将记录设置成已完成修改了,
故不能在变更事件中捕获