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






4.16/5 (15投票s)
使用 Entity Framework 的缓存条目实现审计跟踪

介绍
在第 1 部分中,我讨论了使用对象状态条目创建审计跟踪对象。正如我之前所说,在第二部分中,我将讨论此审计跟踪的回滚功能。在第一部分中,我已经描述了要从该审计跟踪获得回滚功能,我们必须考虑一些问题。我们必须在插入和删除时维护实体图的顺序。这意味着根实体已在子实体之前插入,并且在删除期间,我们必须反转它。最重要的问题是,我们必须确保每个审计跟踪条目都已按照此顺序插入。因此,在插入对象期间,我们还需要考虑以下事项:
- 维护插入日期时间以对审计跟踪进行排序
- 存储旧数据和新数据
- 将数据的状态存储为审计操作
如果用户选择某个时间进行回滚,我们必须从最近的审计开始,并对每个审计执行回滚操作,直到特定时间。所以现在的问题是回滚操作是什么。您可以猜到这取决于每个实体的审计操作。在回滚期间
- 插入的数据将被删除。
- 删除的数据将被插入。
- 修改后的数据将被旧数据替换。
如果我们查看概念模型,我们用于审计的实体集是
这里“RevisionStamp
”保存审计插入日期时间,Actions 保存实体对象的审计操作(插入/删除/修改)。其他属性用其名称描述自己。
因此,这里“Deleted
”和“Modified
”对象将使用“Old data”属性回滚。并且 Inserted
数据将使用“New data”属性回滚。
Using the Code

因此,首先,我将检索特定日期时间之后发生的所有审计。此特定日期时间取自用户。我们要做的是,对每个审计执行回滚操作,直到特定日期,并且我们必须从最后一个审计开始(从下到上)。这就是为什么我们将使用“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 日:初始帖子