Search code examples
c#sql-serverentity-framework-coreef-core-6.0

One to One Relationship Cascade deleting child also deletes the parent


Parent

    public class Blog
    {
        public int Id { get; set; }
        public BlogHeader BlogHeader { get; set; } 
    }

Child

    
    public class BlogHeader
    {
        public int Id { get; set; }
        public int BlogId { get; set; } 
        public Blog Blog { get; set; }
    }

I'm using EF6 and using convention. But when I go to SQL Server Management Studio and delete a row of BlogHeader the Blog associated with it also gets deleted how can I overcome this problem?

Since im using convention it's using Cascade Delete.

What I want is that it should not be possible to delete a BlogHeader. The only way to delete the BlogHeader would be by deleting the Blog (parent), which would delete Blog and BlogHeader associated with it.

What am I doing wrong here?


Solution

  • Cascade delete in a required 1-to-1 will delete either side if the other is deleted. You have set up your Blog to require a BlogHeader and your BlogHeader to require a Blog since neither reference is null-able. If a Blog can exist without a BlogHeader then:

    public class Blog
    {
        public int Id { get; set; }
        public BlogHeader? BlogHeader { get; set; } 
    }
    

    should potentially sort it out. The BlogId FK can exist in BlogHeader, just ensure that your entity configuration is set up to use it. I tend to default to explicit configuration rather than relying on both my, and EF's assumptions around convention marrying up :)

    modelBuilder.Entity<Blog>()
        .HasOne(x => x.BlogHeader)
        .WithOne(x => x.Blog)
        .IsRequired(false)
        .HasForeignKey<BlogHeader>(x => x.BlogId);
    

    Update: If you want a relationship so that a Blog has a BlogHeader and the BlogHeader can only be removed along with its Blog, then what you may be looking for is an Owned relationship. Typically this embeds BlogHeader into the Blog table, but EF supports setting it up as separate tables as well. In that case you would have:

    public class Blog
    {
        public int Id { get; set; }
        public BlogHeader BlogHeader { get; set; } 
    }
    
    public class BlogHeader
    {
        public int Id { get; set; }
        // ... other columns Note, no BlogId/Blog.
    }
    
    modelBuilder.Entity<Blog>()
        .OwnsOne(x => x.BlogHeader);
    

    AFAIK with OwnsOne I believe you need to use the default PK-PK convention used for one-to-one relationships, I don't know if it supports HasForeignKey since by default it uses table splitting. What you get as a table by default with tablesplitting would be:

    Blogs
    ======
    Id [INT PK]
    // Blog Fields
    // BlogHeader_ prefixed blog header fields.
    

    You can tell EF to use a BlogHeader table using the .ToTable() configuration option:

    modelBuilder.Entity<Blog>()
        .OwnsOne(x => x.BlogHeader, bh => { bh.ToTable("BlogHeaders") });
    

    Either way, EF will enforce some rules with owned entities. You cannot have a DbSet of BlogHeaders, they are owned and accessed solely through Blog. They cannot be referenced by other entities. AFAIK there is no inverse navigation property, and really no need for one since you only access a BlogHeader through it's Blog. They will cascade delete when using the ToTable but otherwise delete with the entity that owns them as they normally reside in that table. They also cannot support inheritance structures, so you cannot use something like owning an entity that participates in a TPH/TPT/TPC inheritance structure with a base class.

    Have a look through that option and see if it meets your needs: https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities