我对Entity Framework 6.0下的审核日志有疑问
我已经实现了它,但是当我更新或插入时,它确实减慢了速度。
在其他应用程序(不使用EF)中,我曾经使用审计信息创建一个内存XML,然后将其发送到SQL Server中的存储过程,以便插入审计日志并且速度惊人。你没有意识到你正在进行审计记录。
我想知道是否有其他方法可以做到这一点或者是最佳做法。
我的实际代码如下:
在我的DbContext
中,我覆盖了SaveChanges()
方法:
public override int SaveChanges() {
throw new InvalidOperationException("User ID and Session ID must be provided");
}
然后我实现了我的CustChanges()方法。
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();
}
答案 0 :(得分:3)
您最大的性能问题似乎是对ChangeTracker.Entries()和DbSet.Add()的调用都在内部触发DetectChanges()方法,该方法与DbContext实例主动跟踪的实体数量成慢比例。我怀疑在foreach循环中对.Add()的调用是主要的罪魁祸首,因为它正在调用该方法n次。因为要在foreach循环中添加AuditLog实体,所以每次调用DetectChanges()都会变慢,因为DbContext实例正在跟踪更多实体。
调用DbSet.Add()或DbSet.Remove()适当地设置实体的状态(不调用DetectChanges()),但更新实体的实体状态不会自动更新(这是DetectChanges的一件事()完成)。您需要在方法开头对DetectChanges()进行单次调用(隐式或显式),以确保将所有更新的实体标记为已更新。请注意,除非明确关闭,否则基本SaveChanges()方法会自动调用DetectChanges()。
此外,反复的反思电话可能没什么帮助。我建议使用System.Diagnostics.Stopwatch或更复杂的东西来分析这个方法,以明确识别你的瓶颈。