Hi,
We encountered a scenario where an entity (with its own entity type configuration) contains a nullable reference to IdentityUser.
In the entity configuration, we specified:
.OnDelete(DeleteBehavior.SetNull)
However, when the related IdentityUser is soft deleted, the reference is not updated to null. As a result, the entity continues to reference a soft-deleted IdentityUser, effectively creating a broken reference.
We are currently using ABP.IO version 9.3.2 (commercial license).
Our expectation was that ABP’s soft delete mechanism would also honor OnDelete(DeleteBehavior.SetNull) and automatically set the foreign key to null. However, this does not seem to be happening.
Could you please confirm whether ABP.IO handles soft delete scenarios together with OnDelete(DeleteBehavior.SetNull)? If it does, could you clarify how to achieve the expected behavior?
See below code as an example:
public class Employee : FullAuditedEntity<int>
{
public Guid? ManagerId { get; set; }
[ForeignKey(nameof(ManagerId))]
public IdentityUser? IdentityUser { get; set; }
public Employee()
{
}
}
public sealed class EmployeeConfig : IEntityTypeConfiguration<Employee>
{
public void Configure(EntityTypeBuilder<Employee> builder)
{
builder.ToTable(FfWConsts.DbTablePrefix + "Employees", FfWConsts.DbSchema);
builder.ConfigureByConvention();
builder.HasOne(p => p.IdentityUser).WithMany().HasForeignKey(x => x.ManagerId).OnDelete(DeleteBehavior.SetNull);
}
}
6 Answer(s)
-
0
OnDelete(DeleteBehavior.SetNull)is an EF Core / database-level delete behavior. It is applied when the principal row is physically deleted (hard delete) and the database (or EF Core cascade pipeline) executes the configured referential action.- In ABP, soft delete does not physically delete the
IdentityUserrow; it only setsIsDeleted = true(and audit fields). Therefore, no database delete happens, soDeleteBehavior.SetNullis never triggered. - So: ABP does not (and cannot automatically) honor
DeleteBehavior.SetNullfor soft-deletes, because the FK nulling is tied to actual DELETE operations, not to an UPDATE that marksIsDeleted.
What to do instead (pick one):
Keep the FK as-is and treat “soft-deleted user” as logically missing
- When you load
Employee.Manager(yourIdentityUsernavigation), ABP’sISoftDeletefilter will hide soft-deleted users by default, so the navigation will typically come back asnullin queries that apply filters (even thoughManagerIdstill has a value). - This avoids broken UI behavior but still keeps a “dangling” FK value in the table.
- When you load
Manually set
ManagerId = nullwhen the manager user is soft-deleted (recommended if you truly need FK-null semantics)- Implement this as an application/domain rule. Typical approaches in ABP:
- Handle an event when a user is deleted (soft-deleted) and then update dependent entities (e.g.,
Employee.ManagerId = null). - Or override/customize the user deletion flow in your app service/domain service and explicitly update employees before/after deleting the user.
- Handle an event when a user is deleted (soft-deleted) and then update dependent entities (e.g.,
- Implement this as an application/domain rule. Typical approaches in ABP:
If you really want
SetNullto happen automatically, you must hard-delete the user- Only a hard delete can trigger
ON DELETE SET NULLin the database. - In ABP repositories, this is typically done via
HardDeleteAsync(where applicable). Be careful: hard-deleting identity users may have broader implications.
- Only a hard delete can trigger
Notes about your mapping
- Your mapping is fine for hard delete scenarios:
builder.HasOne(p => p.IdentityUser) .WithMany() .HasForeignKey(x => x.ManagerId) .OnDelete(DeleteBehavior.SetNull);It just won’t run on soft delete, because soft delete is an UPDATE, not a DELETE.
Links (verified)
- Soft/Hard delete behavior in repositories: https://abp.io/docs/latest/framework/architecture/domain-driven-design/repositories
- ABP data filtering (soft delete filter): https://abp.io/docs/latest/framework/infrastructure/data-filtering
- Related ABP support discussion showing that soft-deleted relations don’t cascade automatically: https://abp.io/support/questions/1136
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
-
0
Thanks for the clarification.
In our case, the Manager is deleted through the built-in Identity Management → Users page (i.e., the default user deletion flow provided by ABP).
Given that, how can we customize this flow so we can implement the following approach?
Override/customize the user deletion process in our application/domain service and explicitly update dependent entities (e.g., set Employee.ManagerId = null) before or after the user is soft-deleted.
What is the recommended way in ABP to hook into or override the built-in user deletion logic used by the Identity Management module so we can safely apply our custom logic when a user is deleted from the UI?
-
0
- The Identity Management → Users page ultimately calls the Identity module’s application service that implements user CRUD (i.e., the server-side
Deleteendpoint). To run your own logic safely when a user is deleted from that UI, ABP’s recommended approach is to replace/override the Identity module’s user AppService and add your logic around the baseDeleteAsynccall (before/after the soft-delete happens). - An alternative (more decoupled) approach is to react to the delete via domain/entity events (when the
IdentityUseris updated/soft-deleted) and then null out your foreign keys in an event handler.
Option A (most direct): Replace the Identity User AppService and override Delete
- Create a class in your application layer that derives from the Identity module’s user app service (the one used by the UI).
- Mark it with ABP’s DI replacement attributes so your implementation is used instead of the module’s default.
- Override
DeleteAsync(Guid id)and:- Set
Employee.ManagerId = nullfor all employees referencing that user (before or after callingbase.DeleteAsync(id); usually before is safest if your domain logic expects the manager to exist while you update employees). - Then call
base.DeleteAsync(id)to keep the standard ABP behavior (permissions, checks, etc.).
- Set
Skeleton (structure to follow; the key is ReplaceServices + ExposeServices):
using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IIdentityUserAppService))] public class MyIdentityUserAppService : IdentityUserAppService { private readonly IEmployeeRepository _employeeRepository; public MyIdentityUserAppService( IIdentityUserRepository userRepository, IdentityUserManager userManager, IEmployeeRepository employeeRepository /* plus the other deps required by base ctor in your version */ ) : base(userRepository, userManager /* ... */) { _employeeRepository = employeeRepository; } public override async Task DeleteAsync(Guid id) { // 1) Null out references await _employeeRepository.SetManagerToNullAsync(id); // 2) Proceed with the default ABP deletion (soft delete) await base.DeleteAsync(id); } }Implement the repo method efficiently (example idea):
SetManagerToNullAsync(Guid userId)does a bulk update (preferred) or loads + updates employees.
This ensures every delete from the ABP UI (since it calls
IIdentityUserAppService.DeleteAsync) goes through your custom logic.Option B (more decoupled): Handle soft-delete as an event and update dependents
Instead of overriding the app service, you can subscribe to a user deletion/soft-delete event and update
Employee.ManagerIdthere. This keeps your customization independent from the UI and from which app service triggers the delete.If you go this route, the key is: soft delete is an update on
IdentityUser, so you need an event that runs when the user is soft-deleted and then execute your nulling logic.(No verified, version-specific official sample for “IdentityUser soft-deleted → null FKs” was found in the available context/code search, but this aligns with ABP’s general event-driven extension approach.)
https://abp.io/docs/latest/framework/fundamentals/dependency-injection
https://abp.io/docs/latest/framework/infrastructure/event-bus
https://abp.io/docs/latest/modules/identity
https://abp.io/docs/latest/framework/architecture/modularity/basics
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
- The Identity Management → Users page ultimately calls the Identity module’s application service that implements user CRUD (i.e., the server-side
-
0
hi
ABP soft delete actual do a update operator. So your
OnDeletewon't be called.You can add an event handler
EntityUpdatedEventData<IdentityUser>and check ifIsDeletedis true or false.Thanks.
-
0
[maliming] said: hi
ABP soft delete actual do a update operator. So your
OnDeletewon't be called.You can add an event handler
EntityUpdatedEventData<IdentityUser>and check ifIsDeletedis true or false.Thanks.
Hi,
Thanks for your response on this. We'll look into these options.
Thanks.
-
0
ok. : )