Entity Framework : Why WillCascadeOnDelete() Method is ignored?

4.5k views Asked by At

Here is my situation :

public abstract class Article
{
    [key]
    public Guid Guid { get; set;}

    public string Name { get; set;}
    .
    .
    .
}

public class Download : Article
{
    ...
}

public abstract class Category : Article
{
    ...
}

public class DownloadCategory : Category 
{
    ....
}

And then I should have a many-to-many relation between Download and DownloadCategory like this :

public class DownloadInCategory
{
    [Key, Column(Order = 1), Required]
    [ForeignKey("Download")]
    Public Guid DownloadGuid { get; set; }

    Public Download Download { get; set; }

    [Key, Column(Order = 2), Required]
    [ForeignKey("Category")]
    Public Guid CategoryGuid { get; set; }

    Public DownloadCategory Category { get; set; }
}

When I call Add-Migration the created migration for DownloadInCategory entity is :

CreateTable("dbo.DownloadInCategories",
c => new
{
    CategoryGuid = c.Guid(nullable: false),
    DownloadGuid = c.Guid(nullable: false),
})
.PrimaryKey(t => new { t.CategoryGuid, t.DownloadGuid })
.ForeignKey("dbo.DownloadCategories", t => t.CategoryGuid)
.ForeignKey("dbo.Downloads", t => t.DownloadGuid, cascadeDelete: true)
.Index(t => t.CategoryGuid)
.Index(t => t.DownloadGuid);

Here is My Question : As you notice it's not adding cascadeDelete: true to one of foreign keys. WHY!!!!!!?????

I should mention that I didn't change any of modelbuilder Conventions. So this schema should add Casscade on delete in migration. My properties are [Required].

What am I doing wrong?

Thanks guys...

Update : Please notice that Article and Category classes are abstract. I changed classes above

Update 2: There is no Logical issue with this schema. If I edit the migration manually, It will update the database normally.

Update 3: My EF Inheritance Methodology is TPC

Update 4: After Some investigation and tests It seems problem is inheritanced from Category. When DownloadCategory is Inherited from Category, Cascade is not deployed. but when I Inherit DownloadCategory directly from Article, Cascade is deployed. But Why again?

1

There are 1 answers

8
Paul Coghill On

I would think this is because:

DownloadCategory : Category : Article

vs

Download : Article

The key is on the Article class. Multiple DownloadCategories could use the same Category, so it will not cascade on delete as this could leave other DownloadCategory's corrupted.

This is possibly a failing of Entity Framework, since you are using TPC, this should be inferred. Take a look at this article for workarounds.

Specifically these sections:

In most cases the Entity Framework can infer which type is the dependent and which is the principal in a relationship. However, when both ends of the relationship are required or both sides are optional the Entity Framework cannot identify the dependent and principal. When both ends of the relationship are required, use WithRequiredPrincipal or WithRequiredDependent after the HasRequired method. When both ends of the relationship are optional, use WithOptionalPrincipal or WithOptionalDependent after the HasOptional method.

// Configure the primary key for the OfficeAssignment 
modelBuilder.Entity<OfficeAssignment>() 
    .HasKey(t => t.InstructorID); 

modelBuilder.Entity<Instructor>() 
    .HasRequired(t => t.OfficeAssignment) 
    .WithRequiredPrincipal(t => t.Instructor);

You can configure cascade delete on a relationship by using the WillCascadeOnDelete method. If a foreign key on the dependent entity is not nullable, then Code First sets cascade delete on the relationship. If a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship, and when the principal is deleted the foreign key will be set to null.

You can remove these cascade delete conventions by using:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()

The following code configures the relationship to be required and then disables cascade delete.

modelBuilder.Entity<Course>() 
    .HasRequired(t => t.Department) 
    .WithMany(t => t.Courses) 
    .HasForeignKey(d => d.DepartmentID) 
    .WillCascadeOnDelete(false);