简体   繁体   中英

SQL may cause cycles or multiple cascade paths

I edited the whole question because i managed to find what triggers the error so i just leave the SQL approach since it's easy to test

may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

DROP TABLE dbo.ProductionUnits
CREATE TABLE ProductionUnits
(
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(50)
)

DROP TABLE Cells
CREATE TABLE Cells
(
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(10),
    ProductionUnitId INT FOREIGN KEY REFERENCES ProductionUnits(Id) ON DELETE CASCADE ON UPDATE CASCADE 
)

DROP TABLE CheckLists
CREATE TABLE CheckLists
(
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(20),
    CellId INT FOREIGN KEY REFERENCES Cells(Id) ON DELETE CASCADE ON UPDATE CASCADE
)

DROP TABLE CheckListGroups
CREATE TABLE CheckListGroups
(
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(20),
    CheckListId INT FOREIGN KEY REFERENCES CheckLists(Id) ON DELETE CASCADE ON UPDATE CASCADE
)

DROP TABLE CheckListGroupItems
CREATE TABLE CheckListGroupItems
(
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(20),
    CheckListGroupId INT FOREIGN KEY REFERENCES CheckListGroups(Id) ON DELETE CASCADE ON UPDATE CASCADE
)


DROP TABLE Shifts
CREATE TABLE Shifts
(
    Id INT PRIMARY KEY IDENTITY,
    StartTime TIME,
    EndTime TIME,
    ShiftDescription NVARCHAR(20),
    ProductionUnitId INT FOREIGN KEY REFERENCES ProductionUnits(Id) ON DELETE CASCADE ON UPDATE CASCADE
)

DROP TABLE ProductionRecords
CREATE TABLE ProductionRecords
(
    Id INT PRIMARY KEY IDENTITY,
    CreatedOn DATETIME,
    CreatedBy NVARCHAR(50),
    ModifiedOn DATETIME,
    ModifiedBy NVARCHAR(50)
)

DROP TABLE dbo.Referencias
CREATE TABLE Referencias
(
    Id INT PRIMARY KEY IDENTITY,
    Name NVARCHAR(15),
)

DROP TABLE dbo.CheckListRecords
CREATE TABLE CheckListRecords
(
    Id INT PRIMARY KEY IDENTITY,
    Value NVARCHAR(3),
    ReferenciaId INT FOREIGN KEY REFERENCES Referencias(Id) ON DELETE CASCADE ON UPDATE CASCADE,
    ShiftId INT FOREIGN KEY REFERENCES Shifts(Id) ON DELETE CASCADE ON UPDATE CASCADE,
    CheckListGroupItemId INT FOREIGN KEY REFERENCES dbo.CheckListGroupItems(Id) ON DELETE CASCADE ON UPDATE CASCADE,
    ProductionRecordsId INT FOREIGN KEY REFERENCES ProductionRecords(Id) ON DELETE CASCADE ON UPDATE CASCADE 
)   

So the problem is when I add

CheckListGroupItemId INT FOREIGN KEY REFERENCES dbo.CheckListGroupItems(Id) ON DELETE CASCADE ON UPDATE CASCADE,

But i need this to know which item belongs to but this works if i delete the ON DELETE CASCADE

I'm not very experienced to SQL when it comes to foreign keys and handling cascade paths-

What should I do in this situation?

There are two paths to deleting CheckListRecords when you delete a ProductionUnits row. This is what it's complaining about.

ProductionUnits ->
 Cells ->
  CheckLists ->
   CheckListGroups ->
    CheckListGroupItems ->
     CheckListRecords

...and...

ProductionUnits ->
 Shifts ->
  CheckListRecords

Bummer - you can't have that...an there's no real simple way to get around it :-(

It's a common occurrence. But, have hope - there's a reasonable way to deal with it...that isn't too awful. When you get a message like that, work your way backwards from the table it's griping about...and when you find multiple ON DELETE CASCADE columns in the delete path that point to the same table, you've found your culprit.

Before I suggest a solution, a couple of observations: for one thing, you've got Ids that are identity-generated. So, On Update Cascade probably shouldn't be specified in such a world because the IDs can't readily change (unless you're behaving very badly somewhere).

Another minor best-practice kinda note: specify the schema when creating the tables:

CREATE TABLE dbo.ProductionUnits
(
   -- etc.
)

...and always use the schema when referring to the table in views and procedures.

Okay - so how to deal with the conflict? What you might consider doing is removing the foreign key from CheckListRecords to Shifts and instead, implement a delete trigger on Shifts that deletes the affected CheckListRecords rows. A long time ago...before there was declarative referential integrity, you had to deal with managing the relationships with triggers. It still comes in handy in places like this.

Assuming you've added the schema to the table declarations, it would be something roughly like:

create trigger [Shifts.Delete.Trigger] on dbo.Shifts for delete as
begin
  set nocount on;
  delete dbo.CheckListRecords where ShiftId in ( select Id from deleted );
end

See, that wasn't so bad, eh?

If you just have to update Shifts.Id for some reason, it gets a bit more complicated. You would have to have an alternate key on the Shifts table in order to implement the logic...preferably a column that you never update or at least one that is not updated when the Id gets changed. Maybe the StartTime column? You could add a uniqueidentifier column if you don't have a candidate key. For example:

CREATE TABLE Shifts
(
  Id INT PRIMARY KEY IDENTITY,
  StartTime TIME,
  EndTime TIME,
  ShiftDescription NVARCHAR(20) ,
  ProductionUnitId INT FOREIGN KEY REFERENCES ProductionUnits(Id) ON DELETE CASCADE ON UPDATE CASCADE,
  RowId UNIQUEIDENTIFIER 
    CONSTRAINT [Shifts.RowId.Default] DEFAULT ( NEWID() )
    CONSTRAINT [Shifts.RowId.Unique] UNIQUE
)

Most of your views and logic would just completely ignore this column. You don't even need to let EF see it. With how I declared it, it gets set automatically on insert (similar to identity columns).

The only code that uses RowId is the update trigger:

create trigger [Shifts.Update.Trigger] on dbo.Shifts for update as
begin
  set nocount on;
  if update( Id ) --> just skip everything if the Id did not change
  begin
    update dbo.CheckListRecords
    set ShiftId = i.Id
    from
      inserted i
      inner join
      deleted d
      on
        i.RowId = d.RowId
      inner join
      dbo.CheckListRecords c
      on
        c.ShiftId = d.Id        
  end
end

It's not super horrible...but not ideal. Really, if you don't have any reason to update the ID columns (and you really really shouldn't), you can skip all of this update trigger and extra column nonsense.

I think, if you have trigger-based referential integrity, you have to tell EF about it...but EF is not the way of my people...so I can't help with that particular point. But I'm almost certain it's a fairly simple declaration when you describe your data model to EF.

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