When a role is removed from a user, I need to track who did it (ie. which AbpUser) and when they did it.
The obvious solution is to redefine the UserRole entity so that it inherits from FullAuditedEntity instead of CreationAuditedEntity, but the UserRole entity is defined in a nuget package so I cannot simply change the definition.
Is there a way to achieve this behavior that I am not seeing?
Here is what I have tried so far.
Approach 1: I tried handling this at the database level by setting up a delete trigger on the AbpUserRole table which would insert a record into a AbpUserRoleDeleted table, but I can't think of a way to find out which AbpUser made the deletion with this approach. I can only track when the action happened.
Approach 2: I tried listening for the EntityDeleted domain event on UserRole entities, but it does not seem to get triggered. Interestingly, the EntityUpdated event is triggered when I remove a role from a user, but even assuming that this event would only ever be triggered when a UserRole is deleted, the event data still does not include who made the deletion. If it did, I could manually save the audit information in a separate table just like a database delete trigger would, but this time I would have the AbpUser that was responsible for the deletion.
Approach 3: I tried extending the UserRole entity by following the steps here. I was able to implement the IDeletionAudited interface and generate a migration that creates the associated columns on the AbpUserRoles table, but removing a role from a user performs a hard delete instead of a soft delete so I can't tell if the columns even get populated. I am assuming they do not.
Approach 4: I tried enabling Entity History for the UserRole entity, but it seems to only track when a UserRole entity is created.
This seems to work fine.
//src\aspnet-core\src\Company.App.EntityFrameworkCore\EntityFrameworkCore\AppDbContext.cs
namespace Company.App.EntityFrameworkCore
{
public class AppDbContext : AbpZeroDbContext<Tenant, Role, User, AppDbContext>, IAbpPersistedGrantDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
ChangeTracker.StateChanged += OnEntityStateChanged;
}
private void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.Entry.Entity is UserRole && e.NewState == EntityState.Deleted)
{
//update instead of delete
e.Entry.State = EntityState.Modified;
e.Entry.CurrentValues["IsDeleted"] = true;
e.Entry.CurrentValues["DeletionTime"] = DateTime.Now;
e.Entry.CurrentValues["DeleterUserId"] = AbpSession.UserId;
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//use query filter on the `IsDeleted` shadow property
modelBuilder.Entity<UserRole>().HasQueryFilter(p => !EF.Property<bool>(p, "IsDeleted"));
modelBuilder.Entity<UserRole>().Property<bool>("IsDeleted");
modelBuilder.Entity<UserRole>().Property<DateTime?>("DeletionTime").IsRequired(false);
modelBuilder.Entity<UserRole>().Property<long?>("DeleterUserId").IsRequired(false);
}
}
}