简体   繁体   中英

ef6 and cascading delete

I have found a little issue in EntityFramework 6 and I am not sure if it is something I have done wrong.

By default, I believe it should be enabled as stated here: http://www.entityframeworktutorial.net/code-first/cascade-delete-in-code-first.aspx

But I have found instances in my application where this is not the case. I have this model:

public class Category
{
    public string Id { get; set; }
    [Required] [MaxLength(100)] [Index(IsUnique = true)] public string Name { get; set; }
    [MaxLength(2083)] public string Image { get; set; }
    public bool Live { get; set; }

    public IList<Criteria> Criteria { get; set; }
    public IList<Feed> Feeds { get; set; }
    public IList<Sortation> Sortations { get; set; }
    public IList<Quote> Quotes { get; set; }
    public IList<Question> Questions { get; set; }
}

It did not have cascading delete working for questions, so I updated my DbContext to this:

modelBuilder.Entity<Category>().HasMany(m => m.Questions).WithOptional().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);

And when I ran update-database I saw that the constraint was now correct:

ALTER TABLE [dbo].[Questions]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Questions_dbo.Categories_CategoryId] FOREIGN KEY([CategoryId])
REFERENCES [dbo].[Categories] ([Id])
ON DELETE CASCADE
GO

I tried to delete a Category and got an error:

The DELETE statement conflicted with the REFERENCE constraint

Upon investigation it is moaning about the Answers table. Here is the Question and Answer models:

public class Question : Key
{
    public string CategoryId { get; set; }
    [Required, MaxLength(255)] public string Text { get; set; }
    [MaxLength(255)] public string AltText { get; set; }
    public int Order { get; set; }
    public int Priority { get; set; }
    public QuestionType Type { get; set; }

    public IList<Answer> Answers { get; set; }
}

public class Answer: Key
{
    public int QuestionId { get; set; }
    public int? CriteriaId { get; set; }
    [Required] [MaxLength(255)] public string Text { get; set; }
    public int Order { get; set; }
    public int Priority { get; set; }
    [MaxLength(2083)] public string Image { get; set; }

    public Criteria Criteria { get; set; }
    public Question Question { get; set; }
    public Scenario Scenario { get; set; }
    public IList<AnswerFormula> Formulas { get; set; }
    public IList<Image> Images { get; set; }
}

The mapping looked like this:

modelBuilder.Entity<Question>().HasMany(m => m.Answers).WithRequired(m => m.Question).HasForeignKey(m => m.QuestionId)

But if I inspect the constraint, I see this:

ALTER TABLE [dbo].[Answers]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Answers_dbo.Questions_QuestionId] FOREIGN KEY([QuestionId])
REFERENCES [dbo].[Questions] ([Id])
GO

Which I think is wrong, I think it should be:

ALTER TABLE [dbo].[Answers]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Answers_dbo.Questions_QuestionId] FOREIGN KEY([QuestionId])
REFERENCES [dbo].[Questions] ([Id])
ON DELETE CASCADE
GO

So I changed my mapping to this:

modelBuilder.Entity<Question>().HasMany(m => m.Answers).WithRequired(m => m.Question).HasForeignKey(m => m.QuestionId).WillCascadeOnDelete(true);

and ran 'add-migration AnswerCascadeDelete` and it told me that there were no changes....

Does anyone know why?


As an update, here is my DbContext :

public class DatabaseContext : IdentityDbContext<User>
{
    public DatabaseContext()
        : base("DefaultConnection")
    {
        Database.CommandTimeout = 900;
        Database.Log = s => Debug.WriteLine(s);
        Configuration.LazyLoadingEnabled = false;
    }

    public DbSet<Feed> Feeds { get; set; }

    public DbSet<Organisation> Organisations { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<Criteria> Criteria { get; set; }
    public DbSet<Attribute> Attributes { get; set; }
    public DbSet<AttributeFormula> CriteriaForumlas { get; set; }

    public DbSet<AttributeType> AttributeTypes { get; set; }
    public DbSet<AttributeOperation> AttributeOperations { get; set; }
    public DbSet<Scenario> Scenarios { get; set; }
    public DbSet<Question> Questions { get; set; }
    public DbSet<Answer> Answers { get; set; }
    public DbSet<AnswerFormula> AnswerForumlas { get; set; }

    public DbSet<Quote> Quotes { get; set; }

    public DbSet<Claim> Claims { get; set; }
    public DbSet<Client> Clients { get; set; }
    public DbSet<RefreshToken> RefreshTokens { get; set; }

    public DbSet<Search> Searches { get; set; }
    public DbSet<Charge> Charges { get; set; }
    public DbSet<Exclusion> Exclusions { get; set; }
    public DbSet<Sortation> Sortations { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Table renames
        modelBuilder.Entity<Criteria>().ToTable("Criteria");
        modelBuilder.Entity<IdentityRole>().ToTable("Roles");
        modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
        modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
        modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");
        modelBuilder.Entity<ImageText>().ToTable("ImageText");

        // One to One
        modelBuilder.Entity<Attribute>().HasOptional(m => m.Type).WithRequired(m => m.Attribute).WillCascadeOnDelete(false);
        modelBuilder.Entity<Attribute>().HasOptional(m => m.Operation).WithRequired(m => m.Attribute).WillCascadeOnDelete(false);
        modelBuilder.Entity<Answer>().HasOptional(m => m.Scenario).WithRequired(m => m.Answer).WillCascadeOnDelete(false);
        modelBuilder.Entity<Answer>().HasOptional(m => m.Criteria).WithMany().HasForeignKey(m => m.CriteriaId).WillCascadeOnDelete(false);

        // One to Many  
        modelBuilder.Entity<Criteria>().HasMany(m => m.Attributes).WithRequired().HasForeignKey(m => m.CriteriaId).WillCascadeOnDelete(true);
        modelBuilder.Entity<IdentityRole>().HasMany(m => m.Users).WithRequired().HasForeignKey(m => m.RoleId).WillCascadeOnDelete(false);
        modelBuilder.Entity<Organisation>().HasMany(m => m.Feeds).WithRequired().HasForeignKey(m => m.OrganisationId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Organisation>().HasMany(m => m.Users).WithRequired().HasForeignKey(m => m.OrganisationId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Question>().HasMany(m => m.Answers).WithRequired(m => m.Question).HasForeignKey(m => m.QuestionId).WillCascadeOnDelete(true);

        modelBuilder.Entity<Category>().HasMany(m => m.Sortations).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Category>().HasMany(m => m.Criteria).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Category>().HasMany(m => m.Feeds).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Category>().HasMany(m => m.Questions).WithOptional().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Category>().HasMany(m => m.Quotes).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);

        modelBuilder.Entity<User>().HasMany(m => m.Searches).WithRequired().HasForeignKey(m => m.UserId).WillCascadeOnDelete(true);
        modelBuilder.Entity<User>().HasMany(m => m.Charges).WithRequired().HasForeignKey(m => m.UserId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Answer>().HasMany(m => m.Images).WithRequired().HasForeignKey(m => m.AnswerId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Image>().HasMany(m => m.ImageText).WithRequired().HasForeignKey(m => m.ImageId).WillCascadeOnDelete(true);

        modelBuilder.Entity<Attribute>().HasMany(m => m.Formulas).WithRequired().HasForeignKey(m => m.AttributeId).WillCascadeOnDelete(true);
        modelBuilder.Entity<Answer>().HasMany(m => m.Formulas).WithRequired().HasForeignKey(m => m.AnswerId).WillCascadeOnDelete(true);

        // Create our primary keys
        modelBuilder.Entity<IdentityUserLogin>().HasKey(m => m.UserId);
        modelBuilder.Entity<IdentityRole>().HasKey(m => m.Id);
        modelBuilder.Entity<IdentityUserRole>().HasKey(m => new {m.RoleId, m.UserId});
        modelBuilder.Entity<AttributeOperation>().HasKey(m => m.AttributeId);
        modelBuilder.Entity<AttributeType>().HasKey(m => m.AttributeId);
        modelBuilder.Entity<Scenario>().HasKey(m => m.AnswerId);
    }
}

As you can see, I have explicitly set WillCascadeOnDelete on all relationships. I thought that most would be set by default, and in this case it does not actually generate any code in the migration. But when I check any of my tables, only some have cascading delete enabled and I cannot understand why....

As far as I know, EF treat relationships and cascading delete through the folowing configuration settings:

Don't do cascading delete if:

  • its explicitly stated by .WillCascadeOnDelete(false)
  • with optional relation, ie: .WithOptional()

Do do cascading delete if:

  • its explicitly stated by .WillCascadeOnDelete(true)
  • with required relation, ie: .WithRequired(/*subject*/)

Note: these can also be triggered by data annotations, eg; [RequiredAttribute] , or, the optional variant; using Nullable<Type>

Now, in your constraint there is:

ALTER TABLE [dbo].[Answers] WITH CHECK ADD CONSTRAINT [FK_dbo.Answers_dbo.Questions_QuestionId] FOREIGN KEY([QuestionId]) REFERENCES [dbo].[Questions] ([Id]) GO

But this referrers to to the answers, and not to the questions. So, I think the constraint is valid:

A question can be without answers.

I'll gather if you check the constraint on questions you'll find cascading delete enabled.

Maybe it's the documentation that is misleading or simply wrong. The official documentation tells you this:

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.

So it basically says that the default behavior depends on the nullability of your foreign key and is not enabling cascade deletion on all relationships by default as it states in the link you provided.

I see the same exact thing in your case:

  • In the first case CategoryId is nullable thus causing cascade delete not to be used by default, therefore you need to enable it explicitly
  • In the second case QuestionId is not nullable thus causing cascade delete to be used by default but you are able to explicitly disable it (which you haven't tried, as far as I understand, you only tried enabling it, but it was already enabled by default)

As for why there's no explicit cascade delete in the other case, I'm not sure, but I think it might be default in SQL as well, therefore WillCascadeOnDelete(true) simply doesn't change anything on any side. However, you might get different results if you try WillCascadeOnDelete(false) which should override the default behavior based on the documentation.

So, this was bothering me; I decided to change my DbContext . First I disabled all the cascade deletes and then updated the database like this:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Table renames
    modelBuilder.Entity<Criteria>().ToTable("Criteria");
    modelBuilder.Entity<IdentityRole>().ToTable("Roles");
    modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
    modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
    modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");

    // One to One
    modelBuilder.Entity<Attribute>().HasOptional(m => m.Type).WithRequired(m => m.Attribute).WillCascadeOnDelete(false);
    modelBuilder.Entity<Attribute>().HasOptional(m => m.Operation).WithRequired(m => m.Attribute).WillCascadeOnDelete(false);
    modelBuilder.Entity<Answer>().HasOptional(m => m.Scenario).WithRequired(m => m.Answer).WillCascadeOnDelete(false);
    modelBuilder.Entity<Answer>().HasOptional(m => m.Criteria).WithMany().HasForeignKey(m => m.CriteriaId).WillCascadeOnDelete(false);

    // One to Many  
    modelBuilder.Entity<Criteria>().HasMany(m => m.Attributes).WithRequired().HasForeignKey(m => m.CriteriaId).WillCascadeOnDelete(false);
    modelBuilder.Entity<IdentityRole>().HasMany(m => m.Users).WithRequired().HasForeignKey(m => m.RoleId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Organisation>().HasMany(m => m.Feeds).WithRequired().HasForeignKey(m => m.OrganisationId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Organisation>().HasMany(m => m.Users).WithRequired().HasForeignKey(m => m.OrganisationId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Question>().HasMany(m => m.Answers).WithRequired(m => m.Question).HasForeignKey(m => m.QuestionId).WillCascadeOnDelete(false);

    modelBuilder.Entity<Category>().HasMany(m => m.Sortations).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Category>().HasMany(m => m.Criteria).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Category>().HasMany(m => m.Feeds).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Category>().HasMany(m => m.Questions).WithOptional().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Category>().HasMany(m => m.Quotes).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(false);

    modelBuilder.Entity<User>().HasMany(m => m.Searches).WithRequired().HasForeignKey(m => m.UserId).WillCascadeOnDelete(false);
    modelBuilder.Entity<User>().HasMany(m => m.Charges).WithRequired().HasForeignKey(m => m.UserId).WillCascadeOnDelete(false);

    modelBuilder.Entity<Attribute>().HasMany(m => m.Formulas).WithRequired().HasForeignKey(m => m.AttributeId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Answer>().HasMany(m => m.Formulas).WithRequired().HasForeignKey(m => m.AnswerId).WillCascadeOnDelete(false);

    // Create our primary keys
    modelBuilder.Entity<IdentityUserLogin>().HasKey(m => m.UserId);
    modelBuilder.Entity<IdentityRole>().HasKey(m => m.Id);
    modelBuilder.Entity<IdentityUserRole>().HasKey(m => new {m.RoleId, m.UserId});
    modelBuilder.Entity<AttributeOperation>().HasKey(m => m.AttributeId);
    modelBuilder.Entity<AttributeType>().HasKey(m => m.AttributeId);
    modelBuilder.Entity<Scenario>().HasKey(m => m.AnswerId);
}

When this ran, it actually updated the foreign keys correctly. I could see this:

public partial class DisableCascadeDelete : DbMigration
{
    public override void Up()
    {
        DropForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers");
        DropForeignKey("dbo.Images", "AnswerId", "dbo.Answers");
        DropForeignKey("dbo.Answers", "QuestionId", "dbo.Questions");
        DropForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria");
        DropForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes");
        DropForeignKey("dbo.ImageText", "ImageId", "dbo.Images");
        DropForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Questions", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Charges", "UserId", "dbo.Users");
        DropForeignKey("dbo.Searches", "UserId", "dbo.Users");
        AddForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers", "Id");
        AddForeignKey("dbo.Images", "AnswerId", "dbo.Answers", "Id");
        AddForeignKey("dbo.Answers", "QuestionId", "dbo.Questions", "Id");
        AddForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria", "Id");
        AddForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes", "Id");
        AddForeignKey("dbo.ImageText", "ImageId", "dbo.Images", "Id");
        AddForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Questions", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations", "Id");
        AddForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations", "Id");
        AddForeignKey("dbo.Charges", "UserId", "dbo.Users", "Id");
        AddForeignKey("dbo.Searches", "UserId", "dbo.Users", "Id");
    }

    public override void Down()
    {
        DropForeignKey("dbo.Searches", "UserId", "dbo.Users");
        DropForeignKey("dbo.Charges", "UserId", "dbo.Users");
        DropForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Questions", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.ImageText", "ImageId", "dbo.Images");
        DropForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes");
        DropForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria");
        DropForeignKey("dbo.Answers", "QuestionId", "dbo.Questions");
        DropForeignKey("dbo.Images", "AnswerId", "dbo.Answers");
        DropForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers");
        AddForeignKey("dbo.Searches", "UserId", "dbo.Users", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Charges", "UserId", "dbo.Users", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Questions", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.ImageText", "ImageId", "dbo.Images", "Id", cascadeDelete: true);
        AddForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Answers", "QuestionId", "dbo.Questions", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Images", "AnswerId", "dbo.Answers", "Id", cascadeDelete: true);
        AddForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers", "Id", cascadeDelete: true);
    }
}

Which I could see was correct. Then I created a new migration after changing my DbContext back:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Table renames
    modelBuilder.Entity<Criteria>().ToTable("Criteria");
    modelBuilder.Entity<IdentityRole>().ToTable("Roles");
    modelBuilder.Entity<IdentityUserRole>().ToTable("UserRoles");
    modelBuilder.Entity<IdentityUserClaim>().ToTable("UserClaims");
    modelBuilder.Entity<IdentityUserLogin>().ToTable("UserLogins");

    // One to One
    modelBuilder.Entity<Attribute>().HasOptional(m => m.Type).WithRequired(m => m.Attribute).WillCascadeOnDelete(false);
    modelBuilder.Entity<Attribute>().HasOptional(m => m.Operation).WithRequired(m => m.Attribute).WillCascadeOnDelete(false);
    modelBuilder.Entity<Answer>().HasOptional(m => m.Scenario).WithRequired(m => m.Answer).WillCascadeOnDelete(false);
    modelBuilder.Entity<Answer>().HasOptional(m => m.Criteria).WithMany().HasForeignKey(m => m.CriteriaId).WillCascadeOnDelete(false);

    // One to Many  
    modelBuilder.Entity<Criteria>().HasMany(m => m.Attributes).WithRequired().HasForeignKey(m => m.CriteriaId).WillCascadeOnDelete(true);
    modelBuilder.Entity<IdentityRole>().HasMany(m => m.Users).WithRequired().HasForeignKey(m => m.RoleId).WillCascadeOnDelete(false);
    modelBuilder.Entity<Organisation>().HasMany(m => m.Feeds).WithRequired().HasForeignKey(m => m.OrganisationId).WillCascadeOnDelete(true);
    modelBuilder.Entity<Organisation>().HasMany(m => m.Users).WithRequired().HasForeignKey(m => m.OrganisationId).WillCascadeOnDelete(true);
    modelBuilder.Entity<Question>().HasMany(m => m.Answers).WithRequired(m => m.Question).HasForeignKey(m => m.QuestionId).WillCascadeOnDelete(true);

    modelBuilder.Entity<Category>().HasMany(m => m.Sortations).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
    modelBuilder.Entity<Category>().HasMany(m => m.Criteria).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
    modelBuilder.Entity<Category>().HasMany(m => m.Feeds).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
    modelBuilder.Entity<Category>().HasMany(m => m.Questions).WithOptional().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);
    modelBuilder.Entity<Category>().HasMany(m => m.Quotes).WithRequired().HasForeignKey(m => m.CategoryId).WillCascadeOnDelete(true);

    modelBuilder.Entity<User>().HasMany(m => m.Searches).WithRequired().HasForeignKey(m => m.UserId).WillCascadeOnDelete(true);
    modelBuilder.Entity<User>().HasMany(m => m.Charges).WithRequired().HasForeignKey(m => m.UserId).WillCascadeOnDelete(true);

    modelBuilder.Entity<Attribute>().HasMany(m => m.Formulas).WithRequired().HasForeignKey(m => m.AttributeId).WillCascadeOnDelete(true);
    modelBuilder.Entity<Answer>().HasMany(m => m.Formulas).WithRequired().HasForeignKey(m => m.AnswerId).WillCascadeOnDelete(true);

    // Create our primary keys
    modelBuilder.Entity<IdentityUserLogin>().HasKey(m => m.UserId);
    modelBuilder.Entity<IdentityRole>().HasKey(m => m.Id);
    modelBuilder.Entity<IdentityUserRole>().HasKey(m => new {m.RoleId, m.UserId});
    modelBuilder.Entity<AttributeOperation>().HasKey(m => m.AttributeId);
    modelBuilder.Entity<AttributeType>().HasKey(m => m.AttributeId);
    modelBuilder.Entity<Scenario>().HasKey(m => m.AnswerId);
}

Which generated this:

public partial class EnableCascadeDelete : DbMigration
{
    public override void Up()
    {
        DropForeignKey("dbo.ImageText", "ImageId", "dbo.Images");
        DropForeignKey("dbo.Images", "AnswerId", "dbo.Answers");
        DropForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers");
        DropForeignKey("dbo.Answers", "QuestionId", "dbo.Questions");
        DropForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria");
        DropForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes");
        DropForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Questions", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Charges", "UserId", "dbo.Users");
        DropForeignKey("dbo.Searches", "UserId", "dbo.Users");
        DropIndex("dbo.Images", new[] { "AnswerId" });
        DropIndex("dbo.ImageText", new[] { "ImageId" });
        AddForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Answers", "QuestionId", "dbo.Questions", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria", "Id", cascadeDelete: true);
        AddForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Questions", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Charges", "UserId", "dbo.Users", "Id", cascadeDelete: true);
        AddForeignKey("dbo.Searches", "UserId", "dbo.Users", "Id", cascadeDelete: true);
        DropTable("dbo.ImageText");
        DropTable("dbo.Images");
    }

