410

I've been wrestling with this for a while and can't quite figure out what's happening. I have a Card entity which contains Sides (usually 2) - and both Cards and Sides have a Stage. I'm using EF Codefirst migrations and the migrations are failing with this error:

Introducing FOREIGN KEY constraint 'FK_dbo.Sides_dbo.Cards_CardId' on table 'Sides' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

Here's my Card entity:

public class Card
{
    public Card()
    {
        Sides = new Collection<Side>();
        Stage = Stage.ONE;
    }

    [Key]
    [Required]
    public virtual int CardId { get; set; }

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    [ForeignKey("CardId")]
    public virtual ICollection<Side> Sides { get; set; }
}

Here's my Side entity:

public class Side
{
    public Side()
    {
        Stage = Stage.ONE;
    }

    [Key]
    [Required]     
    public virtual int SideId { get; set; } 

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    public int CardId { get; set; }

    [ForeignKey("CardId")]
    public virtual Card Card { get; set; }

}

And here's my Stage entity:

public class Stage
{
    // Zero
    public static readonly Stage ONE = new Stage(new TimeSpan(0, 0, 0), "ONE");
    // Ten seconds
    public static readonly Stage TWO = new Stage(new TimeSpan(0, 0, 10), "TWO");

    public static IEnumerable<Stage> Values
    {
        get
        {
            yield return ONE;
            yield return TWO;
        }

    }

    public int StageId { get; set; }
    private readonly TimeSpan span;
    public string Title { get; set; }

    Stage(TimeSpan span, string title)
    {
        this.span = span;
        this.Title = title;
    }

    public TimeSpan Span { get { return span; } }
}

What's odd is that if I add the following to my Stage class:

    public int? SideId { get; set; }
    [ForeignKey("SideId")]
    public virtual Side Side { get; set; }

The migration runs successfully. If I open up SSMS and look at the tables, I can see that Stage_StageId has been added to Cards (as expected/desired), however Sides contains no reference to Stage (not expected).

If I then add

    [Required]
    [ForeignKey("StageId")]
    public virtual Stage Stage { get; set; }
    public int StageId { get; set; }

To my Side class, I see StageId column added to my Side table.

This is working, but now throughout my application, any reference to Stage contains a SideId, which is in some cases totally irrelevant. I'd like to just give my Card and Side entities a Stage property based on the above Stage class without polluting the stage class with reference properties if possible... what am I doing wrong?

Phuc Thai
  • 718
  • 7
  • 17
SB2055
  • 12,272
  • 32
  • 97
  • 202
  • 8
    Disable cascading delete by allowing null values in the references... so in `Side` Class add Nullable integer and remove `[Required]` attribute => `public int? CardId { get; set; }` – Jaider Jan 23 '14 at 22:45
  • 4
    In the EF Core, You should disable cascade delete with `DeleteBehavior.Restrict` or `DeleteBehavior.SetNull`. – Sina Lotfi Feb 02 '19 at 14:08
  • 6
    The accepted answer is the only correct answer. The question is: how to prevent a circular cascade path if I want a *required* relationship. One simple mapping instruction suffices. So don't suggest to make the relationship optional, or worse, to edit the generated migration file (introducing a discrepancy between the db model and the conceptual model), or worse yet, to disable all cascaded deletes. – Gert Arnold Nov 08 '20 at 08:53
  • ich changed migration file before doing update-database and repalce cascade with noaction -> onDelete: ReferentialAction.NoAction. it solved the problem – MindRoasterMir Oct 14 '22 at 04:44

19 Answers19

476

Because Stage is required, all one-to-many relationships where Stage is involved will have cascading delete enabled by default. It means, if you delete a Stage entity

  • the delete will cascade directly to Side
  • the delete will cascade directly to Card and because Card and Side have a required one-to-many relationship with cascading delete enabled by default again it will then cascade from Card to Side

So, you have two cascading delete paths from Stage to Side - which causes the exception.

You must either make the Stage optional in at least one of the entities (i.e. remove the [Required] attribute from the Stage properties) or disable cascading delete with Fluent API (not possible with data annotations):

modelBuilder.Entity<Card>()
    .HasRequired(c => c.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);
Riz
  • 6,486
  • 19
  • 66
  • 106
