EF Core DeleteBehavior.Cascade may cause cycles or multiple cascade paths

0

I have the following entities (which are reduced for brevity);

public class Trader : AuditableEntity
{
    public int? UserId { get; set; }
    public ApplicationUser User { get; set; }

    public int? AccountManagerId { get; set; }
    public ApplicationUser AccountManager { get; set; }

    public List<TraderContactHistory> ContactHistory { get; set; }
}

public class ApplicationUser : IdentityUser<int>, IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Level { get; set; }

    public DateTime JoinDate { get; set; }
    public DateTime? LastLogin { get; set; }

    public ApplicationUserImage ProfileImage { get; set; }
}

Just for completeness the TraderContactHistory is as follows;

public class TraderContactHistory : Entity
{
    public Trader Trader { get; set; }
    public DateTime ContactDate { get; set; }
    public ApplicationUser ContactedBy { get; set; }
    public bool RequestedCallbackLater { get; set; }
    public string Notes { get; set; }
}

Now after adding changing the constraint;

public override void Configure(EntityTypeBuilder<Trader> entity)
{
    entity.HasOne(x => x.User)
        .WithOne()
        .HasForeignKey<Trader>(x => x.UserId)
        .OnDelete(DeleteBehavior.Restrict);
}

to the following (as we have the requirement now to clear the related user when the Trader is deleted);

public override void Configure(EntityTypeBuilder<Trader> entity)
{
    entity.HasOne(x => x.User)
        .WithOne()
        .HasForeignKey<Trader>(x => x.UserId)
        .OnDelete(DeleteBehavior.Cascade);

    entity.HasOne(x => x.AccountManager)
        .WithMany()
        .HasForeignKey(x => x.AccountManagerId)
        .OnDelete(DeleteBehavior.SetNull);
}

and for completeness the TraderContactHistory configuration is;

    public override void Configure(EntityTypeBuilder<TraderContactHistory> entity)
    {
        entity.HasKey(x => x.Id);

        entity.HasOne(x => x.Trader)
            .WithMany(x => x.ContactHistory)
            .OnDelete(DeleteBehavior.Restrict);

        entity.HasOne(x => x.ContactedBy)
            .WithMany()
            .OnDelete(DeleteBehavior.Restrict);

    }

Generates the following Migration;

    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropForeignKey(
            name: "FK_Traders_AspNetUsers_UserId",
            schema: "Bemfeito",
            table: "Traders");

        migrationBuilder.AddForeignKey(
            name: "FK_Traders_AspNetUsers_UserId",
            schema: "Bemfeito",
            table: "Traders",
            column: "UserId",
            principalSchema: "Bemfeito",
            principalTable: "AspNetUsers",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    }

which is causing the following error when I run update-database (I am running to a MSSQL database);

System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Traders_AspNetUsers_UserId' on table 'Traders' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

Can anyone see why this is being generated? Could it be the TraderContactHistory linked to the Trader that is silently causing this? As I currently have no configuration set to it?

entity-framework
entity-framework-core
asked on Stack Overflow Oct 7, 2019 by Matthew Flynn • edited Oct 7, 2019 by Matthew Flynn

1 Answer

1

The foreign keys relationships in your data model look like this:

  • Trader ->* ApplicationUser
  • Trader ->* ApplicationUser
  • TraderContactHistory -> Trader
  • TraderContactHistory -> ApplicationUser

So if you delete an ApplicationUser entry, a cascade delete will delete all Trader entries and all TraderContactHistory entries pointing to it. But deleting a Trader entry will in turn also delete depending TraderContactHistory items. So you have two cascade delete paths from ApplicationUser to TraderContactHistory:

  • TraderContactHistory -> ApplicationUser
  • TraderContactHistroy -> Trader -> ApplicationUser

and MSSQL doesn't like that. I know how annoying this can be, believe me, as I have to deal with this limitation on a regular basis.

That being said, you specified "Restrict" in the relationships of TraderContactHistory, which should solve the problem. Maybe the actual schema doesn't reflect that correctly?

Edit:
It just occurred to me that the other direct Trader-ApplicationUser-relationship might be the culprit. If the Trader-AccountManager-dependency is set up as OnDelete Cascade, you also get multiple cascade paths.

answered on Stack Overflow Oct 7, 2019 by Sentry • edited Oct 7, 2019 by Sentry

User contributions licensed under CC BY-SA 3.0