Chapter 05 · Relationships, Navigations, Cascade Behavior, and Many-to-Many Modeling

Cascade Delete, ClientCascade, Restrict/NoAction, Orphans, and Database Constraint Behavior

Separate EF client cascade/orphan behavior from database referential actions and design delete semantics from ownership, retention, and loaded-graph evidence.

Intermediate105–130 minutescascade/orphan + destructive safety labEF Core 10.0.11 · .NET 10.0.11SQLite provider 10.0.11 baselineSDK checkpoint: 10.0.400Last reviewed: August 2026

Learning outcomes

In ServiceHub, deleting a technician, work order, note, SLA snapshot, or tag can trigger very different outcomes depending on requiredness and DeleteBehavior. A cascade can be correct ownership semantics—or catastrophic accidental data loss. This lesson separates EF-side state transitions from database ON DELETE actions and makes the timing/loaded-graph assumptions observable.

01

Explain Cascade, ClientCascade, ClientSetNull, SetNull, Restrict, NoAction, and ClientNoAction at a mechanism level.

02

Connect required/optional relationship defaults to cascade behavior without treating defaults as business rules.

03

Distinguish database cascades from EF client-side cascades and identify when loaded/tracked dependents are required.

04

Observe conceptual nulls and orphan deletion when required relationships are severed.

05

Control cascade/orphan timing for diagnostics and tests.

06

Repair an unsafe cascade design with explicit ownership and retention rules.

1. Start with lifecycle ownership, not an enum

Ask what deleting a principal means. If a work order owns a temporary SLA snapshot that has no meaning independently, cascading that dependent may be sensible. Historical technician assignment records or audit notes may need to survive a principal lifecycle transition. The correct delete behavior follows retention and ownership rules.

Scenario Likely design question
Delete technician Should historical work orders survive and become unassigned? Usually yes.
Delete work order Should transient child rows disappear? Maybe, depending on retention.
Delete tag Should join rows disappear while work orders remain? Usually yes.
Delete audit/history row Should application ever allow this? Often restricted/privileged.

2. Required relationships default to cascade; optional relationships do not

EF conventions configure required relationships for cascading deletes and optional relationships for non-cascading nulling behavior. Those defaults make relational sense, but they are not domain analysis.

csharp · required note with explicit restrict
b.HasOne(x => x.WorkOrder) .WithMany(x => x.Notes) .HasForeignKey(x => x.WorkOrderId) .IsRequired() .OnDelete(DeleteBehavior.Restrict);

This says a work order cannot be deleted while notes reference it. The application must explicitly archive/delete/migrate notes first. That can be safer for retained records.

3. Database Cascade versus ClientCascade

Behavior Tracked EF action Database FK action
Cascade Marks tracked dependents deleted when appropriate. Creates cascading FK action where provider supports it.
ClientCascade Cascades for tracked dependents. Creates non-cascading FK; database will not delete unloaded dependents.
ClientSetNull Nulls tracked optional FKs. Non-cascading FK.
SetNull Nulling semantics plus database ON DELETE SET NULL where supported. Database nulls FK.
Restrict/NoAction No database cascade; exact client fixup semantics differ by enum. Database rejects invalid delete if dependent rows remain.
ClientNoAction EF does not automatically delete/null tracked dependents. Non-cascading FK.

Provider DDL determines what the database can actually do. Always inspect the migration and live constraint.

4. ClientCascade has a loaded-graph requirement

csharp · ClientCascade mapping
modelBuilder.Entity<WorkOrder>()    .HasMany(x => x.TransientChecks)    .WithOne(x => x.WorkOrder)    .HasForeignKey(x => x.WorkOrderId)    .OnDelete(DeleteBehavior.ClientCascade);

If you load the work order with all transient checks, EF can mark the tracked dependents deleted before deleting the principal. If you load only the principal, the database FK is non-cascading and the delete can fail because unseen dependent rows still exist. This is why ClientCascade is not equivalent to database cascade.

csharp · broken unloaded ClientCascade delete
await using var db = factory.CreateDbContext();var order = await db.WorkOrders    .SingleAsync(x => x.Id == id, ct); // dependents not loadeddb.Remove(order);await db.SaveChangesAsync(ct); // FK violation if dependents exist

Repair by choosing the correct lifecycle mechanism: load and explicitly process dependents if client cascade is intentional, or use a reviewed database cascade when ownership requires store-side deletion, or restrict the delete and implement an archival workflow.

5. Optional assignment: SetNull preserves the work order

csharp · technician assignment delete behavior
b.HasOne(x => x.AssignedTechnician) .WithMany(x => x.WorkOrders) .HasForeignKey(x => x.AssignedTechnicianId) .OnDelete(DeleteBehavior.SetNull);

On technician deletion, the database can set assigned_technician_id to null if the provider/schema supports the action. This preserves the work order. If operational history requires “who used to own this?”, a nullable current-assignment FK alone is insufficient; model assignment history separately.

6. Severing a required relationship creates an orphan problem

If a required dependent is removed from a principal collection, EF cannot set its non-nullable FK to a valid null database value. Internally, the tracker can represent a conceptual null while deciding whether the dependent should be deleted or re-parented.

csharp · observe required orphan state
var order = await db.WorkOrders    .Include(x => x.Notes)    .SingleAsync(x => x.Id == id, ct);var note = order.Notes.First();order.Notes.Remove(note);Console.WriteLine(db.ChangeTracker.DebugView.LongView);

Depending on delete behavior/timing, the note may be marked deleted. Do not infer database values from CLR non-nullability alone; DebugView reveals EF's conceptual state before SQL is emitted.

7. Control cascade and orphan timing for diagnostics

csharp · delay cascade/orphan processing
db.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;db.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;// Change relationships here.Console.WriteLine(db.ChangeTracker.DebugView.LongView);await db.SaveChangesAsync(ct);

Changing timing can make tests/diagnostics easier and can support re-parenting workflows, but it is not a substitute for correct relationship ownership. CascadeTiming.Never can cause exceptions if required orphans remain unresolved at save time.

8. Deliberately unsafe cascade: delete historical notes with a work order

csharp · unsafe retention mapping
// Notes are legally/audit-retained, but this mapping treats them as disposable.b.HasOne(x => x.WorkOrder) .WithMany(x => x.Notes) .HasForeignKey(x => x.WorkOrderId) .OnDelete(DeleteBehavior.Cascade);

Deleting a work order could erase retained notes in the database even if the application never loaded them. That is the power—and danger—of database cascade.

Repair

If notes must be retained, block hard deletion with Restrict/NoAction, use soft-delete/archive workflow for the principal, or move retained evidence into an independently retained model. Test deletion against realistic data and permissions before deployment.

9. Cascade cycles and provider differences

Complex models can create cycles or multiple cascade paths. SQL Server, for example, can reject some schemas with multiple cascade paths even when another engine accepts them. The repair may involve ClientCascade, restricting one path, or redesigning ownership. Do not “fix the migration” by randomly changing enums until it applies; document which layer owns each delete.

SQLite supports foreign-key actions, but foreign-key enforcement is a database connection/runtime feature and the actual schema is what matters. Use PRAGMA foreign_key_list and destructive tests only against a disposable database.

10. Hands-on lab: prove delete semantics from three angles

  1. Keep technician assignment as optional SetNull.
  2. Configure work-order notes as Restrict for the retention exercise.
  3. Configure a disposable WorkOrderTransientCheck relationship once with ClientCascade.
  4. Generate/review the migration and classify each database FK action.
  5. Apply to disposable SQLite.
  6. Delete a technician and verify the work order survives with null assignment.
  7. Attempt to delete a work order that still has restricted notes; capture the FK failure and verify no partial deletion occurred.
  8. Load a work order with transient checks and prove ClientCascade deletes tracked dependents.
  9. Reset, then delete the same principal without loading dependents and observe the non-cascading database FK failure.
  10. Remove a required dependent from a loaded collection and inspect DebugView before/after cascade/orphan processing.

Verification checklist

  • Every relationship has a documented retention/ownership reason for its delete behavior.
  • Database and EF-side cascade behavior are not conflated.
  • ClientCascade tests cover both loaded and unloaded dependents.
  • Destructive tests use only disposable seeded databases.
  • Generated migration DDL is reviewed before application.

Check your understanding

  1. What is the default delete behavior direction for a required relationship?
  2. Why can ClientCascade fail when dependents are not loaded?
  3. What does SetNull require of the FK column?
  4. What is a conceptual null?
  5. Why can a database cascade be more dangerous than a client cascade?
  6. What should you do when SQL Server reports multiple cascade paths?
Review the answers

Required relationships default to cascade by convention unless configured otherwise.

The database constraint is non-cascading, so EF cannot delete dependents it is not tracking and the database can reject the principal delete.

The dependent FK must be nullable and the provider/database must support the configured action.

Tracker state representing a severed required relationship before EF resolves it, even though the CLR FK may be non-nullable.

The database can delete unloaded rows directly, so a single principal delete can remove more data than the application graph shows.

Identify the ownership paths and deliberately move/restrict one cascade path or redesign the model; do not randomly change behavior just to make DDL apply.

11. Production judgment and bridge

Hard-delete cascades are schema-level data-loss policies. Treat them like migration and retention decisions, not convenience settings. Prefer explicit archival/retention designs for historical business records, and test both EF-tracked and direct/database-side behaviors because production data may be deleted by scripts, jobs, or other applications.

Lesson 5 completes the chapter by examining relationship fixup and disconnected graphs, where the database may be correct but the object graph can still be inconsistent, duplicated, or dangerously over-updated.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.

\n