Hi,
I have two tables named material definition and material group. I added the material group as a foreign key in the material table. If I have used a material group in a material definition, I should not be
able to delete it. However, because I am using the FullAuditedEntity for BaseClass , the records are being deleted because the isvisible property is set to true without deleting the record.
If a record is being used, display a warning message to the user. How can I do this? Is there an example related to this?
3 Answer(s)
-
0
Hi @MarturAbpUser
In ASP.NET Zero, entities inheriting from
FullAuditedEntityare soft deleted by design. This means that when a delete operation is executed, the record is not physically removed from the database; instead, theIsDeletedflag is set totrue.Because the record still exists in the database, foreign key constraints are not violated, and therefore the framework does not automatically prevent the delete operation even if the entity is referenced by another table.
This is an intentional design choice. ABP assumes that decisions such as this entity should not be delete table when it is in use are business rule, not technical constraints, and should be enforced explicitly by the application logic rather than implicitly by the framework.
As a result, Power Tools generated delete actions will successfully perform a soft delete unless an explicit guard is implemented by the developer.
Example
To prevent deleting an entity that is currently in use, the recommended approach is to add a guard in the Application, following the same pattern used internally by ASP.NET Zero (e.g., Edition deletion).
Example:
public async Task DeleteMaterialGroup(EntityDto input) { // Check if any active (not deleted) MaterialDefinition is using this group var isInUse = await _materialDefinitionRepository.GetAll() .AnyAsync(x => x.MaterialGroupId == input.Id); if (isInUse) { throw new UserFriendlyException(L("MaterialGroupIsBeingUsedCannotDelete")); } await _materialGroupRepository.DeleteAsync(input.Id); }Are you looking for a solution specific to these tables only, or do you want to implement a global in use records cannot be deleted system for the entire project?
Disable Filter Document
Markdown is supportedCopy & paste or drag & drop images (max 30 MB per image) -
0
Hi,
This is basic solution. I have more 15 table. This is not sustainable.
I want to implement a global in use records cannot be deleted system for the entire project.
Markdown is supportedCopy & paste or drag & drop images (max 30 MB per image) -
0
Hi @MarturAbpUser
You are absolutely right that repeating this check in every
Deletemethod is not sustainable once the number of entities grows.However, it’s important to clarify an architectural point in ASP.NET Zero / ABP:
There is no built in global in use record cannot be deleted mechanism by design, even for soft deleted entities. This is intentional.
ASP.NET Zero treats an entity cannot be deleted if it is in use as a business rule, not an infrastructure concern:
- Different entities have different in use definitions (active records only, status based records, tenant scoped records, etc.)
- Some relations may allow deletion, others may not
- Soft delete intentionally bypasses database level FK restrictions
Because of this, the framework cannot reliably infer usage rules globally.
Instead of duplicating logic per entity, the recommended approach is to centralize the rule, not globalize it blindly.
Domain Service / Generic Deletion Guard Pattern (Recommended)
Create a reusable generic deletion guard infrastructure and let each entity define how it is considered in use.
Generic guard contract
public interface IDeletionGuard<TEntity> { Task CheckBeforeDeleteAsync(Guid id); }Generic helper for usage checks
public interface IDeletionUsageChecker { Task<bool> IsInUseAsync(Guid id); }Entity specific usage checker
public class MaterialGroupUsageChecker : IDeletionUsageChecker { private readonly IRepository<MaterialDefinition, Guid> _materialRepo; public async Task<bool> IsInUseAsync(Guid id) { return await _materialRepo.GetAll() .AnyAsync(x => x.MaterialGroupId == id); } }Generic deletion guard implementation
public class DeletionGuard<TEntity> : IDeletionGuard<TEntity> { private readonly IEnumerable<IDeletionUsageChecker> _usageCheckers; public DeletionGuard(IEnumerable<IDeletionUsageChecker> usageCheckers) { _usageCheckers = usageCheckers; } public async Task CheckBeforeDeleteAsync(Guid id) { foreach (var checker in _usageCheckers) { if (await checker.IsInUseAsync(id)) { throw new UserFriendlyException( "This record is being used and cannot be deleted." ); } } } }Usage in AppService
await _materialGroupDeletionGuard.CheckBeforeDeleteAsync(id); await _materialGroupRepository.DeleteAsync(id);This still requires intent but removes boilerplate.
We hope this approach helps you implement a scalable and maintainable solution. If you have any further questions or run into issues while applying this pattern, please feel free to ask.
Thank you.
Markdown is supportedCopy & paste or drag & drop images (max 30 MB per image)
