实体框架6 - 审核日志

时间:2014-08-11 14:11:09

标签: entity-framework-5 entity-framework-6 audit-trail

我对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();
}

1 个答案:

答案 0 :(得分:3)

您最大的性能问题似乎是对ChangeTracker.Entries()和DbSet.Add()的调用都在内部触发DetectChanges()方法,该方法与DbContext实例主动跟踪的实体数量成慢比例。我怀疑在foreach循环中对.Add()的调用是主要的罪魁祸首,因为它正在调用该方法n次。因为要在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或更复杂的东西来分析这个方法,以明确识别你的瓶颈。