13

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?

Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
Manoochehr Dadashi
  • 715
  • 1
  • 10
  • 28
  • If editing the migration manually works fine, is this more of a theoretical question about why cascade delete isn't inferred? – jjj Jun 09 '15 at 19:18
  • 5
    @jjj No. This is completely practical in my point of view. Manually editing the migration is not a good practice at all. In larger projects like the one I'm working on, Do such a thing adds more considerations to maintenance and development of new features to the project. I think manually editing the migration is wrong. and migration should match the schema of models. – Manoochehr Dadashi Jun 10 '15 at 07:16
  • 4
    I suppose that would be true in an ideal world where the built-in conventions were perfect. You could dig into the source code to figure out why this happened in this case, but I'm guessing it's the result of a shortcut to avoid cycles. – jjj Jun 15 '15 at 00:01
  • 2
    I'd think you'd also need to edit the migration if data needs to be moved around, or if you you're adding a view or stored procedure or something. – jjj Jun 15 '15 at 00:03
  • Thanks @jjj I will do some investigations in source code to determine the main cause. And I will update here when I figured it out – Manoochehr Dadashi Jun 15 '15 at 09:59
  • 1
    @abzarak Did you ever find the solution to this in your source-code-delving adventure? I've just come across the same issue and am failing to find the cause. The generated EDM from the code first model (extracted from the `__MigrationsHistory` table) even seems to contain the appropriate `OnDelete Action="Cascade"` but somehow it's not being passed along to the `CreateForeignKeyOperation`... And it really does seem to be limited to classes with somewhat complex inheritance chains. – lc. Oct 23 '15 at 16:46

1 Answers1

3

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);
Amirhossein Mehrvarzi
  • 18,024
  • 7
  • 45
  • 70
Paul Coghill
  • 667
  • 6
  • 27
  • thanks. Article and Category class are Abstract. I'm Updating my question mentioning they are Abstract. – Manoochehr Dadashi Jun 09 '15 at 11:06
  • I'm not sure how this makes a difference? – Paul Coghill Jun 09 '15 at 11:45
  • If Category class is abstract there will be no instances for Category Directly. And Instances are from DownloadCategory. so "Multiple DownloadCategories could use the same Category" doesn't make sense here... – Manoochehr Dadashi Jun 09 '15 at 11:55
  • Also DownloadCategory is inherited from Category. and my EF Inheritance Methodology is TPC. @Paul Coghill – Manoochehr Dadashi Jun 09 '15 at 11:58
  • Thanks for your update. But half of your answer is about one-to-one relation and sorry it is kind of irrelevant to the question. About secend part, my problem is I'm doing all the workarounds. Some unknown thing is preventing from deploying cascade delete. I'm using modelbuilder default conventions that shoud deploy cascade delete on none-nullable properties. Also I use the WillCascadeOnDelete() method to explicitly tell it to deploy the cascade. But I don't know why it's ignored. – Manoochehr Dadashi Jun 09 '15 at 13:10
  • 2
    The one-to-one relationship item was only there to illustrate that there are limitations in what EF will infer. I don't find it irrelevant, but please feel free to edit the answer to remove what's confusing. The question originally seemed to ask why EF was not automatically inferring cascading. Then you mentioned abstraction & TPC, so I inferred you wanted a workaround. So the issue is deploying the migration with the workaround? These are 3 separate issues. All the updates mean this question is difficult to read for others. Could you please rewrite the question with all this in mind? – Paul Coghill Jun 09 '15 at 13:38
  • dear Paul I really appreciate your answer and I'm really thankful for putting your time on my question. Ok I will update the question. and yes my point is to do a workaround to force EF to do what it should do. And your answer is correct in general. But as you see my question title is "WillCascadeOnDelete is ignored". So I used this method as a single workaround provided by EF. So I think there is another unknown cause that overrides my config. after 2 days of searching I'm totally desperate. – Manoochehr Dadashi Jun 09 '15 at 14:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/80094/discussion-between-abzarak-and-paul-coghill). – Manoochehr Dadashi Jun 09 '15 at 16:56