我对实体框架6.0下的审计日志有疑问
我已经实现了它,但当我更新或插入时,它确实会减慢速度。
在其他应用程序(不使用EF)中,我使用审计信息创建内存XML,然后将其发送到SQL Server中的存储过程,以便插入审计日志,速度惊人。你没意识到你在做审计记录。
我想知道是否有其他方法或最佳做法。
我的实际代码如下所示:
在我的DbContext中,我重写了SaveChanges()方法:
public override int SaveChanges() {
throw new InvalidOperationException("User ID and Session ID must be provided");
}然后实现我的custon SaveChanges()方法。
public int SaveChanges(int userId, Guid sessionId, bool saveAuditLog) {
if (saveAuditLog && userId > 0) {
// this list will exclude entities for auditing
List < string > excludeEntities = new List < string > ();
excludeEntities.Add("SecuritySession");
excludeEntities.Add("SecurityUserActivityLog");
this.ObjectContext = ((IObjectContextAdapter) this).ObjectContext;
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach(var ent in this.ChangeTracker.Entries().Where(p => p.State == System.Data.Entity.EntityState.Added || p.State == System.Data.Entity.EntityState.Deleted || p.State == System.Data.Entity.EntityState.Modified)) {
// security session is not Auditable
if (!excludeEntities.Contains(ent.Entity.GetType().Name)) {
// For each changed record, get the audit record entries and add them
foreach(AuditLog x in GetAuditRecordsForChange(ent, userId, sessionId)) {
this.AuditLogs.Add(x);
}
}
}
}最后,我有一个方法GetAuditRecordsForChange()来完成审计工作。
private List < AuditLog > GetAuditRecordsForChange(DbEntityEntry dbEntry, int userId, Guid sessionId) {
Type entityType = dbEntry.Entity.GetType();
if (entityType.BaseType != null && entityType.Namespace == "System.Data.Entity.DynamicProxies")
entityType = entityType.BaseType;
string entityTypeName = entityType.Name;
string[] keyNames;
MethodInfo method = Data.Helpers.EntityKeyHelper.Instance.GetType().GetMethod("GetKeyNames");
keyNames = (string[]) method.MakeGenericMethod(entityType).Invoke(Data.Helpers.EntityKeyHelper.Instance, new object[] {
this
});
List < AuditLog > result = new List < AuditLog > ();
DateTime changeTime = DateTime.Now;
// Get table name (if it has a Table attribute, use that, otherwise get the pluralized name)
string tableName = entityTypeName;
if (dbEntry.State == System.Data.Entity.EntityState.Added) {
// For Inserts, just add the whole record
// If the entity implements IDescribableEntity, use the description from Describe(), otherwise use ToString()
foreach(string propertyName in dbEntry.CurrentValues.PropertyNames) {
result.Add(new AuditLog() {
AuditLogId = Guid.NewGuid(),
UserId = userId,
SessionId = sessionId,
EventDateUTC = changeTime,
EventType = "A", // Added
TableName = tableName,
RecordId = dbEntry.CurrentValues.GetValue < object > (keyNames[0]).ToString(),
ColumnName = propertyName,
NewValue = dbEntry.CurrentValues.GetValue < object > (propertyName) == null ? null : dbEntry.CurrentValues.GetValue < object > (propertyName).ToString()
});
}
} else if (dbEntry.State == System.Data.Entity.EntityState.Deleted) {
// Same with deletes, do the whole record, and use either the description from Describe() or ToString()
result.Add(new AuditLog() {
AuditLogId = Guid.NewGuid(),
UserId = userId,
SessionId = sessionId,
EventDateUTC = changeTime,
EventType = "D", // Deleted
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue < object > (keyNames[0]).ToString(),
ColumnName = "*ALL",
NewValue = (dbEntry.OriginalValues.ToObject() is IDescribableEntity) ? (dbEntry.OriginalValues.ToObject() as IDescribableEntity).Describe() : dbEntry.OriginalValues.ToObject().ToString()
});
} else if (dbEntry.State == System.Data.Entity.EntityState.Modified) {
foreach(string propertyName in dbEntry.OriginalValues.PropertyNames) {
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.OriginalValues.GetValue < object > (propertyName), dbEntry.CurrentValues.GetValue < object > (propertyName))) {
result.Add(new AuditLog() {
AuditLogId = Guid.NewGuid(),
UserId = userId,
SessionId = sessionId,
EventDateUTC = changeTime,
EventType = "M", // Modified
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue < object > (keyNames[0]).ToString(),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue < object > (propertyName) == null ? null : dbEntry.OriginalValues.GetValue < object > (propertyName).ToString(),
NewValue = dbEntry.CurrentValues.GetValue < object > (propertyName) == null ? null : dbEntry.CurrentValues.GetValue < object > (propertyName).ToString()
});
}
}
}
// Otherwise, don't do anything, we don't care about Unchanged or Detached entities
return result;
}
// Call the original SaveChanges(), which will save both the changes made and the audit records
return base.SaveChanges();
}发布于 2014-08-12 23:30:26
您最大的性能问题似乎是对ChangeTracker.Entries()和DbSet.Add()的调用都在内部触发DetectChanges()方法,该方法与DbContext实例主动跟踪的实体数量成正比。我怀疑对foreach循环中的.Add()的调用是主要的罪魁祸首,因为它多次调用该方法。因为要在foreach循环中添加AuditLog实体,所以对DetectChanges()的每次调用都要慢一些,因为DbContext实例正在跟踪更多的实体。
有关更多详细信息,请参阅http://blog.oneunicorn.com/2012/03/11/secrets-of-detectchanges-part-2-when-is-detectchanges-called-automatically/。
调用DbSet.Add()或DbSet.Remove()适当地设置实体的状态(不调用DetectChanges()),但更新实体的实体状态不会自动更新(这是DetectChanges()完成的一件事)。您需要在方法开始时对DetectChanges()进行单个调用(隐式或显式),以确保所有更新的实体都被标记为已更新。注意,除非显式关闭,否则基SaveChanges()方法会自动调用DetectChanges()。
此外,反复的反思性呼吁也可能没有帮助。我建议通过使用System.Diagnostics.Stopwatch或其他更复杂的方法来具体确定瓶颈来分析这种方法。
https://stackoverflow.com/questions/25245351
复制相似问题