Search code examples
c#entity-framework.net-coresoft-delete

C# Entity Framework Core - Throw an error when child item gets added to a soft-deleted parent OnSave


I have bunch of tables that contain DeletedAt column that's used for a soft-delete feature. I've implemented most of the logic and everything works correctly except for one issue where it allows to add a child record to a soft-deleted parent record. I'd like to throw an exception when DbContext.OnSave() runs and finds that the parent record (foreignKey) is actually soft-deleted.

Is there any way I can inject a filter or something that can check for that parent DeletedAt field before adding the child element?

I do have few ideas but kinda painful and require to loop through all foreignKeys in the child model and checks if that parent object has DeletedAt set or not.

Any ideas would be appreciated. Thanks.


Solution

  • So I did find a solution and it's not that bad, here's the implementation for anyone who's interested (before your code hits the DbContext.SaveChanges()) in the DbContext file:

    var entry = Entry(childModel);
    foreach(var prop in entry.CurrentValues.Properties)
    {
        // Handle FKs only
        if(!prop.IsForeignKey()) continue;
    
        var fkValue = entry.CurrentValues[prop.Name];
        
        // Handle nullable properties and skip the checks if the value is null
        if(prop.IsColumnNullable() && fkValue == null) continue;
    
        // If value is null or 0 then break
        if(fkValue == null || (int)fkValue == 0)
        {
            throw new Exception("Must contain a value.");
        }
        
        // Get the FK
        var fk = prop.GetContainingForeignKeys().FirstOrDefault();
        if(fk == null)
        {
            throw new Exception("FK constraint is not available.");
        }
    
        // If parent is not available or deleted
        var parent = Find(fk.PrincipalEntityType.ClrType, new object[]{fkValue});
        if(parent == null)
        {
            throw new Exception($"The entered FK value ({fkValue}) is not valid.");
        }
    }
    

    Find method actually has a global filter that checks if the DeletedAt field is set or not. If the record doesn't exist, this will fail. This should be dynamic enough to handle all FKs in a table.