A .NET package for Entity Framework, providing comprehensive change tracking with deep insights. Capture creation, updates, deletions, and restorations of entities, including property names, old and new values, stack traces, and user details, all neatly stored in an Audits column as JSON.
Seamless Entity Auditing: Easily integrate audit functionality into your Entity Framework applications, offering a complete audit trail enriched with stack traces and user information. Gain full visibility into entity lifecycle changes for compliance, debugging, and accountability.
Full Entity Lifecycle Visibility: Track and visualize the complete life cycle of your entities with detailed auditing. In addition to changes, this package records the stack trace of changes and user actions, enabling a deeper understanding of data evolution and robust audit trails.
dotnet add package R8.EntityFrameworkCore.AuditProvider
The Interceptor does not support queries decorated with .AsNoTracking()
since it is not possible to track changes on entities that are not being tracked.
// ... other services
// Add AuditProvider
services.AddEntityFrameworkAuditProvider(options =>
{
options.JsonOptions.WriteIndented = false;
options.AuditFlagSupport.Created = AuditFlagState.ActionDate | AuditFlagState.Storage;
options.AuditFlagSupport.Changed = AuditFlagState.ActionDate | AuditFlagState.Storage;
options.AuditFlagSupport.Deleted = AuditFlagState.ActionDate | AuditFlagState.Storage;
options.AuditFlagSupport.UnDeleted = AuditFlagState.ActionDate | AuditFlagState.Storage;
options.MaxStoredAudits = 10;
options.DateTimeProvider = serviceProvider => DateTime.UtcNow;
options.UserProvider = serviceProvider =>
{
var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
var user = httpContextAccessor.HttpContext?.User;
if (user?.Identity?.IsAuthenticated == true)
{
var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
var username = user.FindFirstValue(ClaimTypes.Name);
return new AuditProviderUser(userId, new Dictionary<string, object>
{
{ "Username", username }
});
}
return null;
};
});
services.AddDbContext<YourDbContext>((serviceProvider, optionsBuilder) =>
{
// Your DbContext connection configuration here
// ...
optionsBuilder.AddEntityFrameworkAuditProviderInterceptor(serviceProvider);
});
Option | Type | Description | Default |
---|---|---|---|
JsonOptions |
System.Text.Json.JsonSerializerOptions |
Json serializer options to serialize and deserialize audits | An optimal setting |
AuditFlagSupport |
R8.EntityFrameworkCore.AuditProvider.AuditProviderFlagSupport |
Audit flags to include | All flags are included |
MaxStoredAudits * |
int? |
Maximum number of audits to store in Audits column |
null |
DateTimeProvider |
Func<IServiceProvider, DateTime> |
DateTime provider to get current date time | DateTime.UtcNow |
UserProvider |
Func<IServiceProvider, EntityFrameworkAuditUser> |
User provider to get current user id | null |
- If the number of audits exceeds this number, the earliest audits (except
Created
) will be removed from the column. Ifnull
, all audits will be stored.
IAuditActivator
interface: to start auditing entities.IAuditStorage
interface: to store audits in a column.IAuditSoftDelete
interface: to soft-delete entities.IAuditCreateDate
interface: to store creation date in a column.IAuditUpdateDate
interface: to store last update/restore date in a column.IAuditDeleteDate
interface: to store deletion date in a column.[AuditIgnore]
attribute: to ignore a property from audit.
PostgreSQL
: AggregateAuditable.csMicrosoft Sql Server
: AggregateAuditable.cs- or as below (for
PostgreSQL
):
public record YourEntity : IAuditActivator, IAuditStorage, IAuditSoftDelete, IAuditCreateDate, IAuditUpdateDate, IAuditDeleteDate
{
[Key]
public int Id { get; set; }
[Column(TypeName = "jsonb"), AuditIgnore]
public JsonElement? Audits { get; set; }
public bool IsDeleted { get; set; }
[Column("CreatedAt", TypeName = "timestamp")]
public DateTime? CreateDate { get; set; }
[Column("UpdatedAt", TypeName = "timestamp")]
public DateTime? UpdateDate { get; set; }
[Column("DeletedAt", TypeName = "timestamp")]
public DateTime? DeleteDate { get; set; }
// ...
// public string Name { get; set; }
// public string Description { get; set; }
// etc.
}
Highly recommended to test it on a test database first, to avoid any data loss.
- Since
Microsoft Sql Server
does not supportjson
type,Audits
column will be stored asnvarchar(max)
andJsonElement
will be serialized/deserialized to/fromstring
. (See AggregateAuditable.cs) - The key to allow auditing entities is implementation of
IAuditActivator
to your entity.- the
IAuditStorage
,IAuditSoftDelete
,IAuditCreateDate
,IAuditUpdateDate
, andIAuditDeleteDate
interfaces takes effect only ifIAuditActivator
is implemented to entity. If not implemented, the entity will be updated with the properSaveChanges
/SaveChangesAsync
functionality inEntity Framework Core
.
- the
Deleted
andUnDeleted
flag cannot be stored simultaneously withCreated
andChanged
flags.- If
IAuditStorage
is implemented to your entity,Audits
column will be stored in the specified table. - If any of
IAuditCreateDate
,IAuditUpdateDate
orIAuditDeleteDate
is implemented to entity, the corresponding date will be stored among theAudits
column (ofIAuditStorage
interface) update. - Any support flag in
AuditProviderOptions.AuditFlagSupport
must be written as a flag:AuditFlagState.ActionDate | AuditFlagState.Storage
- If any of
AuditFlag
enums are included/excluded fromAuditFlagSupport
, the corresponding flag will take action inAudits
and/or{Action}Date
column according to the its state inAuditFlagSupport
. (For instance, ifAuditFlagSupport.Created = AuditProviderFlagSupport.Excluded
,IAuditCreateDate
andIAuditStorage
, also andCreated
flag will be ignored.)
- If any of
To take advantages of JsonElement Audits
(as a property in IAuditStorage
interface):
var entity = await dbContext.YourEntities.FindAsync(1);
var audits = entity.GetAuditCollection();
Audit[] deserializedAudits = audits.ToArray(); // Get audits as array
Audit creationAudit = audits.First(); // Get created audit
Audit lastAudit = audits.Last(false); // Get last audit. (false) means to exclude Deleted flag audit, if is the last one.
Audit[] changes = audit.Track(nameof(entity.Name)); // Get changes of a property
Stored data in Audits
column will be like this:
[
{
"f": 0, // Created
"dt": "2023-09-25T12:00:00.0000000+03:30", // Date and time of the action
},
{
"f": 1, // Changed
"dt": "2023-09-25T12:00:00.0000000+03:30", // Date and time of the action
"c": [ // Changes
{
"n": "Name", // Name of the property
"_v": "OldName", // Old value
"v": "NewName" // New value
},
{
"n": "Age", // Name of the property
"_v": 0, // Old value
"v": 33 // New value
}
],
"u": { // User that made the change
"id": "1", // The user id (if provided)
"ad": { // The user additional info (if provided)
"Username": "Foo"
}
}
},
{
"f": 2, // Deleted
"dt": "2023-09-25T12:00:00.0000000+03:30", // Date and time of the action
},
{
"f": 3, // Restored/Undeleted
"dt": "2023-09-25T12:00:00.0000000+03:30", // Date and time of the action
}
]
🎆 Happy coding!