asp.netentity-frameworkentity-framework-coreasp.net-boilerplate

Is it possible to extend IEntityHistoryStore to store per-table rather than track all in the same table?


In asp.net boilerplate, we have the need to track changes to entities. I see that ABP has entity tracking (new) but it's storing all changes across entities in one table, which won't work for us. Is it possible to create a custom IEntityHistoryStore to store those changes per entity (in their own table)?

For example, let's say there's an entity Task.

[Table("Task", Schema = "Tasks")]
public class Task : Entity<int>
{
    [Column(TypeName = "varchar(255)")]
    public string Description { get; set; }
    // some more poco properties
}

Then what I would like to define is another table, zzLog.Tasks_Task and I would like that table to look like this:

╔════════╦════════════════════════╦══════╦══════════╦════════════════════╗
║ TaskId ║      Description       ║ lgId ║ lgAction ║       lgTime       ║
╠════════╬════════════════════════╬══════╬══════════╬════════════════════╣
║      1 ║ Some description       ║    1 ║        1 ║ 2019-05-30 9:05 AM ║
║      1 ║ Some other description ║    2 ║        2 ║ 2019-05-30 9:06 AM ║
║      1 ║ Changed again          ║    3 ║        2 ║ 2019-05-30 9:07 AM ║
║      1 ║ Changed again          ║    4 ║        3 ║ 2019-05-30 9:08 AM ║
╚════════╩════════════════════════╩══════╩══════════╩════════════════════╝

lgAction is an enum, 1 = create, 2 = update, 3 = delete

Is this possible? Instinct says no since IEntityHistoryStore likely can't work this way.


Solution

  • Yes, from https://aspnetboilerplate.com/Pages/Documents/Entity-History#about-ientityhistorystore:

    The Entity History tracking system uses IEntityHistoryStore to save change information. ... you can implement it in your own way ...

    Implement MyEntityHistoryStore:

    public class MyEntityHistoryStore : IEntityHistoryStore
    {
        private readonly IRepository<zzLog.Tasks_Task> _taskLogRepository;
    
        public EntityHistoryStore(IRepository<zzLog.Tasks_Task> taskLogRepository)
        {
            _taskLogRepository = taskLogRepository;
        }
    
        public virtual async Task SaveAsync(EntityChangeSet changeSet)
        {
            foreach (var entityChange in changeSet.EntityChanges)
            {
                var taskLog = new zzLog.Tasks_Task()
                {
                    TaskId = entityChange.EntityId,
                    Description = entityChange.PropertyChanges
                        .Where(pc => pc.PropertyName == "Description")
                        .FirstOrDefault()?
                        .NewValue,
                    // lgId = 0,  // Auto-increment
                    lgAction =
                        entityChange.ChangeType == EntityChangeType.Created ? 1 :
                        entityChange.ChangeType == EntityChangeType.Updated ? 2 :
                        entityChange.ChangeType == EntityChangeType.Deleted ? 3 : 0,
                    lgTime = entityChange.ChangeTime
                };
    
                await _taskLogRepository.InsertAsync(taskLog);
            }
        }
    }
    

    Replace the service for IEntityHistoryStore in your module's PreInitialize method:

    // using Abp.Configuration.Startup;
    
    Configuration.ReplaceService<IEntityHistoryStore, MyEntityHistoryStore>(DependencyLifeStyle.Transient);