Slauma
  • 175,098
  • 59
  • 401
  • 420
  • 3
    Thanks Slauma. If I use fluent API as you've demonstrated above, will other fields retain their cascade delete behavior? I still need Sides to be deleted when cards are deleted, for example. – SB2055 Jun 15 '13 at 20:53
  • 1
    @SB2055: Yes, it will only affect the relationships from `Stage`. Other relationships remain unchanged. – Slauma Jun 15 '13 at 21:08
  • 3
    Is there any way to know wich properties are causing the error? I'm having the same problem, and looking at my classes I can´t see where is the cycle – Rodrigo Juarez May 27 '17 at 01:00
  • 5
    Is this a limitation in their implementation? Seems fine to me for a `Stage` deletion to cascade down to `Side` both directly and through a `Card` – aaaaaa Jul 04 '17 at 22:20
  • 1
    Suppose we set CascadeOnDelete to false. Then we removed a stage record which is related with one of the Card records. What happens to Card.Stage (FK)? Does it remain same? or is it set to Null? – ninbit Dec 28 '18 at 23:40
  • 1
    What i you want this cascade behavior? In the case of a hierarchical entity structure, that is expected to cascade in both the hierarchy, and if the referenced entity is deleted? – Douglas Gaskell Mar 26 '20 at 20:19
  • `.OnDelete(DeleteBehavior.NoAction)` in EF Core 5. – Marc.2377 Jul 09 '21 at 01:43
  • I don't follow your answer - I read this as Stage & Side have a 1:1 relationship, not a 1:many. – David Thielen Mar 22 '23 at 20:39
  • 1
    Also, the latest EF Core does not have WillCascadeOnDelete(). I think you now need OnDelete(). – David Thielen Mar 22 '23 at 20:44
96

I had a table that had a circular relationship with others and I was getting the same error. Turns out it is about the foreign key which was not nullable. If the key is not nullable the related object must be deleted, and circular relations don't allow that. So use nullable foreign key.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Null
  • 1,950
  • 9
  • 30
  • 33
Cem Mutlu
  • 1,969
  • 1
  • 24
  • 24
  • 8
    I removed [Required] tag but anther important thing was to use `int?` instead of `int` to let it be nullable. – VSB Mar 28 '16 at 17:45
  • 1
    I tried many different ways of turning off cascade delete and nothing worked - this fixed it! – ambog36 Oct 05 '16 at 20:57
  • 16
    You shouldn't do this if you don't want to allow Stage to be set to null (Stage was a required field in the original question). – cfwall Nov 15 '17 at 10:26
51

Anybody wondering how to do it in EF core:

      protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
                {
                    relationship.DeleteBehavior = DeleteBehavior.Restrict;
                }
           ..... rest of the code.....
Nexus23
  • 6,195
  • 9
  • 50
  • 67
  • 6
    That would turn off cascade delete on all the relationships. Cascade delete may be a desired feature for some use cases. – Blaze Jun 06 '17 at 10:29
  • 28
    Alternatively, `builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);` – Biscuits Apr 04 '18 at 11:30
  • @Biscuits Either the extension methods changed over time or you forgot the `builder _ .Entity() _` before `HasOne() ` can be called... – ViRuSTriNiTy May 18 '20 at 13:51
  • 1
    @ViRuSTriNiTy, my snippet is 2 years old. But, I think you're right - nowadays it would be for when you opt to implement `IEntityTypeConfiguration`. I don't recall seeing the `builder.Entity` method those days, but I could be wrong. Nevertheless, they'll both work :) – Biscuits May 20 '20 at 13:18
  • That inspires to a lot of dynamic configuration approaches.. thanks – sabsab Feb 14 '22 at 00:15
  • 1
    That should be reducing the specific solution to a let's cancel cascading behavior everywhere. I don't think this is a solution but a reformulation of convention defaults. – Rick May 26 '22 at 22:30
  • That's cool because I don't need to create all my relations using Fluent Api. I could use Annotations and your code did the rest. Thank you. – Rhuan Barros Aug 31 '22 at 20:18
27

I was getting this error for lots of entities when I was migrating down from an EF7 model to an EF6 version. I didn't want to have to go through each entity one at a time, so I used:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Sean
  • 14,359
  • 13
  • 74
  • 124
  • 3
    This should be added in the class(es) that inherit from DbContext e.g. in the OnModelCreating method. The builder is of type DbModelBuilder – CodingYourLife Dec 15 '17 at 09:35
  • This worked for me; .NET 4.7, EF 6. One stumbling block was I got the error, so when I regenerated by migration script with these conventions removed, it didn't APPEAR to help. Running the "Add-Migration" with "-Force" cleared it all, and rebuilt it including these conventions above. Problem solved... – James Joyce Jun 14 '18 at 16:35
  • Those don't exist in .net core, any equivalent there? – jjxtra Jul 29 '19 at 01:13
  • @jjxtra check https://stackoverflow.com/questions/46526230/disable-cascade-delete-on-ef-core-2-globally – Sean Jul 29 '19 at 06:35
