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:

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: