EF Core: How to cascade delete when DB-first FK is the wrong way around?

c# cascade entity-framework-core


Consider this (admittedly rather confused) entity model of a legacy database:

enter image description here

As you can see it models Blogs that have a collection of Entrys which point to a blog Post each. The tips of the arrows point to the principal/parent of the relationship. At least this is the way EF core sees it based on where the foreign keys are stored.

Unfortunately the FK between Entry and Post is the wrong way around, because a Post is actually a child of an Entry; i.e. when I delete an Entry from a Blog then the associated Post should be deleted as well.

As I'm unable to change the schema of the legacy database, I'm wondering how I can convice EF to cascade-delete Posts although it doesn't consider them to be children of Entrys.

I tried specifiying the DeleteBehavior explicitely to no avail:

    .HasOne(e => e.Post)
    .WithOne(p => p.Entry)

I also tried making the navigation property Post.Entry required to indicate that a Post cannot live without its Entry, but that didn't help either:

public class Post {
    // ...
    public Entry Entry { get; set; }

I'm currently checking if owned entity types could help, but I'm sceptical.

I guess what I need is a way to explicitely state the parent/principal of an entity relationship. Something like SetPrincipalEntityType().

Of course I could always manually delete the Post, but I'm trying to avoid that.

Any ideas anyone?

Working example

I created a test project with integration tests that you can use to reproduce the issue:

> git clone https://github.com/bert2/ef-vs-garbage-db.git
> cd .\ef-vs-garbage-db\
> dotnet test

The test DeletesTextWhenDeletingReview will fail.

2/10/2019 1:16:30 PM

Accepted Answer

I posted the same question on the EF Core GitHub page. The idea of the solution posted there is to find the orphaned entities in an override of SaveChanges() and delete them manually:

public override int SaveChanges(bool acceptAllChangesOnSuccess)
    // Note that this is internal code to force cascade deletes to happen.
    // It may stop working in any future release.

        ChangeTracker.AutoDetectChangesEnabled = false;

        foreach (var entry in ChangeTracker
            .Where(e => Entry(e.Entity.Review).State == EntityState.Deleted))
            entry.State = EntityState.Deleted;
        ChangeTracker.AutoDetectChangesEnabled = true;

    return base.SaveChanges(acceptAllChangesOnSuccess);

I took this and build an attribute based solution from this that you can find in the test project repo.

2/15/2019 9:11:06 PM

Related Questions


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow