我已将Net core API配置为.net core 2.0和EF core 2.0。它包含存储库模式架构。
现在,我正在尝试使用EF更改跟踪器为每个保存更改实施审核日志。
我的问题:每当我尝试为编辑/修改端点添加日志时,原始值和当前值都保持不变,并且是新近更新的值。因此,我无法跟踪修改或更改。
这是我的ApplicationContext文件,在其中我已重写保存调用。
public class ApplicationContext : DbContext
{
public ApplicationContext(DbContextOptions options) : base(options: options) { }
public DbSet<Item> Item { get; set; }
public DbSet<ChangeLog> ChangeLog { get; set; }
public override int SaveChanges()
{
var modifiedEntities = ChangeTracker.Entries();
foreach (var change in modifiedEntities)
{
var entityType = change.Entity.GetType().Name;
if (entityType == "LogItem")
continue;
if (change.State == EntityState.Modified)
{
foreach (var prop in change.OriginalValues.Properties)
{
var id = change.CurrentValues["Id"].ToString();
//here both originalValue and currentValue are same and it's newly updated value
var originalValue = change.OriginalValues[prop]?.ToString();
var currentValue = change.CurrentValues[prop]?.ToString();
if (originalValue != currentValue)
{
ChangeLog.Add(
new ChangeLog()
{
CreationDateTime = DateTime.Now,
CreationUserId = 1,
Log = $"Edited item named {prop.Name} in {entityType} Id {id}.",
OldValue = originalValue,
NewValue = currentValue,
TableName = entityType,
FieldName = prop.Name
}
);
}
}
}
}
return base.SaveChanges();
}
}
这是我的基本存储库。
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IFullAuditedEntity, new()
{
private readonly ApplicationContext context;
public EntityBaseRepository(ApplicationContext context)
{
this.context = context;
}
public virtual T GetSingle(int id) => context.Set<T>().AsNoTracking().FirstOrDefault(x => x.Id == id);
public virtual T Add(T entity) => Operations(entity: entity, state: EntityState.Added);
public virtual T Update(T entity) => Operations(entity: entity, state: EntityState.Modified);
public virtual T Delete(T entity) => Operations(entity: entity, state: EntityState.Deleted);
public virtual T Operations(T entity, EntityState state)
{
EntityEntry dbEntityEntry = context.Entry<T>(entity);
if (state == EntityState.Added)
{
entity.CreationDateTime = DateTime.UtcNow;
entity.CreationUserId = 1;
context.Set<T>().Add(entity);
dbEntityEntry.State = EntityState.Added;
}
else if (state == EntityState.Modified)
{
entity.LastModificationDateTime = DateTime.UtcNow;
entity.LastModificationUserId = 1;
//var local = context.Set<T>().Local.FirstOrDefault(entry => entry.Id.Equals(entity.Id));
//if (local != null)
//{
// context.Entry(local).State = EntityState.Detached;
//}
dbEntityEntry.State = EntityState.Modified;
}
else if (state == EntityState.Deleted)
{
entity.DeletionFlag = true;
entity.DeletionUserId = 1;
entity.DeletionDateTime = DateTime.UtcNow;
dbEntityEntry.State = EntityState.Modified;
}
return entity;
}
public virtual void Commit() => context.SaveChanges();
}
最后是我的控制器,带有放置端点。
[Produces("application/json")]
[Route("api/Item")]
public class ItemController : Controller
{
private readonly IItemRepository repository;
private readonly IChangeLogRepository changeLogRepository;
private readonly IMapper mapper;
public ItemController(IItemRepository repository, IChangeLogRepository _changeLogRepository, IMapper mapper)
{
this.repository = repository;
this.changeLogRepository = _changeLogRepository;
this.mapper = mapper;
}
[HttpPut]
public IActionResult Put([FromBody]ItemDto transactionItemDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (transactionItemDto.Id <= 0)
{
return new NotFoundResult();
}
Item item = repository.GetSingle(transactionItemDto.Id); //find entity first
if (item == null)
{
return new NotFoundResult();
}
//map all the properties and commit
var entity = mapper.Map<Item>(transactionItemDto);
var updatedItem = repository.Update(entity);
repository.Commit();
return new OkObjectResult(mapper.Map<Item, ItemDto>(source: updatedItem));
}
}
我不确定我在哪里做错什么,我尝试用SO来检查这种情况,但是没有运气。任何帮助将不胜感激,谢谢。
我想我看到您的代码有问题。在您的控制器中:
//map all the properties and commit
var entity = mapper.Map<Item>(transactionItemDto);
var updatedItem = repository.Update(entity);
repository.Commit();
在该代码中,您正在获取DTO并将其映射到Item的新实例。该Item的新实例知道当前数据库值的nothing,这就是为什么您在OriginalValue和CurrentValue中看到相同的新值的原因。
如果重复使用在此行中获得的Item item变量:
Item item = repository.GetSingle(transactionItemDto.Id); //find entity first
注意,您需要获取具有跟踪功能的实体,而不是存储库GetSingle如何通过AsNoTracking实现它。如果您使用该项目(现在具有原始/当前数据库值,则将您的transactionItemDto属性映射到该项目上,如下所示:
var entityToUpdate = mapper.Map<ItemDto, Item>(transactionItemDto);
然后,当您调用将它传递给EntityToUpdate的repository.Update方法时,我相信您会看到正确的前后值。
。。。。
我最初发布的旧(错误)答案:在您的ApplicationContext代码中,您具有以下循环
foreach (var prop in change.OriginalValues.Properties)
我相信这是导致您的原始值/当前值相同的原因,因为您正在遍历原始值属性。尝试将该循环更改为:
foreach (var prop in change.Properties)
然后,尝试通过prop变量读取每个属性的值,如下所示:
var currentValue = prop.CurrentValue;
var originalValue = prop.OriginalValue;
编辑:啊-我现在看到在您的代码中,您正在尝试从change.OriginalValues集合中读取原始值,所以我认为这无济于事。
我没有使用存储库模式,但是我为EF Core 2.1实现了非常相似的审核日志。我遍历实体框架变更跟踪器正在追踪的变更列表,并将其记录下来。
我注意到,当我想更新实体时,有两种方法可以做到。一种是我从数据库中读取现有实体,重新分配变量,然后保存它。第二种方法是简单地创建一个对象,将其附加到数据库上下文,然后将要更新的属性设置为修改后的状态。当我这样做时,我的审核将无法使用原始值,因为原始值从未真正从数据库中读取过。
示例:
//auditing works fine
var myEntity = await db.MyEntity.FindAsync(entityId);
myEntity.Property = newValue;
await db.SaveChangesAsync();
//auditing can't track the old value
var myEntity = new MyEntity();
db.Attach(myEntity);
myEntity.Property = newValue;
await db.SaveChangesAsync();
例如,这是我的审核代码的重要部分
foreach (var entity in db.ChangeTracker.Entries())
{
if(entity.State == EntityState.Detached || entity.State == EntityState.Unchanged)
{
continue;
}
var audits = new List<Audit>();
//the typeId is a string representing the primary keys of this entity.
//this will not be available for ADDED entities with generated primary keys, so we need to update those later
string typeId;
if (entity.State == EntityState.Added && entity.Properties.Any(prop => prop.Metadata.IsPrimaryKey() && prop.IsTemporary))
{
typeId = null;
}
else
{
var primaryKey = entity.Metadata.FindPrimaryKey();
typeId = string.Join(',', primaryKey.Properties.Select(prop => prop.PropertyInfo.GetValue(entity.Entity)));
}
//record an audit for each property of each entity that has been changed
foreach (var prop in entity.Properties)
{
//don't audit anything about primary keys (those can't change, and are already in the typeId)
if(prop.Metadata.IsPrimaryKey() && entity.Properties.Any(p => !p.Metadata.IsPrimaryKey()))
{
continue;
}
//ignore values that won't actually be written
if(entity.State != EntityState.Deleted && entity.State != EntityState.Added && prop.Metadata.AfterSaveBehavior != PropertySaveBehavior.Save)
{
continue;
}
//ignore values that won't actually be written
if (entity.State == EntityState.Added && prop.Metadata.BeforeSaveBehavior != PropertySaveBehavior.Save)
{
continue;
}
//ignore properties that didn't change
if(entity.State == EntityState.Modified && !prop.IsModified)
{
continue;
}
var audit = new Audit
{
Action = (int)entity.State,
TypeId = typeId,
ColumnName = prop.Metadata.SqlServer().ColumnName,
OldValue = (entity.State == EntityState.Added || entity.OriginalValues == null) ? null : JsonConvert.SerializeObject(prop.OriginalValue),
NewValue = entity.State == EntityState.Deleted ? null : JsonConvert.SerializeObject(prop.CurrentValue)
};
}
//Do something with audits
}