Deleting Event

Summary

The Deleting event fires before an entity is removed from the data context. Use it to prevent deletion when business rules forbid it, or to specify additional entities that should be deleted along with the current entity.

When does it fire?

The Deleting event fires when context.Delete(entity) is called, before the entity is actually removed. If any AbortIf condition is true, the entire delete operation (including additional entities) is cancelled.

Syntax

Preventing deletion

productUom.OnDeleting()
    .AbortIf((entity, args) => /* condition */)
    .WithMessage("Reason deletion is not allowed");

Deleting additional entities

product.OnDeleting()
    .AlsoDelete((entity, args) => /* return list of entities to delete */);

Fluent API

Method Description
.AbortIf(condition) Cancel deletion when the condition is true
.WithMessage(message) Error message shown when deletion is aborted
.AlsoDelete(expression) Return additional entities to delete with this entity

AbortIf overloads

Signature Description
.AbortIf((entity, args) => bool) Condition with access to event args

AlsoDelete overloads

Signature Description
.AlsoDelete(entity => IEnumerable) Return entities to also delete
.AlsoDelete((entity, args) => IEnumerable) Return entities with access to event args

Scenarios

1. Preventing deletion of a main UOM

The main unit of measure on a product should not be deletable directly. It can only be deleted when the product itself is deleted.

[Logic]
public class ProductUomBL(ProductUom.Logic productUom)
{
    [RegisterLogic]
    public void PreventMainUomDeletion()
    {
        productUom.OnDeleting()
            .AbortIf((uom, args) =>
                uom.Operation == UomOperation.Main
                && args.OriginalTriggeringEntity != uom.Product)
            .WithMessage("Main UOM can not be deleted");
    }
}

args.OriginalTriggeringEntity lets you distinguish between a direct delete of the UOM versus a cascading delete triggered by the parent product.

2. Deleting a product-specific pricing schema

When a product is deleted, its pricing schema should also be deleted — but only if it is a schema that is specific to this one product.

[Logic]
public class ProductBL(Product.Logic product)
{
    [RegisterLogic]
    public void DeleteProductSchema()
    {
        product.OnDeleting().AlsoDelete(p =>
        {
            if (p.PricingSchema?.SchemaType == SchemaType.SpecificForOneProduct)
                return [p.PricingSchema];
            return [];
        });
    }
}

3. Preventing deletion of a customer with open orders

A customer cannot be deleted if they have any open sales orders.

[Logic]
public class CustomerBL(Customer.Logic customer)
{
    [RegisterLogic]
    public void PreventDeletionWithOpenOrders()
    {
        customer.OnDeleting()
            .AbortIf((c, args) =>
                args.GetEntities<SalesOrder>()
                    .Any(o => o.SellToCustomer == c
                           && o.Status == SalesOrderStatus.Open))
            .WithMessage("Cannot delete a customer with open sales orders");
    }
}

4. Preventing deletion of a product used on sales orders

A product should not be deletable if it appears on any sales order detail.

[Logic]
public class ProductBL(Product.Logic product)
{
    [RegisterLogic]
    public void PreventDeletionIfOnOrders()
    {
        product.OnDeleting()
            .AbortIf((p, args) =>
                args.GetEntities<SalesOrderDetail>().Any(d => d.Product == p))
            .WithMessage("Cannot delete a product that is used on sales orders");
    }
}

When a sales order is deleted, also delete any draft shipments that have not been sent.

[Logic]
public class SalesOrderBL(SalesOrder.Logic salesOrder)
{
    [RegisterLogic]
    public void DeleteDraftShipments()
    {
        salesOrder.OnDeleting().AlsoDelete(o =>
        {
            return o.Shipments
                .Where(s => s.Status == ShipmentStatus.Draft)
                .ToList();
        });
    }
}

Deleting vs. ReferenceProperty DeleteAction

For simple cascading or restricting deletes that don't require custom logic, use the DeleteAction on the [ReferenceProperty] attribute in the model instead:

DeleteAction Behavior
Cascade Automatically delete child entities (e.g., SalesOrderDetail when SalesOrder is deleted)
Restrict Prevent deletion of the referenced entity (e.g., can't delete a Product used on a SalesOrderDetail)
SetNull Set the reference to null (e.g., clearing a UOM reference when the UOM is deleted)

Use OnDeleting() only when you need custom conditional logic beyond what DeleteAction provides.

Notes

  • If any AbortIf condition is true, the entire delete operation is aborted — including any AlsoDelete entities.
  • AlsoDelete entities go through their own Deleting event pipeline, so they can also abort.
  • Use args.OriginalTriggeringEntity to distinguish direct deletes from cascading deletes.