65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (51投票s)

2009年3月27日

CPOL

3分钟阅读

viewsIcon

335500

downloadIcon

5643

使用 Entity Framework 的缓存条目实现审计跟踪

引言

Entity Framework 会跟踪容器中被删除、添加和修改的所有对象和关系。 EF 会保存对象的状态,并保存必要的更改信息。每个对象或关系的所有这些跟踪信息都作为“objectStateEntry”存在。使用“ObjectStateManager”,可以访问所有这些更改信息,例如对象状态(已添加/已修改/已删除)、已修改的属性、原始值和当前值,并且可以轻松地为这些对象执行审计追踪。要从该审计追踪中获得回滚功能,我们必须考虑一些问题。我们必须在插入和删除期间维护实体图的顺序。这意味着根实体必须在子实体之前插入,并且在删除期间,我们必须将其反转。最重要的问题是,我们必须确保审计追踪条目将按照此顺序插入。

现在我将讨论审计追踪的实现,该实现能够回滚到某个特定时期。为了进行这样的实现,我将使用 Entity Framework 的缓存管理,它被称为“ObjectStateManager”。使用此管理器,我能够找出当前已更改、已添加或已删除并作为对象状态条目驻留在 EF 缓存中的对象。在第 1 部分中,我将仅讨论使用对象状态条目创建审计追踪对象。在第 2 部分中,我将讨论此审计追踪的回滚功能。

Using the Code

首先,我在数据库中创建表 audit trail

ImplAudingTrailUsingEFP1/DataBaseDbAudit.JPG

对于此表,我将在概念级别上创建一个实体集,如下所示

ImplAudingTrailUsingEFP1/dbAuditEF.JPG

在 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 日:初始版本
© . All rights reserved.