In this document
Introduction
ASP.NET Boilerplate provides an infrastructure to automatically log all entity and property changes.
The saved fields for an entity change are: The related tenant id, entity change set id, entity id, entity type name, change time and the change type.
The saved fields for an entity property change are: The related tenant id, entity change id, property name, property type name, new value and the original value.
The entity changes are grouped in a change set for each SaveChanges call.
The saved fields for an entity change set are: The related tenant id, changer user id, creation time, reason, the client's IP address, the client's computer name and the browser info (if entities are changed in a web request).
The Entity History tracking system uses
IAbpSession to
get the current UserId and TenantId.
No entities are automatically tracked by default. You should configure entities either by using the startup configuration or via attributes.
About IEntityHistoryStore
The Entity History tracking system uses IEntityHistoryStore to
save change information. While you can implement it in your own way,
it's fully implemented in the Module Zero project.
Configuration
To configure Entity History, you can use the
Configuration.EntityHistory property
in your module's PreInitialize method.
Entity History is enabled by default.
You can disable it as shown below:
public class MyModule : AbpModule
{
public override void PreInitialize()
{
Configuration.EntityHistory.IsEnabled = false;
}
//...
}
Here are the Entity History configuration properties:
IsEnabled: Used to enable/disable the tracking system completely. Default:true.IsEnabledForAnonymousUsers: If this is set to true, the change logs are saved for users that are not logged in to the application. Default:false.Selectors: Used to select entities to save change logs.IgnoredTypes: Used to skip entities when saving change logs.
Selectors is a list of predicates to select entities to save
change logs. A selector has a unique name and a predicate.
For example, a selector can be used to select full audited entities.
It's defined as shown below:
Configuration.EntityHistory.Selectors.Add(
new NamedTypeSelector(
"Abp.FullAuditedEntities",
type => typeof (IFullAudited).IsAssignableFrom(type)
)
);
You can add your selectors in your module's PreInitialize method.
IgnoredTypes is a list of entity types to be ignored when saving
change logs.
For example, we configured all entities that implements ISetting to be tracked by the Entity History system.
In order to skip entity history for SecretSetting which implements ISetting.
It can be defined as shown below:
Configuration.EntityHistory.IgnoredTypes.AddIfNotContains(typeof(SecretSetting));
You can add your ignored types in your module's PreInitialize method.
Notes
IgnoredTypestakes priority over theSelectorswhen saving change log for the entities.EntityChangeSet,EntityChange,EntityPropertyChangeare added toIgnoredTypesby default in the Entity History system.AuditLogis added toIgnoredTypesby default in Module Zero project.
Enable/Disable by attributes
While you can select tracked entities by configuration, you can use the
Audited and DisableAuditing attributes for a single
entity or an individual property. Example:
[Audited]
public class MyEntity : Entity
{
public string MyProperty1 { get; set; }
[DisableAuditing]
public int MyProperty2 { get; set; }
public long MyProperty3 { get; set; }
}
All properties of MyEntity are tracked except MyProperty2 since it's
explicitly disabled. The Audited attribute can be used to
save change logs for a desired property.
When using Audited attribute on an entity class, EntityChange will be created for an entity in Added/Modified/Deleted state, regardless of whether any PropertyChange is being created.
For example, if only MyProperty2 is modified, EntityChange will be created even though PropertyChange will not be created.
When using Audited attribute on a property, PropertyChange will be created for the property if there is any difference between the new and old values of the property.
DisableAuditing can be used for an entity or a single property of an
entity. Thus, you can hide sensitive data in change logs, such as
passwords for example.
Reason Property
The entity change set has a Reason property that can be used to understand why a
set of changes has occurred, i.e. the use case that resulted in these changes.
For example, Person A transfers money from Account A to Account B. Both account balances change and "Money transfer" is recorded as the Reason for this change set. Since a balance change can be due to other reasons, the Reason property explains why these changes were made.
The Abp.AspNetCore package implements HttpRequestEntityChangeSetReasonProvider,
which returns the HttpContext.Request's URL as the Reason.
UseCase Attribute
The preferred approach is using the UseCase attribute. Example:
[UseCase(Description = "Assign an issue to a user")]
public virtual async Task AssignIssueAsync(AssignIssueInput input)
{
// ...
await _unitOfWorkManager.Current.SaveChangesAsync();
}
UseCase Attribute Restrictions
You can use the UseCase attribute for:
- All
publicorpublic virtualmethods for classes that are used via its interface, e.g. an application service used via its interface. - All
public virtualmethods for self-injected classes, e.g. MVC Controllers. - All
protected virtualmethods.
IEntityChangeSetReasonProvider
In some cases, you may need to change/override the Reason value for a limited scope.
You can use the IEntityChangeSetReasonProvider.Use(...) method as shown below:
public class MyService
{
private readonly IEntityChangeSetReasonProvider _reasonProvider;
public MyService(IEntityChangeSetReasonProvider reasonProvider)
{
_reasonProvider = reasonProvider;
}
public virtual async Task AssignIssueAsync(AssignIssueInput input)
{
var reason = "Assign an issue to user: " + input.UserId.ToString();
using (_reasonProvider.Use(reason))
{
...
await _unitOfWorkManager.Current.SaveChangesAsync();
}
}
}
The Use method returns an IDisposable and it must be disposed. Once the return
value is disposed, the Reason is automatically restored to the previous value.
IEntitySnapshotManager
You may need to get a snapshot of your entity on a given date. You can use the IEntitySnapshotManager.GetSnapshotAsync(...) method as shown below:
public class MyService
{
private readonly IEntitySnapshotManager _entitySnapshotManager;
public MyService(IEntitySnapshotManager entitySnapshotManager)
{
_entitySnapshotManager = entitySnapshotManager;
}
public Task<EntityHistorySnapshot> GetMyEntitySnapshot(long id, DateTime time)
{
return _entitySnapshotManager.GetSnapshotAsync<MyEntity, long>(id, time);
}
public async Task<string> GetMyEntityMyPropertySnapshotValue(long id, DateTime time)
{
var snapshot = await GetMyEntitySnapshot(id, time);
if (snapshot.IsPropertyChanged(nameof(MyEntity.MyProperty)))
{
// var stacktree = snapshot.PropertyChangesStackTree[nameof(MyEntity.MyProperty)];
return snapshot[nameof(MyEntity.MyProperty)];
}
return null;
}
}
IEntitySnapshotManager.GetSnapshotAsync(...) returns an EntityHistorySnapshot that contains the snapshot value of all changed properties on the given date and a stack tree of the changes.
ORM Integrations
Entity History in Module Zero project is implemented for Entity Framework 6.x and Entity Framework Core with the following logics:
Entity Changes
- An entity must not be one of the
IgnoredTypes. - An entity must be
publicin order to be saved in the change logs.private,protected,internalentities are ignored. DisableAuditingtakes priority over theAuditedattribute when saving entity changes.- An entity must satisfy one of the predicates from
Selectors.
Property Changes
- Primary key properties are excluded from an entity's property changes.
DisableAuditingtakes priority over theAuditedattribute when saving property changes.- Property changes would only be saved if there are changes between original/new values.
Auditedattribute will make property changes being saved even if there is no change between original/new values.
Entity Framework Core
- Entity changes only work for entities and owned entities.
- Property changes only work for scalar properties (e.g. string, int, bool...)
- including shadow properties.
Owned Entities
Entity History tracks changes to owned entities as entity changes. The primary key of the owner entity is saved as the entity id.
Entity Framework 6.x
- Entity changes only work for entities.
- Property changes only work for complex type properties and scalar properties (e.g. string, int, bool...)
- including relationship changes.
Complex Type Properties
Entity History tracks changes to complex type properties as property changes (unlike owned entities for EF Core). The original/new values of a complex type property will be a serialized JSON string of the complex type property.
Relationship Changes
Entity History tracks changes to relationships (navigation properties without foreign key) as property changes (like shadow properties for EF Core).
However, these use the navigation property name (e.g. Blog) as declared in the entity, unlike the shadow property name (e.g. BlogId) generated by EF Core.