Open Closed

Soft Delete and DeleteBehavior.SetNull #10463


User avatar
0
sserestyen created

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)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • 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 IdentityUser row; it only sets IsDeleted = true (and audit fields). Therefore, no database delete happens, so DeleteBehavior.SetNull is never triggered.
    • So: ABP does not (and cannot automatically) honor DeleteBehavior.SetNull for soft-deletes, because the FK nulling is tied to actual DELETE operations, not to an UPDATE that marks IsDeleted.

    What to do instead (pick one):

    1. Keep the FK as-is and treat “soft-deleted user” as logically missing

      • When you load Employee.Manager (your IdentityUser navigation), ABP’s ISoftDelete filter will hide soft-deleted users by default, so the navigation will typically come back as null in queries that apply filters (even though ManagerId still has a value).
      • This avoids broken UI behavior but still keeps a “dangling” FK value in the table.
    2. Manually set ManagerId = null when 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.
    3. If you really want SetNull to happen automatically, you must hard-delete the user

      • Only a hard delete can trigger ON DELETE SET NULL in the database.
      • In ABP repositories, this is typically done via HardDeleteAsync (where applicable). Be careful: hard-deleting identity users may have broader implications.

    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.

  • User Avatar
    0
    sserestyen created

    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?

  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • The Identity Management → Users page ultimately calls the Identity module’s application service that implements user CRUD (i.e., the server-side Delete endpoint). 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 base DeleteAsync call (before/after the soft-delete happens).
    • An alternative (more decoupled) approach is to react to the delete via domain/entity events (when the IdentityUser is 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

    1. Create a class in your application layer that derives from the Identity module’s user app service (the one used by the UI).
    2. Mark it with ABP’s DI replacement attributes so your implementation is used instead of the module’s default.
    3. Override DeleteAsync(Guid id) and:
      • Set Employee.ManagerId = null for all employees referencing that user (before or after calling base.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.).

    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.ManagerId there. 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.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    ABP soft delete actual do a update operator. So your OnDelete won't be called.

    You can add an event handler EntityUpdatedEventData<IdentityUser> and check if IsDeleted is true or false.

    Thanks.

  • User Avatar
    0
    sserestyen created

    [maliming] said: hi

    ABP soft delete actual do a update operator. So your OnDelete won't be called.

    You can add an event handler EntityUpdatedEventData<IdentityUser> and check if IsDeleted is true or false.

    Thanks.

    Hi,

    Thanks for your response on this. We'll look into these options.

    Thanks.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    ok. : )

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.2.0-preview. Updated on February 17, 2026, 09:10
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.