    public override void Down()
    {
        CreateTable(
            "dbo.ImageText",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    ImageId = c.Int(nullable: false),
                    Text = c.String(),
                    Delay = c.Int(nullable: false),
                })
            .PrimaryKey(t => t.Id);

        CreateTable(
            "dbo.Images",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    AnswerId = c.Int(nullable: false),
                    Url = c.String(),
                })
            .PrimaryKey(t => t.Id);

        DropForeignKey("dbo.Searches", "UserId", "dbo.Users");
        DropForeignKey("dbo.Charges", "UserId", "dbo.Users");
        DropForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations");
        DropForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Questions", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories");
        DropForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes");
        DropForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria");
        DropForeignKey("dbo.Answers", "QuestionId", "dbo.Questions");
        DropForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers");
        CreateIndex("dbo.ImageText", "ImageId");
        CreateIndex("dbo.Images", "AnswerId");
        AddForeignKey("dbo.Searches", "UserId", "dbo.Users", "Id");
        AddForeignKey("dbo.Charges", "UserId", "dbo.Users", "Id");
        AddForeignKey("dbo.Users", "OrganisationId", "dbo.Organisations", "Id");
        AddForeignKey("dbo.Feeds", "OrganisationId", "dbo.Organisations", "Id");
        AddForeignKey("dbo.Sortations", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Quotes", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Questions", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Feeds", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.Criteria", "CategoryId", "dbo.Categories", "Id");
        AddForeignKey("dbo.AttributeFormulas", "AttributeId", "dbo.Attributes", "Id");
        AddForeignKey("dbo.Attributes", "CriteriaId", "dbo.Criteria", "Id");
        AddForeignKey("dbo.Answers", "QuestionId", "dbo.Questions", "Id");
        AddForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers", "Id");
        AddForeignKey("dbo.Images", "AnswerId", "dbo.Answers", "Id");
        AddForeignKey("dbo.ImageText", "ImageId", "dbo.Images", "Id");
    }
}

As you can see, each foreign key now has:

AddForeignKey("dbo.AnswerFormulas", "AnswerId", "dbo.Answers", "Id", cascadeDelete: true);

So it is explicitly settings the cascadeDelete option which has never been present.

So, what do I conclude? It appears that when setting up a database code first, you cannot and should not rely on EntityFramework to create cascading delete. If you want it, you need to specify it on creation. This should avoid my issue happening to you. But, if it does, simply disable it and re-enable on the next migration and that will fix your issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM