使用 Entity Framework 实现审计跟踪 - 第 1 部分






4.91/5 (51投票s)
使用 Entity Framework 的缓存条目实现审计跟踪
引言
Entity Framework 会跟踪容器中被删除、添加和修改的所有对象和关系。 EF 会保存对象的状态,并保存必要的更改信息。每个对象或关系的所有这些跟踪信息都作为“objectStateEntry
”存在。使用“ObjectStateManager
”,可以访问所有这些更改信息,例如对象状态(已添加/已修改/已删除)、已修改的属性、原始值和当前值,并且可以轻松地为这些对象执行审计追踪。要从该审计追踪中获得回滚功能,我们必须考虑一些问题。我们必须在插入和删除期间维护实体图的顺序。这意味着根实体必须在子实体之前插入,并且在删除期间,我们必须将其反转。最重要的问题是,我们必须确保审计追踪条目将按照此顺序插入。
现在我将讨论审计追踪的实现,该实现能够回滚到某个特定时期。为了进行这样的实现,我将使用 Entity Framework 的缓存管理,它被称为“ObjectStateManager
”。使用此管理器,我能够找出当前已更改、已添加或已删除并作为对象状态条目驻留在 EF 缓存中的对象。在第 1 部分中,我将仅讨论使用对象状态条目创建审计追踪对象。在第 2 部分中,我将讨论此审计追踪的回滚功能。
Using the Code
首先,我在数据库中创建表 audit trail
对于此表,我将在概念级别上创建一个实体集,如下所示
在 Entity Framework 中,要将所有更改保存到 Db 中,我们必须调用“Context.SaveChanges()
”,并且此 Context
是一个从“ObjectContext
”类继承的容器。为了为每个“Modified
/Added
/Deleted
”创建审计追踪对象,我将捕获该事件
Context.SavingChanges +=new EventHandler(Context_SavingChanges);
在示例程序中,我通过编写 partial class 来完成它
public partial class AdventureWorksEntities
{ partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(AdventureWorksEntities_SavingChanges);
}
void AdventureWorksEntities_SavingChanges(object sender, EventArgs e)
{
因此,在我的“AdventureWorksEntities_SavingChanges
”方法中,我将创建所有将要保存在 DB 中的“dbaudit
”对象。在这里,它从 EF 状态缓存中获取每个条目 - 已添加、已删除或已修改,并调用工厂方法来生成审计追踪对象。
public partial class AdventureWorksEntities
{
public string UserName { get; set; }
List<DBAudit> auditTrailList = new List<DBAudit>();
public enum AuditActions
{
I,
U,
D
}
partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(AdventureWorksEntities_SavingChanges);
}
void AdventureWorksEntities_SavingChanges(object sender, EventArgs e)
{
IEnumerable<ObjectStateEntry> changes =
this.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Deleted | EntityState.Modified);
foreach (ObjectStateEntry stateEntryEntity in changes)
{
if (!stateEntryEntity.IsRelationship &&
stateEntryEntity.Entity != null &&
!(stateEntryEntity.Entity is DBAudit))
{//is a normal entry, not a relationship
DBAudit audit = this.AuditTrailFactory(stateEntryEntity, UserName);
auditTrailList.Add(audit);
}
}
if (auditTrailList.Count > 0)
{
foreach (var audit in auditTrailList)
{//add all audits
this.AddToDBAudit(audit);
}
}
}
这里的“AuditTrailFactory
”是一个 Factory
方法,用于创建 dbaudit
对象。特别是对于 Modify
状态,它会保留已修改的属性并序列化为 XML。因此,使用这些字段,您可以轻松地显示已修改对象的更改,而无需对旧数据和新数据进行任何比较。
private DBAudit AuditTrailFactory(ObjectStateEntry entry, string UserName)
{
DBAudit audit = new DBAudit();
audit.AuditId = Guid.NewGuid().ToString();
audit.RevisionStamp = DateTime.Now;
audit.TableName = entry.EntitySet.Name;
audit.UserName = UserName;
if (entry.State == EntityState.Added)
{//entry is Added
audit.NewData = GetEntryValueInString(entry, false);
audit.Actions = AuditActions.I.ToString();
}
else if (entry.State == EntityState.Deleted)
{//entry in deleted
audit.OldData = GetEntryValueInString(entry, true);
audit.Actions = AuditActions.D.ToString();
}
else
{//entry is modified
audit.OldData = GetEntryValueInString(entry, true);
audit.NewData = GetEntryValueInString(entry, false);
audit.Actions = AuditActions.U.ToString();
IEnumerable<string> modifiedProperties = entry.GetModifiedProperties();
//assing collection of mismatched Columns name as serialized string
audit.ChangedColumns = XMLSerializationHelper.XmlSerialize(
modifiedProperties.ToArray());
}
return audit;
}
这里的“GetEntryValueInString
”用于创建先前或已修改对象的 XML 文本。在 Entity Framework 中,每个条目都包含所有更改定义。首先,我克隆当前对象。使用 entry.GetModifiedProperties()
,我可以仅获取对象的已修改属性,并使用“OriginalValues
”和“CurrentValues
”,我可以自己构建旧数据和新数据。工厂告诉我它想要什么——旧数据还是新数据。最后,我执行 XML 序列化并返回 XML 字符串。
private string GetEntryValueInString(ObjectStateEntry entry, bool isOrginal)
{
if (entry.Entity is EntityObject)
{
object target = CloneEntity((EntityObject)entry.Entity);
foreach (string propName in entry.GetModifiedProperties())
{
object setterValue = null;
if (isOrginal)
{
//Get original value
setterValue = entry.OriginalValues[propName];
}
else
{
//Get original value
setterValue = entry.CurrentValues[propName];
}
//Find property to update
PropertyInfo propInfo = target.GetType().GetProperty(propName);
//update property with ribald value
if (setterValue == DBNull.Value)
{//
setterValue = null;
}
propInfo.SetValue(target, setterValue, null);
}//end foreach
XmlSerializer formatter = new XmlSerializer(target.GetType());
XDocument document = new XDocument();
using (XmlWriter xmlWriter = document.CreateWriter())
{
formatter.Serialize(xmlWriter, target);
}
return document.Root.ToString();
}
return null;
}
为了克隆实体,我使用了我在 MSDN 论坛帖子中找到的方法(感谢 Patrick Magee)。
public EntityObject CloneEntity(EntityObject obj)
{
DataContractSerializer dcSer = new DataContractSerializer(obj.GetType());
MemoryStream memoryStream = new MemoryStream();
dcSer.WriteObject(memoryStream, obj);
memoryStream.Position = 0;
EntityObject newObject = (EntityObject)dcSer.ReadObject(memoryStream);
return newObject;
}
这就是第 1 部分的全部内容,我仅为每个 CUD 操作创建审计追踪对象。
历史
- 2009 年 3 月 27 日:初始版本