我正在使用EF 5构建MVC 4应用程序。 我需要做一个审计跟踪,即记录最终用户所做的任何更改。
我已经问了几次这个问题,但之前没有真正得到满意的答案。所以我希望在某个地方添加更多细节......
目前我有多个存储库
即
public class AuditZoneRepository : IAuditZoneRepository
{
private AISDbContext context = new AISDbContext();
public int Save(AuditZone model, ModelStateDictionary modelState)
{
if (model.Id == 0)
{
context.AuditZones.Add(model);
}
else
{
var recordToUpdate = context.AuditZones.FirstOrDefault(x => x.Id == model.Id);
if (recordToUpdate != null)
{
recordToUpdate.Description = model.Description;
recordToUpdate.Valid = model.Valid;
recordToUpdate.ModifiedDate = DateTime.Now;
}
}
try
{
context.SaveChanges();
return 1;
}
catch (Exception ex)
{
modelState.AddModelError("", "Database error has occured. Please try again later");
return -1;
}
}
}
public class PostcodesRepository : IPostcodesRepository
{
private AISDbContext context = new AISDbContext();
public int Save(Postcodes model, ModelStateDictionary modelState)
{
if (model.Id == 0)
{
context.Postcodes.Add(model);
}
else
{
var recordToUpdate = context.Postcodes.FirstOrDefault(x => x.Id == model.Id);
if (recordToUpdate != null)
{
recordToUpdate.Suburb = model.Suburb;
recordToUpdate.State = model.State;
recordToUpdate.Postcode = model.Postcode;
recordToUpdate.AuditZoneId = model.AuditZoneId;
recordToUpdate.ModifiedDate = DateTime.Now;
}
}
try
{
context.SaveChanges();
return 1;
}
catch (Exception ex)
{
modelState.AddModelError("", "Database error has occured. Please try again later");
return -1;
}
}
}
现在我知道我要添加代码来检查是否有任何更改我需要在保存尝试中添加它。在context.SaveChanges()之前。
但目前我有10个回购。我真的不想将代码添加到10个不同的地方。因为这段代码会做同样的事情。我想以某种方式拥有一个repos继承的基类。
任何帮助?任何示例代码?任何指针?
将不胜感激。我相信其他人会在此之前做到这一点
我正在修改我的密钥,关系和表格
public class AuditZoneMap : EntityTypeConfiguration<AuditZone>
{
public AuditZoneMap()
{
// Primary Key
HasKey(t => t.Id);
// Properties
Property(t => t.Description)
.HasMaxLength(100);
// Table & Column Mappings
ToTable("AuditZone");
Property(t => t.Id).HasColumnName("Id");
Property(t => t.Description).HasColumnName("Description");
Property(t => t.Valid).HasColumnName("Valid");
Property(t => t.CreatedDate).HasColumnName("CreatedDate");
Property(t => t.CreatedBy).HasColumnName("CreatedBy");
Property(t => t.ModifiedDate).HasColumnName("ModifiedDate");
Property(t => t.ModifiedBy).HasColumnName("ModifiedBy");
// Relationships
HasOptional(t => t.CreatedByUser)
.WithMany(t => t.CreatedByAuditZone)
.HasForeignKey(d => d.CreatedBy);
HasOptional(t => t.ModifiedByUser)
.WithMany(t => t.ModifiedByAuditZone)
.HasForeignKey(d => d.ModifiedBy);
}
}
答案 0 :(得分:24)
我建议您使用EF中的ChangeTracker属性。
在您的DBContext.cs中,您将拥有:
public class DBContext : DbContext
{
public DBContext () : base("DatabaseName")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
public DbSet<YourPocoModelNameHere > YourPocoModelNameHere { get; set; }
// This is overridden to prevent someone from calling SaveChanges without specifying the user making the change
public override int SaveChanges()
{
throw new InvalidOperationException("User ID must be provided");
}
public int SaveChanges(int userId)
{
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Added || p.State == System.Data.EntityState.Deleted || p.State == System.Data.EntityState.Modified))
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog x in GetAuditRecordsForChange(ent, userId))
{
this.AuditLogs.Add(x);
}
}
// Call the original SaveChanges(), which will save both the changes made and the audit records
return base.SaveChanges();
}
private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, int userId)
{
List<AuditLog> result = new List<AuditLog>();
DateTime changeTime = DateTime.UtcNow;
// Get the Table() attribute, if one exists
//TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), true).SingleOrDefault() as TableAttribute;
// Get table name (if it has a Table attribute, use that, otherwise get the pluralized name)
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
// Get primary key value (If you have more than one key column, this will need to be adjusted)
var keyNames = dbEntry.Entity.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).ToList();
string keyName = keyNames[0].Name; //dbEntry.Entity.GetType().GetProperties().Single(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).Name;
if (dbEntry.State == System.Data.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,
EventDateUTC = changeTime,
EventType = "A", // Added
TableName = tableName,
RecordId = dbEntry.CurrentValues.GetValue<object>(keyName).ToString(),
ColumnName = propertyName,
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
else if (dbEntry.State == System.Data.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,
EventDateUTC = changeTime,
EventType = "D", // Deleted
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue<object>(keyName).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.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,
EventDateUTC = changeTime,
EventType = "M", // Modified
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue<object>(keyName).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;
}
}
这将在您的数据库中使用以下表格:
USE [databasename]
GO
/****** Object: Table [dbo].[auditlog] Script Date: 06/01/2014 05:56:49 p. m. ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[auditlog](
[auditlogid] [uniqueidentifier] NOT NULL,
[userid] [int] NOT NULL,
[eventdateutc] [datetime] NOT NULL,
[eventtype] [char](1) NOT NULL,
[tablename] [nvarchar](100) NOT NULL,
[recordid] [nvarchar](100) NOT NULL,
[columnname] [nvarchar](100) NOT NULL,
[originalvalue] [nvarchar](max) NULL,
[newvalue] [nvarchar](max) NULL,
CONSTRAINT [PK_AuditLog] PRIMARY KEY NONCLUSTERED
(
[auditlogid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[auditlog] WITH CHECK ADD CONSTRAINT [FK_auditlog_users] FOREIGN KEY([userid])
REFERENCES [dbo].[users] ([userid])
GO
ALTER TABLE [dbo].[auditlog] CHECK CONSTRAINT [FK_auditlog_users]
GO
有了这一切,那么你只需要调用你的dbContext.SaveChanges(这里是userId);
希望这对你有用......我在所有应用程序中都使用它并且效果很好!
享受它。
答案 1 :(得分:8)
我找到了这个NuGet包(TrackerEnabledDbContext),并按照以下4个步骤进行了操作:
安装包TrackerEnabledDbContext
在TrackerEnabledDbContext命名空间中从TrackerContext继承我的DbContext
public class ApplicationDbContext : TrackerContext
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
添加迁移并更新我的数据库。为记录更改创建了两个新表(AuditLog和AuditLogDetails)。
确定要跟踪的表,并将[TrackChanges]
属性应用于类。如果您想跳过某些特定列的跟踪,可以将[SkipTracking]
属性应用于这些列(属性)。
每当您在数据库中进行更改时,都会调用DbContext.SaveChanges()
。现在你有一个可用的重载,它接受一个整数。这应该是登录人员的用户ID。如果您未通过用户ID,则此更改不会记录到跟踪表中。
databaseContext.SaveChanges(userId);
这就是全部。稍后您可以使用以下方法检索日志:
var AuditLogs = db.GetLogs<Proyecto>(id).ToList();
答案 2 :(得分:3)
免责声明:我是该项目的所有者Entity Framework Plus
EF +具有支持EF5,EF6和EF Core的审核功能。
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntityContext();
// ... ctx changes ...
var audit = new Audit();
audit.CreatedBy = "ZZZ Projects"; // Optional
ctx.SaveChanges(audit);
// Access to all auditing information
var entries = audit.Entries;
foreach(var entry in entries)
{
foreach(var property in entry.Properties)
{
}
}
许多选项都可用,如数据库中的AutoSave。
文档:EF+ Audit
答案 3 :(得分:2)
在Generic repository模式中,我们可以为db context savechanges事件编写通用事件处理程序。
我用Google搜索并收集了很多信息。
所以我打算编写通用的单一方法
我正在使用的Db结构
审核表
CREATE TABLE [dbo].[Audit](
[Id] [BIGINT] IDENTITY(1,1) NOT NULL,
[TableName] [nvarchar](250) NULL,
[Updated By] [nvarchar](100) NULL,
[Actions] [nvarchar](25) NULL,
[OldData] [text] NULL,
[NewData] [text] NULL,
[Created For] varchar(200) NULL,
[Updated Date] [datetime] NULL,
CONSTRAINT [PK_DBAudit] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
2.使用Audit表实体更新dbcontext。
3.为Dbcontext savechanges
挂钩通用事件处理程序c#code
namespace ARMS.Domain
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
public partial class ARMSContext
{
Collection<Audit> auditTrailList = new Collection<Audit>();
partial void OnContextCreated()
{
this.SavingChanges += new EventHandler(ArmsEntities_SavingChanges);
}
public enum AuditActions
{
Added,
Modified,
Deleted
}
void ArmsEntities_SavingChanges(object sender, EventArgs e)
{
auditTrailList.Clear();
IEnumerable<ObjectStateEntry> changes =
this.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Deleted | EntityState.Modified);
foreach (ObjectStateEntry stateEntryEntity in changes)
{
if (!stateEntryEntity.IsRelationship && stateEntryEntity.Entity != null && !(stateEntryEntity.Entity is Audit))
{
Audit audit = this.GetAudit(stateEntryEntity);
auditTrailList.Add(audit);
}
}
if (auditTrailList.Count > 0)
{
foreach (var audit in auditTrailList)
{
this.Audits.AddObject(audit);
}
}
}
public Audit GetAudit(ObjectStateEntry entry)
{
Audit audit = new Audit();
audit.Updated_By ="Test";
audit.TableName = entry.EntitySet.ToString();
audit.Updated_Date = DateTime.Now;
audit.Created_For = Convert.ToString(entry.Entity);
audit.Actions = Enum.Parse(typeof(AuditActions),entry.State.ToString(), true).ToString();
StringBuilder newValues = new StringBuilder();
StringBuilder oldValues = new StringBuilder();
if (entry.State == EntityState.Added)
{
SetAddedProperties(entry, newValues);
audit.NewData = newValues.ToString();
}
else if (entry.State == EntityState.Deleted)
{ SetDeletedProperties(entry, oldValues);
audit.OldData = oldValues.ToString();
}
else if (entry.State == EntityState.Modified)
{
SetModifiedProperties(entry, oldValues, newValues);
audit.OldData = oldValues.ToString();
audit.NewData = newValues.ToString();
}
return audit;
}
private void SetAddedProperties(ObjectStateEntry entry, StringBuilder newData)
{
CurrentValueRecord currentValues = entry.CurrentValues;
for (int i = 0; i < currentValues.FieldCount; i++)
{
newData.AppendFormat("{0}={1} || ", currentValues.GetName(i), currentValues.GetValue(i));
}
}
private void SetDeletedProperties(ObjectStateEntry entry, StringBuilder oldData)
{
foreach (var propertyName in entry.GetModifiedProperties())
{
var oldVal = entry.OriginalValues[propertyName];
if (oldVal != null)
{
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
}
private void SetModifiedProperties(ObjectStateEntry entry, StringBuilder oldData, StringBuilder newData)
{
foreach (var propertyName in entry.GetModifiedProperties())
{
var oldVal = entry.OriginalValues[propertyName];
var newVal = entry.CurrentValues[propertyName];
if (oldVal != null && newVal != null && !Equals(oldVal, newVal))
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
}
}
}
答案 4 :(得分:1)
创建一个类来捕获更改或跟踪实体添加,修改或删除时的更改。
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Web;
namespace MVC_AuditTrail.Models
{
public class AuditTrailFactory
{
private DbContext context;
public AuditTrailFactory(DbContext context)
{
this.context = context;
}
public Audit GetAudit(DbEntityEntry entry)
{
Audit audit = new Audit();
// var user = (User)HttpContext.Current.Session[":user"];
audit.UserId = "swapnil";// user.UserName;
audit.TableName = GetTableName(entry);
audit.UpdateDate = DateTime.Now;
audit.TableIdValue = GetKeyValue(entry);
//entry is Added
if (entry.State == EntityState.Added)
{
var newValues = new StringBuilder();
SetAddedProperties(entry, newValues);
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.I.ToString();
}
//entry in deleted
else if (entry.State == EntityState.Deleted)
{
var oldValues = new StringBuilder();
SetDeletedProperties(entry, oldValues);
audit.OldData = oldValues.ToString();
audit.Actions = AuditActions.D.ToString();
}
//entry is modified
else if (entry.State == EntityState.Modified)
{
var oldValues = new StringBuilder();
var newValues = new StringBuilder();
SetModifiedProperties(entry, oldValues, newValues);
audit.OldData = oldValues.ToString();
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.U.ToString();
}
return audit;
}
private void SetAddedProperties(DbEntityEntry entry, StringBuilder newData)
{
foreach (var propertyName in entry.CurrentValues.PropertyNames)
{
var newVal = entry.CurrentValues[propertyName];
if (newVal != null)
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
}
}
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
private void SetDeletedProperties(DbEntityEntry entry, StringBuilder oldData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in dbValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
if (oldVal != null)
{
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
}
private void SetModifiedProperties(DbEntityEntry entry, StringBuilder oldData, StringBuilder newData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in entry.OriginalValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
var newVal = entry.CurrentValues[propertyName];
if (oldVal != null && newVal != null && !Equals(oldVal, newVal))
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
public long? GetKeyValue(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
long id = 0;
if (objectStateEntry.EntityKey.EntityKeyValues != null)
id = Convert.ToInt64(objectStateEntry.EntityKey.EntityKeyValues[0].Value);
return id;
}
private string GetTableName(DbEntityEntry dbEntry)
{
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
return tableName;
}
}
public enum AuditActions
{
I,
U,
D
}
}
然后创建审计表实体和上下文类。
此方法中的覆盖savechanges方法获取审计更改并在保存基本实体之前保存。
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Web;
namespace MVC_AuditTrail.Models
{
public class Student
{
public int StudentID { get; set; }
public string Name { get; set; }
public string mobile { get; set; }
}
public class Audit
{
public long Id { get; set; }
public string TableName { get; set; }
public string UserId { get; set; }
public string Actions { get; set; }
public string OldData { get; set; }
public string NewData { get; set; }
public Nullable<long> TableIdValue { get; set; }
public Nullable<System.DateTime> UpdateDate { get; set; }
}
public class StdContext : DbContext
{
private AuditTrailFactory auditFactory;
private List<Audit> auditList = new List<Audit>();
private List<DbEntityEntry> objectList = new List<DbEntityEntry>();
public StdContext() : base("stdConnection")
{
Database.SetInitializer<StdContext>(new CreateDatabaseIfNotExists<StdContext>());
}
public DbSet<Student> Student { get; set; }
public DbSet<Audit> Audit { get; set; }
public override int SaveChanges()
{
auditList.Clear();
objectList.Clear();
auditFactory = new AuditTrailFactory(this);
var entityList = ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified);
foreach (var entity in entityList)
{
Audit audit = auditFactory.GetAudit(entity);
bool isValid = true;
if (entity.State == EntityState.Modified && string.IsNullOrWhiteSpace(audit.NewData) && string.IsNullOrWhiteSpace(audit.OldData))
{
isValid = false;
}
if (isValid)
{
auditList.Add(audit);
objectList.Add(entity);
}
}
var retVal = base.SaveChanges();
if (auditList.Count > 0)
{
int i = 0;
foreach (var audit in auditList)
{
if (audit.Actions == AuditActions.I.ToString())
audit.TableIdValue = auditFactory.GetKeyValue(objectList[i]);
this.Audit.Add(audit);
i++;
}
base.SaveChanges();
}
return retVal;
}
}
}
答案 5 :(得分:0)
public override int SaveChanges()
{
var auditEntries = new List<AuditEntry>();
var modifiedEntities = ChangeTracker.Entries()
.Where(p => p.State == EntityState.Modified).ToList();
foreach (var change in modifiedEntities)
{
var auditEntry = new AuditEntry(change);
var primaryKey = GetPrimaryKeys(change);
auditEntry.TableName = change.Entity.GetType().Name;//get table name
// var id = change.CurrentValues.GetValue<object>("Id").ToString();//get current id
auditEntry.EntityId = primaryKey.ToString();
auditEntry.EntityTypeId = primaryKey.ToString();
auditEntries.Add(auditEntry);
foreach (var prop in change.OriginalValues.PropertyNames)
{
if (prop == "Id")
{
continue;
}
switch (change.State)
{
case EntityState.Modified:
if ((change.State & EntityState.Modified) != 0)
{
auditEntry.OldValues[prop] = change.OriginalValues[prop].ToString();
auditEntry.NewValues[prop] = change.CurrentValues[prop].ToString();
}
break;
}
}
foreach (var auditEntry1 in auditEntries.Where(_ => !_.HasTemporaryProperties))
{
Audits.Add(auditEntry1.ToAudit());
}
}
return base.SaveChanges();
}
private object GetPrimaryKeys(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
return objectStateEntry.EntityKey.EntityKeyValues[0].Value;
}
答案 6 :(得分:0)
您可以这样做,而无需任何用于Entity Framework Core的外部库:
using (var context = new SampleContext())
{
// Insert a row
var customer = new Customer();
customer.FirstName = "John";
customer.LastName = "doe";
context.Customers.Add(customer);
await context.SaveChangesAsync();
// Update the first customer
customer.LastName = "Doe";
await context.SaveChangesAsync();
// Delete the customer
context.Customers.Remove(customer);
await context.SaveChangesAsync();
}
型号:
public class Audit
{
public int Id { get; set; }
public string TableName { get; set; }
public DateTime DateTime { get; set; }
public string KeyValues { get; set; }
public string OldValues { get; set; }
public string NewValues { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class SampleContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Audit> Audits { get; set; }
}
DbContext:
public class SampleContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Audit> Audits { get; set; }
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var auditEntries = OnBeforeSaveChanges();
var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
await OnAfterSaveChanges(auditEntries);
return result;
}
private List<AuditEntry> OnBeforeSaveChanges()
{
ChangeTracker.DetectChanges();
var auditEntries = new List<AuditEntry>();
foreach (var entry in ChangeTracker.Entries())
{
if (entry.Entity is Audit || entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
continue;
var auditEntry = new AuditEntry(entry);
auditEntry.TableName = entry.Metadata.Relational().TableName;
auditEntries.Add(auditEntry);
foreach (var property in entry.Properties)
{
if (property.IsTemporary)
{
// value will be generated by the database, get the value after saving
auditEntry.TemporaryProperties.Add(property);
continue;
}
string propertyName = property.Metadata.Name;
if (property.Metadata.IsPrimaryKey())
{
auditEntry.KeyValues[propertyName] = property.CurrentValue;
continue;
}
switch (entry.State)
{
case EntityState.Added:
auditEntry.NewValues[propertyName] = property.CurrentValue;
break;
case EntityState.Deleted:
auditEntry.OldValues[propertyName] = property.OriginalValue;
break;
case EntityState.Modified:
if (property.IsModified)
{
auditEntry.OldValues[propertyName] = property.OriginalValue;
auditEntry.NewValues[propertyName] = property.CurrentValue;
}
break;
}
}
}
// Save audit entities that have all the modifications
foreach (var auditEntry in auditEntries.Where(_ => !_.HasTemporaryProperties))
{
Audits.Add(auditEntry.ToAudit());
}
// keep a list of entries where the value of some properties are unknown at this step
return auditEntries.Where(_ => _.HasTemporaryProperties).ToList();
}
private Task OnAfterSaveChanges(List<AuditEntry> auditEntries)
{
if (auditEntries == null || auditEntries.Count == 0)
return Task.CompletedTask
foreach (var auditEntry in auditEntries)
{
// Get the final value of the temporary properties
foreach (var prop in auditEntry.TemporaryProperties)
{
if (prop.Metadata.IsPrimaryKey())
{
auditEntry.KeyValues[prop.Metadata.Name] = prop.CurrentValue;
}
else
{
auditEntry.NewValues[prop.Metadata.Name] = prop.CurrentValue;
}
}
// Save the Audit entry
Audits.Add(auditEntry.ToAudit());
}
return SaveChangesAsync();
}
}
public class AuditEntry
{
public AuditEntry(EntityEntry entry)
{
Entry = entry;
}
public EntityEntry Entry { get; }
public string TableName { get; set; }
public Dictionary<string, object> KeyValues { get; } = new Dictionary<string, object>();
public Dictionary<string, object> OldValues { get; } = new Dictionary<string, object>();
public Dictionary<string, object> NewValues { get; } = new Dictionary<string, object>();
public List<PropertyEntry> TemporaryProperties { get; } = new List<PropertyEntry>();
public bool HasTemporaryProperties => TemporaryProperties.Any();
public Audit ToAudit()
{
var audit = new Audit();
audit.TableName = TableName;
audit.DateTime = DateTime.UtcNow;
audit.KeyValues = JsonConvert.SerializeObject(KeyValues);
audit.OldValues = OldValues.Count == 0 ? null : JsonConvert.SerializeObject(OldValues);
audit.NewValues = NewValues.Count == 0 ? null : JsonConvert.SerializeObject(NewValues);
return audit;
}
}
来源:
https://www.meziantou.net/entity-framework-core-history-audit-table.htm
如果您使用的是Entity Framework 6或Entity Framework Core,则也可以使用@ thepirat000编写的Audit.NET和Audit.EntityFramework。效果很好,但我希望最少的NuGet依赖项(最好为0)不受大型社区/公司的支持,并且在很大程度上依赖于单个开发人员。
https://github.com/thepirat000/Audit.NET/graphs/contributors
您还可以阅读有关Slowly changing dimension类型的更多信息,并从那里创建适合您需求的解决方案。
如果您需要整个Entity Framework快照历史记录,请查看this answer。
答案 7 :(得分:-1)
另一个选择是创建审核操作属性,以允许用描述审核操作的属性来修饰方法。只需从Attribute继承并列出要在构造函数中捕获的信息,然后创建一个拦截器(从Castle继承),以拦截对方法的请求。拦截器将调用审核服务(这是将审核消息写入数据库的简单类)。