A plugin for Microsoft.EntityFrameworkCore to support automatically recording data changes history.
This fork works for added entities and support .Net5.0
, .Net6.0
and .Net7.0
, with their respective EFCore versions.
AutoHistoryFork
will recording all the data changing history in one Table
named AutoHistories
, this table will recording data
UPDATE
, DELETE
history and, if you want, ADD
history.
This Fork brings two additional fields to the original version: UserName
and ApplicationName
.
- Install AutoHistoryFork Package
Run the following command in the Package Manager Console
to install Microsoft.EntityFrameworkCore.AutoHistoryFork
PM> Install-Package Microsoft.EntityFrameworkCore.AutoHistoryFork
- Enable AutoHistoryFork
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// enable auto history functionality.
modelBuilder.EnableAutoHistory();
}
}
- Ensure AutoHistory in DbContext. This must be called before
bloggingContext.SaveChanges()
orbloggingContext.SaveChangesAsync()
.
bloggingContext.EnsureAutoHistory()
If you want to record data changes for all entities (except for Added - entities), just override SaveChanges
and SaveChangesAsync
methods and call EnsureAutoHistory()
inside overridden version:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public override int SaveChanges()
{
this.EnsureAutoHistory();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
this.EnsureAutoHistory();
return base.SaveChangesAsync(cancellationToken);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// enable auto history functionality.
modelBuilder.EnableAutoHistory();
}
}
- If you also want to record Added - Entities, which is not possible per default, override
SaveChanges
andSaveChangesAsync
methods this way:
public class BloggingContext : DbContext
{
public override int SaveChanges()
{
var addedEntities = ChangeTracker
.Entries()
.Where(e => e.State == EntityState.Added)
.ToArray(); // remember added entries,
var hasAdded = addedEntities.Any();
IDbContextTransaction transaction = null;
// if has added entities, begin transaction
if (hasAdded)
transaction = Database.BeginTransaction();
// before EF Core is assigning valid Ids (it does on save changes,
// when ids equal zero) and setting their state to
// Unchanged (it does on every save changes)
this.EnsureAutoHistory();
int changes = base.SaveChanges();
if (hasAdded)
{
// after "SaveChanges" added enties now have gotten valid ids (if it was necessary)
// and the history for them can be ensured and be saved with another "SaveChanges"
this.EnsureAddedHistory(addedEntities);
changes += base.SaveChanges();
transaction.Commit();
}
return changes;
}
}
You can use the user name of the current user passing the user name to the EnsureAutoHistory
method.
public override int SaveChanges()
{
this.EnsureAutoHistory(currentUserName); // currentUserName can be a field of the DbContext
return base.SaveChanges();
}
The EnsureAddedHistory
method also accepts the name of the current user.
this.EnsureAddedHistory(addedEntities, currentUserName);
You can use other DbContext for saving AutoHistory by passing the DbContext to the EnsureAutoHistory
method.
public override int SaveChanges()
{
var addedEntities = ChangeTracker
.Entries()
.Where(e => e.State == EntityState.Added)
.ToArray(); // remember added entries,
this.EnsureAutoHistory(historyDbContext, currentUserName); // historyDbContext and currentUserName can be fields of the DbContext
var changes = base.SaveChanges();
historyDbContext.EnsureAddedHistory(addedEntities, currentUserName);
historyDbContext.TrySaveChanges(); // extension method that tries to save changes and, in the event of an error, logs the error and does not throw an exception
// it is not implemented by default, you can implement it yourself
return changes;
}
The application name is defined in the AutoHistoryOptions
class.
The default value is the name of the entry assembly.
You can configure the application name by passing it as a parameter in the EnableAutoHistory
extension method for
ModelBuilder
or by configuring AutoHistoryOptions
.
Here's an example:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// enable auto history functionality and set the application name
modelBuilder.EnableAutoHistory("MyApplicationName");
}
You can use a custom auto history entity by extending the Microsoft.EntityFrameworkCore.AutoHistory class.
class CustomAutoHistory : AutoHistory
{
public String CustomField { get; set; }
}
Then register it in the db context like follows:
modelBuilder.EnableAutoHistory<CustomAutoHistory>(o => { });
Then provide a custom history entity creating factory when calling EnsureAutoHistory. The example shows using the
factory directly, but you should use a service here that fills out your history extended properties(The properties inherited from AutoHistory
will be set by the framework automatically).
db.EnsureAutoHistory(() => new CustomAutoHistory()
{
CustomField = "CustomValue"
});
You can now excluded properties from being saved into the AutoHistory tables by adding a custom attribute[ExcludeFromHistoryAttribute] attribute to your model properties.
public class Blog
{
[ExcludeFromHistory]
public string PrivateURL { get; set; }
}
The ChangedHistory
class is a dictionary that contains the changed properties of an entity.
The key is the property name and the value is an array os strings that contains the old and new values.
For the added entities, the ChangedHistory
class contains only the new values, and the array has only one item.
The same happens for the deleted entities, but the array has only the old values.
For modified entities, the ChangedHistory
class contains the old and new values, and the array has two items.
The first item is the old value and the second item is the new value.
The ChangedHistory
class has a Serialize
method that returns a JSON string of the dictionary.
Also, has a static Deserialize
method that returns a ChangedHistory
object from a JSON string.
The function for saving histories of added entities didn't work in the original version, but this one does.
In the history entity, AutoHistory
, the properties UserName
and ApplicationName
have been added.
The default length for the Changed
column has been changed from 2048 to 8000 characters.
In the AutoHistoryOptions
class, the ApplicationName
, DateTimeFactory
, UserNameMaxLength
and ApplicationNameMaxLength
properties have been added.
The JsonSerializerOptions
property has been removed for version .Net6.0 onwards, although it still exists for version .Net5.0.
The ChangedHistory
class was created, which is a dictionary containing the changed properties of an entity.
This changes the JSON format for the Changed
field of the history entity.
The EnsureAutoHistory
method optionally accepts the name of the current user and a second DbContext
for recording histories.