25

You can set cascadeDelete to false or true (in your migration Up() method). Depends upon your requirement.

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Nozim Turakulov
  • 869
  • 1
  • 9
  • 19
Musakkhir Sayyed
  • 7,012
  • 13
  • 42
  • 65
23

In .NET Core I changed the onDelete option to ReferencialAction.NoAction

         constraints: table =>
            {
                table.PrimaryKey("PK_Schedule", x => x.Id);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_HomeId",
                    column: x => x.HomeId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_VisitorId",
                    column: x => x.VisitorId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
            });
Mike Jones
  • 261
  • 2
  • 2
12

I had this issue also, I solved it instantly with this answer from a similar thread

In my case, I didn't want to delete the dependent record on key deletion. If this is the case in your situation just simply change the Boolean value in the migration to false:

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);

Chances are, if you are creating relationships which throw this compiler error but DO want to maintain cascade delete; you have an issue with your relationships.

Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
jonc.js
  • 408
  • 4
  • 11
10

I fixed this. When you add the migration, in the Up() method there will be a line like this:

.ForeignKey("dbo.Members", t => t.MemberId, cascadeDelete:True)

If you just delete the cascadeDelete from the end it will work.

Rafael Herscovici
  • 16,558
  • 19
  • 65
  • 93
Usman Khan
  • 676
  • 1
  • 7
  • 20
7

Just for documentation purpose, to someone that comes on the future, this thing can be solved as simple as this, and with this method, you could do a method that disabled one time, and you could access your method normally

Add this method to the context database class:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
ikos23
  • 4,879
  • 10
  • 41
  • 60
sgrysoft
  • 588
  • 7
  • 14
5

In .NET Core I played with all upper answers - but without any success. I made changes a lot in DB structure and every time added new migration attempting to update-database, but received the same error.

Then I started to remove-migration one by one until Package Manager Console threw me exception:

The migration '20170827183131_***' has already been applied to the database

After that, I added new migration (add-migration) and update-database successfully

So my suggestion would be: clear out all your temp migrations, until your current DB state.

rock_walker
  • 453
  • 5
  • 14
  • This was it for me! Tried all variants of fluid configuration of cascading behaviour, but i kept seeing the same SQL beeing executed, beacuse of already created migrations that were trying to apply first :/ – BobbyTables Aug 21 '20 at 08:06
2
public partial class recommended_books : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.RecommendedBook",
            c => new
                {
                    RecommendedBookID = c.Int(nullable: false, identity: true),
                    CourseID = c.Int(nullable: false),
                    DepartmentID = c.Int(nullable: false),
                    Title = c.String(),
                    Author = c.String(),
                    PublicationDate = c.DateTime(nullable: false),
                })
            .PrimaryKey(t => t.RecommendedBookID)
            .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: false) // was true on migration
            .ForeignKey("dbo.Department", t => t.DepartmentID, cascadeDelete: false) // was true on migration
            .Index(t => t.CourseID)
            .Index(t => t.DepartmentID);

    }

    public override void Down()
    {
        DropForeignKey("dbo.RecommendedBook", "DepartmentID", "dbo.Department");
        DropForeignKey("dbo.RecommendedBook", "CourseID", "dbo.Course");
        DropIndex("dbo.RecommendedBook", new[] { "DepartmentID" });
        DropIndex("dbo.RecommendedBook", new[] { "CourseID" });
        DropTable("dbo.RecommendedBook");
    }
}

When your migration fails you are given a couple of options: 'Introducing FOREIGN KEY constraint 'FK_dbo.RecommendedBook_dbo.Department_DepartmentID' on table 'RecommendedBook' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. Could not create constraint or index. See previous errors.'

Here is an example of using the 'modify other FOREIGN KEY constraints' by setting 'cascadeDelete' to false in the migration file and then run 'update-database'.

  • 2
    It is a lot better to change this in the `modelBuilder` than to edit an auto generated migration. – Ogglas Sep 18 '21 at 10:39
