Entity Framework Classic Audit
Description
The EF Audit feature allows you to create an audit trail of all changes that occured when saving in Entity Framework.
The audit trail can be automatically saved in a database or log file.
public class EntityContext : DbContext { public EntityContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { // Add your configuration here this.Configuration.Audit.AutoSaveSet = this.XmlAuditEntries; this.Configuration.Audit.IsEnabled = true; } public DbSet<Customer> Customers { get; set; } public DbSet<XmlAuditEntry> XmlAuditEntries { get; set; } } public static void Main() { using (var context = new EntityContext()) { context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); // Save changes and Audit trail in database context.SaveChanges(); // Display Audit trail var auditEntries = context.XmlAuditEntries.ToAuditEntries(); FiddleHelper.WriteTable("1 - Audit Entries", auditEntries); FiddleHelper.WriteTable("2 - Audit Properties", auditEntries.SelectMany(x => x.Properties)); } }
Try it: NET Core | NET Framework
This feature allows you to handle various scenario such as:
- Saving audit trail in a database
- Saving audit trail in a log file
- Saving audit trail in a different database
- Displaying audit trail history
What is supported?
SaveChanges()
BatchSaveChanges()
BulkSaveChanges()
- All types of modifications:
EntityAdded
EntityModified
EntityDeleted
EntitySoftDeleted
RelationshipAdded
RelationshipDeleted
Advantage
- Track what, who, and when a value is changed
- Keep the audit trail of all changes
- Display the audit trail of all changes
Community vs Enterprise
The Audit
feature is free to use in the Community version.
The Enterprise version offer performance enhancement by automatically saving audit entries using the BulkInsert
.
Getting Started
Enable Auditing
By default, not to impact performance, the Audit feature is disabled. You can activate it by enabling the following configuration context.Configuration.Audit.IsEnabled = true
.
Always enabled
To have the Audit feature always enabled, you activate it in your context constructor.
public class EntityContext : DbContext { public EntityContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { // Add your configuration here this.Configuration.Audit.AutoSaveSet = this.XmlAuditEntries; this.Configuration.Audit.IsEnabled = true; } public DbSet<Customer> Customers { get; set; } public DbSet<XmlAuditEntry> XmlAuditEntries { get; set; } } public static void Main() { using (var context = new EntityContext()) { context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); // Save changes and Audit trail in database context.SaveChanges(); // Display Audit trail var auditEntries = context.XmlAuditEntries.ToAuditEntries(); FiddleHelper.WriteTable("1 - Audit Entries", auditEntries); FiddleHelper.WriteTable("2 - Audit Properties", auditEntries.SelectMany(x => x.Properties)); } }
Try it: NET Core | NET Framework
On Demand enabled
To have the Audit feature on demand enabled, you activate it after the context is created.
public class EntityContext : DbContext { public EntityContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { // Add your configuration here this.Configuration.Audit.AutoSaveSet = this.XmlAuditEntries; } public DbSet<Customer> Customers { get; set; } public DbSet<XmlAuditEntry> XmlAuditEntries { get; set; } } public static void Main() { using (var context = new EntityContext()) { // You can activate the Audit feature on demand by enabling it after the context is created. context.Configuration.Audit.IsEnabled = true; context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); // Save changes and Audit trail in database context.SaveChanges(); // Display Audit trail var auditEntries = context.XmlAuditEntries.ToAuditEntries(); FiddleHelper.WriteTable("1 - Audit Entries", auditEntries); FiddleHelper.WriteTable("2 - Audit Properties", auditEntries.SelectMany(x => x.Properties)); } }
Try it: NET Core | NET Framework
Last Audit
The latest audit can be accessed with the LastAudit
property.
The LastAudit
property gives you additional information that are not saved such as:
- Entity
- Entry
- OldValueRaw
- NewValueRaw
context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); context.Customers.Add(new Customer() { Name = "Customer_B", Description = "Description" }); context.SaveChanges(); var lastAudit = context.Configuration.Audit.LastAudit; FiddleHelper.WriteTable("1 - LastAudit - Entries", lastAudit.Entries); FiddleHelper.WriteTable("2 - LastAudit - Properties", lastAudit.Entries.SelectMany(x => x.Properties));
Try it: NET Core | NET Framework
AutoSave Set
To automatically save the audit trail in your database, you need to specify the DbSet<>
in which audit entries will be added then saved.
One of those following set must be added to your context:
Name | Description | Example |
---|---|---|
AuditEntry |
The AuditEntry class allows you to save one row per property (Only recommended for Enterprise version). The DbSet<AuditEntry> and DbSet<AuditEntryProperty> must be added to your context. |
NET Core / NET Framework |
XmlAuditEntry |
The XmlAuditEntry class allows you to save your properties in an XML format. The DbSet<XmlAuditEntry> must be added to your context. |
NET Core / NET Framework |
AutoSave Action
To automatically save the audit trail in another context or a log file, you need to specify an AutoSaveAction
. This action will be executed after all saves are completed.
public EntityContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { var audit = this.Configuration.Audit; audit.AutoSaveAction = (context, auditing) => { foreach(var entry in auditing.EntriesXml) { Log.AppendLine("EntitySetName: " + entry.EntitySetName); Log.AppendLine("EntityTypeName: " + entry.EntityTypeName); Log.AppendLine("State: " + entry.State); Log.AppendLine("StateName: " + entry.StateName); Log.AppendLine("CreatedBy: " + entry.CreatedBy); Log.AppendLine("CreatedDate: " + entry.CreatedDate); Log.AppendLine("XmlProperties: " + entry.XmlProperties); Log.AppendLine("---"); Log.AppendLine("---"); Log.AppendLine("---"); } }; audit.IsEnabled = true; } // ...code... using (var context = new EntityContext()) { context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); context.Customers.Add(new Customer() { Name = "Customer_B", Description = "Description" }); context.Customers.Add(new Customer() { Name = "Customer_C", Description = "Description" }); // Save changes with Audit Enabled context.SaveChanges(); } // Display Audit Trail Console.WriteLine(Log.ToString());
Try it: NET Core | NET Framework
Real Life Scenarios
Saving audit trail in a database
Your application need to keep an audit trail of all changes in a database. You can automatically save the audit trail by specifying an AutoSaveSet
.
public class EntityContext : DbContext { public EntityContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { // Add your configuration here // Saving audit trail in a database this.Configuration.Audit.AutoSaveSet = this.XmlAuditEntries; this.Configuration.Audit.IsEnabled = true; } public DbSet<Customer> Customers { get; set; } public DbSet<XmlAuditEntry> XmlAuditEntries { get; set; } } public static void Main() { // Many changes using (var context = new EntityContext()) { context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); context.Customers.Add(new Customer() { Name = "Customer_B", Description = "Description" }); // Save changes and Audit trail in database context.SaveChanges(); } using (var context = new EntityContext()) { var customers = context.Customers.ToList(); context.Customers.Remove(customers.Where(x => x.Name == "Customer_B").First()); customers.Where(x => x.Name == "Customer_A").First().Description = "updated"; // Save changes and Audit trail in database context.SaveChanges(); } // Display using (var context = new EntityContext()) { // Displaying audit trail history var auditEntries = context.XmlAuditEntries.ToAuditEntries(); FiddleHelper.WriteTable("1 - Audit Entries", auditEntries); FiddleHelper.WriteTable("2 - Audit Properties", auditEntries.SelectMany(x => x.Properties)); } }
Try it: NET Core | NET Framework
Saving audit trail in a log file
Your application need to keep an audit trail of all changes in a log file. You can automatically save the audit trail in a log file by specifying an AutoSaveAction
.
public class EntityContext : DbContext { public EntityContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { // Add your configuration here this.Configuration.Audit.AutoSaveAction = (context,auditing) => { using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"Log.txt")) { foreach(var entry in auditing.EntriesXml) { file.WriteLine("EntitySetName: " + entry.EntitySetName); file.WriteLine("EntityTypeName: " + entry.EntityTypeName); file.WriteLine("State: " + entry.State); file.WriteLine("StateName: " + entry.StateName); file.WriteLine("CreatedBy: " + entry.CreatedBy); file.WriteLine("CreatedDate: " + entry.CreatedDate); file.WriteLine("XmlProperties: "); file.WriteLine(entry.XmlProperties); file.WriteLine("---"); file.WriteLine("---"); file.WriteLine("---"); } } }; this.Configuration.Audit.IsEnabled = true; } public DbSet<Customer> Customers { get; set; } } public static void Main() { using (var context = new EntityContext()) { context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); context.Customers.Add(new Customer() { Name = "Customer_B", Description = "Description" }); context.Customers.Add(new Customer() { Name = "Customer_C", Description = "Description" }); // Save changes with Audit Enabled context.SaveChanges(); } // Display Audit Trail in the Log.txt string[] lines = System.IO.File.ReadAllLines(@"Log.txt"); foreach (string line in lines) { Console.WriteLine(line); } }
Try it: NET Core | NET Framework
Saving audit trail in a different database
Your application need to keep an audit trail of all changes in a different database. You can automatically save the audit trail in a different database file by specifying an AutoSaveAction
.
public class EntityContext : DbContext { public EntityContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { // Add your configuration here this.Configuration.Audit.AutoSaveAction = (context, auditing) => { var auditContext = new AuditContext(); auditContext.XmlAuditEntries.AddRange(auditing.EntriesXml); auditContext.SaveChanges(); }; this.Configuration.Audit.IsEnabled = true; } public DbSet<Customer> Customers { get; set; } } public class AuditContext : DbContext { public AuditContext() : base(FiddleHelper.GetConnectionStringSqlServer()) { } public DbSet<XmlAuditEntry> XmlAuditEntries { get; set; } } public static void Main() { using (var context = new EntityContext()) { context.Customers.Add(new Customer() { Name = "Customer_A", Description = "Description" }); // Save changes and Audit trail in database context.SaveChanges(); } using(var auditContext = new AuditContext()) { // Display Audit trail var auditEntries = auditContext.XmlAuditEntries.ToAuditEntries(); FiddleHelper.WriteTable("1 - Audit Entries", auditEntries); FiddleHelper.WriteTable("2 - Audit Properties", auditEntries.SelectMany(x => x.Properties)); } }
Try it: NET Core | NET Framework
Displaying audit trail history
Your application need to display the audit trail of all changes in a user interface. If your audit trail is saved in a database, you can retrieve and display audit entries by using your audit DbSet<>
.
// Display using (var context = new EntityContext()) { // Displaying audit trail history var auditEntries = context.XmlAuditEntries.ToAuditEntries(); FiddleHelper.WriteTable("1 - Audit Entries", auditEntries); FiddleHelper.WriteTable("2 - Audit Properties", auditEntries.SelectMany(x => x.Properties)); }
Try it: NET Core | NET Framework
Documentation
AuditManager
The AuditManager
allows you to configure how the audit trail will be created, saved, and retrieved.
Properties
Name | Description | Default | Example |
---|---|---|---|
IsEnabled |
Gets or sets if the Audit feature is enabled. By default, this feature is disabled not to impact the performance. |
false |
NET Core / NET Framework |
AutoSaveAction |
Gets or sets the AutoSaveAction . This option is usually used to automatically save the audit trail in a log file or different database. |
null |
NET Core / NET Framework |
AutoSaveSet |
Gets or sets the AutoSaveSet . This option is usually used to automatically save the audit trail in a database. |
null |
NET Core / NET Framework |
LastAudit |
Gets the last Audit trail. |
null |
NET Core / NET Framework |
Methods (Display)
Name | Description | Example |
---|---|---|
DisplayDBNullAsNull(bool value) |
Format DBNull.Value as 'null' value. True by default. | NET Core / NET Framework |
DisplayFormatType<Type>(Func<Type, string> formatter) |
Format the specified type into a string. | NET Core / NET Framework |
DisplayEntityName<TEntityType>(string name) |
Use the specified name for the EntityName value. |
NET Core / NET Framework |
DisplayEntityName<TEntityType>(Func<TEntityType, string, string> formatter) |
Use the factory specified name for the EntityName value. |
NET Core / NET Framework |
DisplayPropertyName<TEntityType>(Expression<Func<TEntityType, object>> propertySelector, string name) |
Use the specified name for the PropertyName value. |
NET Core / NET Framework |
DisplayPropertyName<TEntityType>(Expression<Func<TEntityType, object>> propertySelector, Func<TEntityType, string, string> formatter) |
Use the factory specified name for the PropertyName value. |
NET Core / NET Framework |
Methods (Include & Exclude)
Name | Description | Example |
---|---|---|
ExcludeEntity() |
Excludes all entities from the audit. | NET Core / NET Framework |
ExcludeEntity<TEntityType>() |
Excludes all entities of TEntityType type from the audit. |
NET Core / NET Framework |
ExcludeEntity(Func<object, bool> predicate) |
Excludes all entities that satisfy the predicate from the audit. | NET Core / NET Framework |
ExcludeEntity<TEntityType>(Func<TEntityType, bool> predicate) |
Excludes all entities of TEntityType that satisfy the predicate from the audit. |
NET Core / NET Framework |
ExcludeEntity(AuditEntryState entryState) |
Excludes all entities with specified AuditEntryState from the audit. |
NET Core / NET Framework |
ExcludeEntity<TEntityType>(AuditEntryState entryState) |
Excludes all entities of 'TEntityType' type and with specified AuditEntryState from the audit. |
NET Core / NET Framework |
ExcludeEntity(Func<object, bool> predicate, AuditEntryState entryState) |
Excludes all entities that satisfy the predicate and with specified AuditEntryState from the audit. |
NET Core / NET Framework |
ExcludeEntity<TEntityType>(Func<TEntityType, bool> predicate, AuditEntryState entryState) |
Excludes all entities of T / EntityType that satisfy the predicate and with specified AuditEntryState from the audit. |
NET Core / NET Framework |
ExcludeProperty() |
Excludes all properties from the audit. Key properties are never excluded. | NET Core / NET Framework |
ExcludeProperty<TEntityType>() |
Excludes all properties of a TEntityType type from the audit. Key properties are never excluded. |
NET Core / NET Framework |
ExcludeProperty<TEntityType>(Expression<Func<TEntityType, object>> propertySelector) |
Excludes specified properties of a TEntityType type from the audit. Key properties are never excluded. |
NET Core / NET Framework |
ExcludePropertyUnchanged() |
Excludes all properties value unchanged from the audit. Key values are never excluded. | NET Core / NET Framework |
ExcludePropertyUnchanged<TEntityType>() |
Excludes all properties value unchanged of a TEntityType type from the audit. Key values are never excluded. |
NET Core / NET Framework |
ExcludePropertyUnchanged(Func<object, bool> predicate) |
Excludes all properties value that satistfy the predicate from the audit. Key values are never excluded. | NET Core / NET Framework |
IncludeEntity() |
Includes all entities in the audit. | NET Core / NET Framework |
IncludeEntity<TEntityType>() |
Includes all entities of TEntityType type in the audit. |
NET Core / NET Framework |
IncludeEntity(Func<object, bool> predicate) |
Includes all entities that satisfy the predicate in the audit. | NET Core / NET Framework |
IncludeEntity<TEntityType>(Func<TEntityType, bool> predicate) |
Includes all entities of TEntityType that satisfy the predicate in the audit. |
NET Core / NET Framework |
IncludeEntity(AuditEntryState entryState) |
Includes all entities with specified AuditEntryState in the audit. |
NET Core / NET Framework |
IncludeEntity<TEntityType>(AuditEntryState entryState) |
Includes all entities of 'TEntityType' type and with specified AuditEntryState in the audit. |
NET Core / NET Framework |
IncludeEntity(Func<object, bool> predicate, AuditEntryState entryState) |
Includes all entities that satisfy the predicate and with specified AuditEntryState in the audit. |
NET Core / NET Framework |
IncludeEntity<TEntityType>(Func<TEntityType, bool> predicate, AuditEntryState entryState) |
Includes all entities of TEntityType that satisfy the predicate and with specified AuditEntryState in the audit. |
NET Core / NET Framework |
IncludeProperty() |
Includes all properties in the audit. | NET Core / NET Framework |
IncludeProperty<TEntityType>() |
Includes all properties of a TEntityType type in the audit. |
NET Core / NET Framework |
IncludeProperty<TEntityType>(Expression<Func<TEntityType, object>> propertySelector) |
Includes specified properties of a TEntityType type in the audit. |
NET Core / NET Framework |
IncludePropertyUnchanged() |
Includes all property values unchanged in the audit. | NET Core / NET Framework |
IncludePropertyUnchanged<TEntityType>() |
Includes all property values unchanged of a TEntityType type in the audit. |
NET Core / NET Framework |
IncludePropertyUnchanged(Func<object, bool> predicate) |
Includes all property values that satistfy the predicate in the audit. | NET Core / NET Framework |
UseIncludeDataAnnotation(bool value) |
Exclude all entities and properties from the Audit. Only entities and properties with Include data annotations or specified with the fluent API will be included. |
NET Core / NET Framework |
Methods (Soft Delete)
Name | Description | Example |
---|---|---|
SoftDeleted(Func<object, bool> predicate) |
Change the AuditEntryState from EntityModified to EntitySoftDeleted for all entities that satisfy the soft delete predicate. |
NET Core / NET Framework |
SoftDeleted<TEntityType>(Func<TEntityType, bool> predicate) |
Change the AuditEntryState from EntityModified to EntitySoftDeleted for all entities of TEntityType type and that satisfy the soft delete predicate. |
NET Core / NET Framework / |
Audit
The Audit
class provide information about the audit trail.
Properties
Name | Description | Example |
---|---|---|
Entries |
Gets a list of AuditEntry . |
NET Core / NET Framework |
EntriesXml |
Gets a list of XmlAuditEntry . |
NET Core / NET Framework |
Manager |
Gets the AuditManager |
NET Core / NET Framework |
AuditEntry
The AuditEntry
class contains information about the entry and a list of AuditEntryProperty
.
Properties (Mapped)
This properties values are saved in a database.
Name | Description | Example |
---|---|---|
AuditEntryID |
Gets or sets the AuditEntryID . |
NET Core / NET Framework |
EntitySetName |
Gets or sets the EntitySet name. |
NET Core / NET Framework |
EntityTypeName |
Gets or sets the EntityType name. |
NET Core / NET Framework |
State |
Gets or sets the AuditEntryState . |
NET Core / NET Framework |
StateName |
Gets or sets the AuditEntryState name. |
NET Core / NET Framework |
CreatedBy |
Gets or sets the AuditEntry created user. |
NET Core / NET Framework |
CreatedDate |
Gets or sets the AuditEntry created date. |
NET Core / NET Framework |
Properties |
Gets or sets the AuditEntry properties. |
NET Core / NET Framework |
Properties (Unmapped)
This properties values are only accessible via the LastAudit
property.
Name | Description | Example |
---|---|---|
Parent |
Gets or sets the parent Audit . |
NET Core / NET Framework |
Entity |
Gets or sets the audited Entity . |
NET Core / NET Framework |
Entry |
Gets or sets the audited ObjectStateEntry . |
NET Core / NET Framework |
Database First SQL
Coming soon...
AuditEntryProperty
The AuditEntryProperty
contains information about property
.
Properties (Mapped)
These property values are saved in a database.
Name | Description | Example |
---|---|---|
Parent |
Gets or sets the parent AuditEntry . |
NET Core / NET Framework |
AuditEntryPropertyID |
Gets or sets the AuditEntryPropertyID . |
NET Core / NET Framework |
AuditEntryID |
Gets or sets the AuditEntryID . |
NET Core / NET Framework |
RelationName |
Gets or sets the relation name. Only available for RelationshipAdded and RelationshipDeleted state |
NET Core / NET Framework |
PropertyName |
Gets or sets the property name. | NET Core / NET Framework |
OldValue |
Gets or sets the old value formatted as string. Avalable for Modified , Deleted , and RelationshipDeleted state. |
NET Core / NET Framework |
NewValue |
Gets or sets the new value formatted as string. Avalable for Insert , Modified , and RelationshipModified state. |
NET Core / NET Framework |
Properties (Unmapped)
These property values are only accessible via the LastAudit
property.
Name | Description | Example |
---|---|---|
OldValueRaw |
Gets or sets the old raw value. This is the original raw value without being formatted. | NET Core / NET Framework |
NewValueRaw |
Gets or sets the new raw value. This is the original raw value without being formatted. | NET Core / NET Framework |
Database First SQL
Coming soon...
XmlAuditEntry
The XmlAuditEntry
class contains information about the entry and all properties in a Xml
format.
Properties (Mapped)
Name | Description | Example |
---|---|---|
XmlAuditEntryID |
Gets or sets the AuditEntryID . |
NET Core / NET Framework |
EntitySetName |
Gets or sets the EntitySet name. |
NET Core / NET Framework |
EntityTypeName |
Gets or sets the EntityType name. |
NET Core / NET Framework |
State |
Gets or sets the AuditEntryState . |
NET Core / NET Framework |
StateName |
Gets or sets the AuditEntryState name. |
NET Core / NET Framework |
CreatedBy |
Gets or sets the AuditEntry created user. |
NET Core / NET Framework |
CreatedDate |
Gets or sets the AuditEntry created date. |
NET Core / NET Framework |
XmlProperties |
Gets or sets audit properties formatted as Xml . |
NET Core / NET Framework |
Database First SQL
Coming soon...
Data Annotations
Entity
Name | Description | Example |
---|---|---|
AuditDisplay(string name) |
Attribute to change the Audit entity or property display name. | NET Core / NET Framework |
AuditExclude |
Attribute to exclude from the audit the entity or property. | NET Core / NET Framework |
AuditInclude |
Attribute to include in the audit the entity or property. Require to enable Include with AuditManager UseIncludeDataAnnotation(true) method. |
NET Core / NET Framework |
Property
Name | Description | Example |
---|---|---|
AuditDisplay(string name) |
Attribute to change the Audit entity or property display. | NET Core / NET Framework |
AuditDisplayFormat(string dataFormatString) |
Attribute to change the Audit property display format. | NET Core / NET Framework |
AuditExclude |
Attribute to exclude from the audit the entity or property. | NET Core / NET Framework |
AuditInclude |
Attribute to include in the audit the entity or property. Require to enable Include with AuditManager UseIncludeDataAnnotation(true) method. |
NET Core / NET Framework |
Extension Methods
Name | Description | Example |
---|---|---|
Where<TEntityType>(this DbSet<AuditEntry> set) |
Gets the audit trail of all entries entry of TEntityType type. |
NET Core / NET Framework |
Where<TEntityType>(this DbSet<AuditEntry> set, TEntityType entry) |
Gets the audit trail of the specific entry. | NET Core / NET Framework |
Where<TEntityType>(this DbSet<AuditEntry> set, params object[] keyValues) |
Gets the audit trail of the specific key. | NET Core / NET Framework |
Where<TEntityType>(this DbSet<XmlAuditEntry> set) |
Gets the audit trail of all entries entry of TEntityType type. |
NET Core / NET Framework |
ToAuditEntries(this IEnumerable<XmlAuditEntry> items) |
Return a list of XmlAuditEntry converted into AuditEntry . |
NET Core / NET Framework |
Limitations
Bulk Operations & Batch Operations
All operations that doesn't use the ChangeTracker
(required to create the audit trail) are currently not supported: