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

实现审计跟踪(Entity Framework - 第2部分)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.16/5 (15投票s)

2009 年 4 月 1 日

CPOL

3分钟阅读

viewsIcon

71887

downloadIcon

1440

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

Click to enlarge image

介绍 

第 1 部分中,我讨论了使用对象状态条目创建审计跟踪对象。正如我之前所说,在第二部分中,我将讨论此审计跟踪的回滚功能。在第一部分中,我已经描述了要从该审计跟踪获得回滚功能,我们必须考虑一些问题。我们必须在插入和删除时维护实体图的顺序。这意味着根实体已在子实体之前插入,并且在删除期间,我们必须反转它。最重要的问题是,我们必须确保每个审计跟踪条目都已按照此顺序插入。因此,在插入对象期间,我们还需要考虑以下事项:

  1. 维护插入日期时间以对审计跟踪进行排序
  2. 存储旧数据和新数据
  3. 将数据的状态存储为审计操作

如果用户选择某个时间进行回滚,我们必须从最近的审计开始,并对每个审计执行回滚操作,直到特定时间。所以现在的问题是回滚操作是什么。您可以猜到这取决于每个实体的审计操作。在回滚期间

  1. 插入的数据将被删除。
  2. 删除的数据将被插入。
  3. 修改后的数据将被旧数据替换。

如果我们查看概念模型,我们用于审计的实体集是

这里“RevisionStamp”保存审计插入日期时间,Actions 保存实体对象的审计操作(插入/删除/修改)。其他属性用其名称描述自己。

因此,这里“Deleted”和“Modified”对象将使用“Old data”属性回滚。并且 Inserted 数据将使用“New data”属性回滚。

Using the Code

UserDateInput.JPG

因此,首先,我将检索特定日期时间之后发生的所有审计。此特定日期时间取自用户。我们要做的是,对每个审计执行回滚操作,直到特定日期,并且我们必须从最后一个审计开始(从下到上)。这就是为什么我们将使用“RevisionStamp”按降序对审计进行排序并开始执行回滚的原因。

