【.com原稿】在实际项目中,我们经常需要记录数据库中存储的数据的变化(比如记录数据修改前的原始值)数据),这样在发生误操作的情况下,数据可以恢复到更改前的状态,也可以追溯到修改数据的人。大多数开发者会定义自己的代码来记录数据的变化,但这不仅费时费力,有时还会影响业务的性能。当然,我们也可以使用数据库触发器来记录这些操作。SQLServer数据库2017以上版本为我们提供了跟踪数据库数据变化的功能。利用这个功能,我们可以准确的记录数据库数据的变化。虽然这个功能很强大,但是有时候我们使用的数据库不是SQLServer数据库,或者在某些情况下我们不适合使用SQLServer数据库提供的这个功能。那么这个时候我们应该怎么办呢?如果你是使用EntityFrameworkCore2.0及以上版本开发项目,那么这个问题就很容易解决了。在EntityFrameworkCore中,只要捕获到数据变更记录,我们就可以随时将数据恢复到变更前的状态,这里的数据库变更记录称为审计数据。那么我们先来看两个问题:审计数据是什么时候产生并写入数据库的?如何获取数据的新旧值?回答完以上两个问题,跟着我一起看看如何使用EntityFrameworkCore抓取审计数据吧。1.创建审计模型捕获审计数据并将其存储在数据库中的第一步是创建审计模型。只有具有审计模型的审计数据才能正确地存储在数据库中。publicclassAudit{publicintId{get;set;}publicstringTableName{get;set;}publicDateTimeDateTime{get;set;}[NotMapped]publicOperationOperation{get;set;}publicstringOperationString{get{returnOperation.ToString();}privateset{Operation=(Operation)Enum.Parse(typeof(Operation),value,true);}}publicstringKey{get;set;}publicstringOld{get;set;}//////操作后的数据///publicstringNew{get;set;}}//////操作类型///publicenumOperation{Add=0,Delete=1,Modified=2}以上代码创建的审计模型包含operation表名TableName,操作类型Operation,操作数据的主键Key,操作前数据Old,操作后数据New,操作类型包括增删改查。2.创建审计数据存储现在我们有了一个审计模型,但是只有审计模型是不够的,我们还需要创建和存储审计数据相关的类,下面我们一起来创建这个类。publicclassAuditDb{publicEntityEntry_entityEntry{get;set;}publicAuditDb(EntityEntryentityEntry){this._entityEntry=entityEntry;}publicstringTableName{get;set;}publicOperationOperation{get;set;}publicDictionarykeys{get;}=newDictionary();publicDictionaryolds{get;}=newDictionary();publicDictionarynews{get;}=newDictionary();publicListpropertyEntries{get;}=newList();publicboolHasPropertyEntries=>propertyEntries.Any();publicAuditToAudit(){Auditaudit=newAudit{TableNameTableName=TableName,OperationOperation=Operation,DateTimeDateTime=DateTime.Now,Key=JsonConvert。SerializeObject(keys),Old=olds.Count==0?null:JsonConvert.SerializeObject(olds),New=news.Count==0?null:JsonConvert.SerializeObject(news)};returnaudit;}}这个类主要是用于存放表名,操作数据的主键Id,操作前后的数据。在上面的代码中,我们可以看到我们将操作数据的主键Id,操作前数据和操作后数据的变量定义为字典类型,因为我们的程序题中可能会出现批量操作。将上述信息转换为Audit时,会提示我们判断运算前后数据的长度。这是因为当我们添加新数据时,没有旧数据。当我们不检查数据没有新的数据时提交数据进行任何更改。3.重写SaveChanges本例重写SaveChanges,同样适用于SaveChangesAsync。我们需要在OnBeforSaveBehavior方法中创建AuditDb列表。publicclassEFContext:DbContext{publicoverrideintSaveChanges(boolacceptAllChangesOnSuccess){ListauditDbs=OnBeforeSaveBehavior();varresult=base.SaveChanges(acceptAllChangesOnSuccess);返回结果;}ListOnBeforeSaveBehavior(){ChangeTracker.DetectChanges();List=newList();foreach(EntityEntryentityinChangeTracker.Entries()){if(entity.EntityisAudit||entity.State==EntityState.Detached||entity.State==EntityState.Unchanged){continue;}AuditDbauditDb=newAuditDb(实体){TableName=entity.Metadata.Name};auditDbs.Add(auditDb);foreach(varpropertyinentity.Properties){if(property.IsTemporary){auditDb.propertyEntries.Add(属性);继续;}varpropertName=属性。Metadata.Name;if(property.Metadata.IsPrimaryKey()){auditDb.keys[propertName]=property.CurrentValue;continue;}switch(entity.State){caseEntityState.Deleted:auditDb.Operation=Operation.Delete;auditDb。olds[propertName]=property.OriginalValue;break;caseEntityState.Modified:if(property.IsModified){auditDb.Operation=Operation.Modified;auditDb.olds[propertName]=property.OriginalValue;auditDb.news[propertName]=property.CurrentValue;}break;caseEntityState.Added:auditDb.Operation=Operation.Add;auditDb.news[propertName]=property.CurrentValue;break;}}}Listaudits=newList();foreach(variteminauditDbs.Where(p=>!p.HasPropertyEntries)){audits.Add(item.ToAudit());}returnauditDbs.Where(p=>p.HasPropertyEntries).ToList();}}至此所有抓取审计数据的代码就完成了,下面是一些thingstonote有一点是有些实体属性是由数据库生成的,比如当前日期,Id等,这些值需要等待SaveChanges方法执行后才能获取到。也就是说,在这种情况下,必须在SaveChanges方法之后保存审计数据。4.总结通过有了前面的代码示例和解释,我们就可以回答上面提出的两个问题了。除了一些数据是数据库自动生成的,大多数情况下,在调用SaveChanges方法之前,我们都是通过context中的ChangeTracker属性来获取的。新旧值并保存。上面的代码比较简单易懂,适用于大部分情况,可以直接在项目中使用。作者简介:朱刚,化名苗叔,国内某技术博客认证专家,.NET高级开发工程师。曾就职于初创公司,从事企业级安全监控系统开发。【原创稿件,合作网站转载请注明原作者和出处为.com】