2

Make your Foreign key attributes nullable. That will work.

1

This sounds weird and I don't know why, but in my case that was happening because my ConnectionString was using "." in "data source" attribute. Once I changed it to "localhost" it workded like a charm. No other change was needed.

Marco Alves
  • 2,776
  • 3
  • 23
  • 33
1

The existing answers are great I just wanted to add that I ran into this error because of a different reason. I wanted to create an Initial EF migration on an existing DB but I didn't use the -IgnoreChanges flag and applied the Update-Database command on an empty Database (also on the existing fails).

Instead I had to run this command when the current db structure is the current one:

Add-Migration Initial -IgnoreChanges

There is likely a real problem in the db structure but save the world one step at a time...

CodingYourLife
  • 7,172
  • 5
  • 55
  • 69
1

In .NET 5 < and .NET Core 2.0 < you can use .OnDelete(DeleteBehavior.Restrict) in OnModelCreating like @Nexus23 answer but you do not need to disable cascade for every model.

Example with join entity type configuration many-to-many:

internal class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasMany(p => p.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity<PostTag>(
                j => j
                    .HasOne(pt => pt.Tag)
                    .WithMany(t => t.PostTags)
                    .HasForeignKey(pt => pt.TagId)
                    .OnDelete(DeleteBehavior.Restrict),
                j => j
                    .HasOne(pt => pt.Post)
                    .WithMany(p => p.PostTags)
                    .HasForeignKey(pt => pt.PostId)
                    .OnDelete(DeleteBehavior.Restrict),
                j =>
                {
                    j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                    j.HasKey(t => new { t.PostId, t.TagId });
                });
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<Tag> Tags { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<Post> Posts { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

Sources:

https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration

https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0

This does require you to remove the many to many relationship yourself or you will receive the following error when you remove a parent entity:

The association between entity types '' and '' has been severed, but the relationship is either marked as required or is implicitly required because the foreign key is not nullable. If the dependent/child entity should be deleted when a required relationship is severed, configure the relationship to use cascade deletes. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values

You can solve this by using DeleteBehavior.ClientCascade instead which will allow EF to perform cascade deletes on loaded entities.

internal class MyContext : DbContext
{
    public MyContext(DbContextOptions<MyContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasMany(p => p.Tags)
            .WithMany(p => p.Posts)
            .UsingEntity<PostTag>(
                j => j
                    .HasOne(pt => pt.Tag)
                    .WithMany(t => t.PostTags)
                    .HasForeignKey(pt => pt.TagId)
                    .OnDelete(DeleteBehavior.Cascade),
                j => j
                    .HasOne(pt => pt.Post)
                    .WithMany(p => p.PostTags)
                    .HasForeignKey(pt => pt.PostId)
                    .OnDelete(DeleteBehavior.ClientCascade),
                j =>
                {
                    j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
                    j.HasKey(t => new { t.PostId, t.TagId });
                });
    }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public ICollection<Tag> Tags { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public ICollection<Post> Posts { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public DateTime PublicationDate { get; set; }

    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.deletebehavior?view=efcore-5.0

Ogglas
  • 62,132
  • 37
  • 328
  • 418
0

None of the aforementioned solutions worked for me. What I had to do was use a nullable int (int?) on the foreign key that was not required (or not a not null column key) and then delete some of my migrations.

Start by deleting the migrations, then try the nullable int.

Problem was both a modification and model design. No code change was necessary.

Don P
  • 519
  • 6
  • 12
0

The simple way is to, Edit your migration file (cascadeDelete: true) into (cascadeDelete: false) then after assign the Update-Database command in your Package Manager Console.if it's problem with your last migration then all right. Otherwise check your earlier migration history, copy those things, paste into your last migration file, after that do it the same thing. it perfectly works for me.

0

You could add this in your DataContext.cs, this works for me...

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
    modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
FelixAVeras
  • 964
  • 9
  • 17
-3

I ran into the same problem and stuck for a long. The following steps saved me. Go through the constraints and change the onDelete ReferentialAction to NoAction from Cascade

  constraints: table =>
  {
      table.PrimaryKey("PK_table1", x => x.Id);
      table.ForeignKey(
         name: "FK_table1_table2_table2Id",
         column: x => x.table2Id,
         principalTable: "table2",
         principalColumn: "Id",
         onDelete: ReferentialAction.NoAction);
  });