public static bool RollBack(DateTime rollBackDate, string userName)
{
    using (TransactionScope transactionScope = 
	new TransactionScope(System.Transactions.TransactionScopeOption.Required, 
	TimeSpan.FromMinutes(30)))
    {
        AdventureWorksEntities context =new AdventureWorksEntities();
        try
        {
            if (context.Connection.State != ConnectionState.Open)
            {//Open the connection
                context.Connection.Open();
            }//end if 
            //find all audits to roll back
            IEnumerable<DBAudit> auditsToRollBack = DataAdapter.GetEntity
			<DBAudit>(context, a => a.RevisionStamp >= rollBackDate );
             if (null != auditsToRollBack && auditsToRollBack.Count() > 0)
            {//if any data found to roll back
                //Order by the audits by creation datetime
                IEnumerable<DBAudit> orderedAudits = 
		auditsToRollBack.OrderByDescending(a => a.RevisionStamp);
                 foreach (var audit in orderedAudits)
                {//iterate each audit 
                    AuditTrailHelper.DoRollBackAction(ref context, audit, userName);
                }//end foreach
                int i =context.SaveChanges();
                if (i > 0)
                {
                    transactionScope.Complete();
                    return true;
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            // Explicitly dispose of the context, 
            // which closes the connection. 
            context.Dispose();
        }
    }
    return false;
}

DoRollBackAction 中,正如我之前所说,“Deleted”和“Modified”对象将使用“Old data”属性回滚。并且 Inserted 数据将使用“New data”属性回滚。

由于我在创建 Audit 对象时使用 XML 序列化存储了旧数据和新数据,因此我必须将该数据反序列化为“EntityObject”,并根据审计操作执行反向操作。

 private static void DoRollBackAction
	(ref AdventureWorksEntities context,  DBAudit auditInfo, string userName)
        {
            if (auditInfo.Actions == AuditTrailHelper.AuditActions.U.ToString() || 
		auditInfo.Actions == AuditTrailHelper.AuditActions.D.ToString())
            {//For action of Update and Delete
                //Deserialized old data from the XML
                object oldData = AuditTrailHelper.GetAuditObjectFromXML
				(auditInfo.OldData, auditInfo.TableName);

                if (oldData is EntityObject)
                {
                    EntityObject oldEntity = (EntityObject)oldData;
                    oldEntity.EntityKey = null;
                    //add in case of delete or edit the current one with old data 
                    DataAdapter.EditEntity(ref context, oldEntity);
                }
            }
            else if (auditInfo.Actions == AuditTrailHelper.AuditActions.I.ToString())
            {//For Insert Action
                object newData = AuditTrailHelper.GetAuditObjectFromXML
				(auditInfo.NewData, auditInfo.TableName);

                if (newData is EntityObject)
                {
                    //Otherwise, delete the entity that has been inserted before
                    EntityObject newEntity = (EntityObject)newData;
                    newEntity.EntityKey = null;
                    EntityKey key = context.CreateEntityKey
				(newEntity.GetType().Name, newEntity);

                    object objToRemoved = null;
                    if (context.TryGetObjectByKey(key, out objToRemoved))
                    {//delete the entity
                        context.DeleteObject(objToRemoved);
                    }//end if 
                }
            }
            //delete the audit
            context.DeleteObject(auditInfo);
        }
    }

在序列化实体对象后,我们得到如下所示的 XML 字符串

<SalesOrderDetail xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance 
			xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <EntityKey>
    <EntitySetName>SalesOrderDetail</EntitySetName>
    <EntityContainerName>AdventureWorksEntities</EntityContainerName>
  </EntityKey>
  <SalesOrderID>1</SalesOrderID>
  <SalesOrderDetailID>0</SalesOrderDetailID>
  <OrderQty>1</OrderQty>
  <UnitPrice>1898.0944</UnitPrice>
  <UnitPriceDiscount>0</UnitPriceDiscount>
  <LineTotal>0</LineTotal>
  <rowguid>6bfaa372-292a-4fd6-84d3-c4e900f09589</rowguid>
  <ModifiedDate>2009-03-26T16:16:41.9691358+06:00</ModifiedDate>
  <SalesOrderHeaderReference />
  <SpecialOfferProductReference>
    <EntityKey>
      <EntitySetName>SpecialOfferProduct</EntitySetName>
      <EntityContainerName>AdventureWorksEntities</EntityContainerName>
      <EntityKeyValues>
        <EntityKeyMember>
          <Key>SpecialOfferID</Key>
          <Value xsi:type="xsd:int">1</Value>
        </EntityKeyMember>
        <EntityKeyMember>
          <Key>ProductID</Key>
          <Value xsi:type="xsd:int">777</Value>
        </EntityKeyMember>
      </EntityKeyValues>
    </EntityKey>
  </SpecialOfferProductReference>
</SalesOrderDetail> 

我必须反序列化此 XML 字符串。在反序列化期间,我创建了一个 XML 文档和一个使用该文档的 XML 读取器。使用该读取器,我反序列化了实体对象。反序列化 entityobject 的方法是

public static object GetAuditObjectFromXML(string ObjectInXML, string typeName)
{
    XDocument doc = XDocument.Parse(ObjectInXML);
    //Assuming doc is an XML document containing a 
    //serialized object and objType is a System.Type set to the type of the object.
    XmlReader reader = doc.CreateReader();
    //get the Type of entity object  
    Type entityType = Assembly.GetExecutingAssembly().GetType
			("ImplAuditTrailUsingEF." + typeName);
     XmlSerializer ser = new XmlSerializer(entityType);
    return ser.Deserialize(reader);
}

为了插入已删除的对象或更改回修改后的对象,我编写了一个用于编辑对象的单个方法

EditEntity(ref AdventureWorksEntities context, EntityObject entity)

在这个方法中,我更改了存在的对象并将其附加到容器,否则我已将 entityObject 插入到上下文中。为了保存所有这些更改,我们需要在对象状态缓存中输入一个条目。

    public static void EditEntity
	(ref AdventureWorksEntities context, EntityObject entity)
    {
        // Define an ObjectStateEntry and EntityKey for the current object.
        EntityKey key;
        object originalItem;
        // Get the detached object's entity key.
        if (entity.EntityKey == null)
        {
            // Get the entity key of the updated object.
            key = context.CreateEntityKey(entity.GetType().Name, entity);
        }
        else
        {
            key = entity.EntityKey;
        }
        try
        {
            // Get the original item based on the entity key from the context
            // or from the database.
            if (context.TryGetObjectByKey(key, out originalItem))
            {//accept the changed property
                 // Call the ApplyPropertyChanges method to apply changes
                // from the updated item to the original version.
                context.ApplyPropertyChanges(
                    key.EntitySetName, entity);                    
            }
            else
            {//add the new entity
                context.AddObject(entity.GetType().Name, entity);
            }//end else
        }
        catch (System.Data.MissingPrimaryKeyException ex)
        {
            throw ex;
        }
        catch (System.Data.MappingException ex)
        {
            throw ex;
        }
        catch (System.Data.DataException ex)
        {
            throw ex;
        }
    }
}

这就是在我们的审计跟踪中实现回滚机制的全部内容。现在我们要做的就是调用“RollBack”方法并给它一个特定的日期进行回滚。

private void btnRollBack_Click(object sender, RoutedEventArgs e)
{
    if (AuditTrailHelper.RollBack(dtpRollBackDate.SelectedDate.Value, "Admin"))
    {
        MessageBox.Show("Successfully Roll Backed!!");
    }
}

这就是使用 Entity Framework 实现审计跟踪的全部内容。在这里,我删除了已回滚的审计。当然,每个回滚操作也是一个 DB 操作,并且我对每个回滚操作进行了审计。

在示例项目中,我使用了 Xeed 的免费版本。您可能需要下载该库,否则您可以将其替换为 WPF 工具包。它仅用于从用户处获取日期时间以进行回滚。

历史

  • 2009 年 4 月 1 日:初始帖子
© . All rights reserved.