Ограничение внешнего ключа может вызвать циклы или несколько каскадных путей?

у меня проблема, когда я пытаюсь добавить ограничения в свои таблицы. Я получаю сообщение об ошибке:

введение ограничения внешнего ключа "FK74988DB24B3C886" в таблице "сотрудник" может вызвать циклы или несколько каскадных путей. Укажите на Удалить нет действия или на обновить нет действия или изменить другие ограничения внешнего ключа.

мои ограничения между Code и employee таблица. The Code таблица содержит Id, Name, FriendlyName, Type и a Value. The employee имеет ряд полей, которые ссылаются на коды, так что для каждого типа кода может быть ссылка.

мне нужно, чтобы поля были установлены в null, если код, на который ссылаются, удален.

любые идеи, как я могу это сделать?

9 ответов


SQL Server делает простой подсчет каскадных путей и вместо того, чтобы пытаться выяснить, существуют ли какие-либо циклы, он предполагает худшее и отказывается создавать ссылочные действия (каскад): вы можете и должны создавать ограничения без ссылочных действий. Если вы не можете изменить свой дизайн (или это поставит под угрозу вещи), то вы должны рассмотреть использование триггеров в качестве последнего средства.

fwiw разрешение каскадных путей является сложной проблемой. Другой SQL продукты просто проигнорируют проблему и позволят вам создать циклы, в этом случае это будет гонка, чтобы увидеть, что перепишет значение последним, вероятно, к незнанию дизайнера (например, ACE/Jet делает это). Я понимаю, что некоторые продукты SQL попытаются разрешить простые случаи. Факт остается фактом, SQL Server даже не пытается, играет ультра безопасно, запрещая более одного пути, и, по крайней мере, он говорит вам об этом.


типичная ситуация с несколькими дорожками cascasing будет этого: Мастер-таблица с двумя деталями, скажем, "Master" и "Detail1"и " Detail2". Обе детали-каскадное удаление. Пока никаких проблем. Но что, если обе детали имеют отношение "один ко многим"с какой-то другой таблицей (скажем, "SomeOtherTable"). SomeOtherTable имеет столбец Detail1ID и столбец Detail2ID.

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

другими словами: некоторые записи в SomeOtherTable связаны с Detail1-records и некоторыми из записи в SomeOtherTable связаны с записями Detail2. Даже если гарантируется, что записи SomeOtherTable никогда не принадлежат обеим деталям, теперь невозможно удалить каскад записей SomeOhterTable для обеих деталей, потому что существует несколько каскадных путей от Master к SomeOtherTable (один через Detail1 и один через Detail2). Возможно, вы уже поняли это. Вот возможное решение:

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

все ID поля ключевые поля и автоинкремент. Затруднение лежит в полях DetailMainId таблиц Detail. Эти поля являются как ключевыми, так и ссылочными. Теперь можно каскадно удалить все, только удалив мастер-записи. Недостатком является то, что для каждой записи detail1 и для каждой записи detail2 также должна быть запись DetailMain (которая фактически создается первой, чтобы получить правильный и уникальный идентификатор).


Я бы указал, что (функционально) существует большая разница между циклами и/или несколькими путями в схеме и данных. Хотя циклы и, возможно, многолучевости в данных, безусловно, могут усложнить обработку и вызвать проблемы с производительностью (стоимость "правильной" обработки), стоимость этих характеристик в схеме должна быть близка к нулю.

поскольку большинство очевидных циклов в RDBs происходят в иерархических структурах (орг-диаграмма, часть, подраздел и т. д.) к сожалению этот SQL Server предполагает худшее; т. е. цикл схемы = = цикл данных. Фактически, если вы используете ограничения RI, вы не можете построить цикл в данных!

Я подозреваю, что многолучевая проблема аналогична; т. е. несколько путей в схеме не обязательно подразумевают несколько путей в данных, но у меня меньше опыта работы с многолучевой проблемой.

конечно, если SQL Server сделал разрешить циклы он все равно будет подвержен глубине 32, но это, вероятно достаточное для большинства случаев. (Жаль, что это не настройка базы данных, однако!)

"вместо удаления" триггеры тоже не работают. При втором посещении таблицы триггер игнорируется. Итак, если вы действительно хотите имитировать каскад, вам придется использовать хранимые процедуры при наличии циклов. Однако триггер вместо удаления будет работать для многолучевых случаев.

Celko предлагает "лучший" способ представления иерархий, который не вводит циклы, но есть компромиссный.


есть статья, в которой объясняется, как выполнить несколько путей удаления с помощью триггеров. Возможно, это полезно для сложных сценариев.

http://www.mssqltips.com/sqlservertip/2733/solving-the-sql-server-multiple-cascade-path-issue-with-a-trigger/


по звукам у вас есть действие OnDelete/OnUpdate на одном из ваших существующих внешних ключей, которое изменит вашу таблицу кодов.

таким образом, создавая этот внешний ключ, вы создадите циклическую проблему,

например. Обновление сотрудников, приводит к изменению кодов действием On Update, приводит к изменению сотрудников действием On Update... так далее...

Если вы публикуете определения таблиц для обеих таблиц и определения внешнего ключа / ограничений, мы должен быть в состоянии сказать вам, в чем проблема...


это потому, что Emplyee может иметь коллекцию других сущностей, скажем, квалификация и квалификация могут иметь некоторые другие университеты коллекции например,

public class Employee{
public virtual ICollection<Qualification> Qualifications {get;set;}

}

public class Qualification{

public Employee Employee {get;set;}

public virtual ICollection<University> Universities {get;set;}

}

public class University{

public Qualification Qualification {get;set;}

}

на DataContext это может быть как ниже

protected override void OnModelCreating(DbModelBuilder modelBuilder){

modelBuilder.Entity<Qualification>().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications);
modelBuilder.Entity<University>.HasRequired(x => x.Qualification).WithMany(e => e.Universities);

}

в этом случае существует цепочка от сотрудника до квалификации и от квалификации до университетов. Таким образом, он бросал то же исключение мне.

это сработало для меня, когда я изменил

    modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications); 

до

    modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);

это ошибка типа политики триггера базы данных. триггер-это код и может добавить некоторые интеллекты или условия в каскадное отношение, такое как каскадное удаление. вам может потребоваться специализировать связанные параметры таблиц вокруг этого, как отключение CascadeOnDelete:

protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
    modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false);
}

или полностью отключить эту функцию:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

триггер является решением этой проблемы:

IF OBJECT_ID('dbo.fktest2', 'U') IS NOT NULL
    drop table fktest2
IF OBJECT_ID('dbo.fktest1', 'U') IS NOT NULL
    drop table fktest1
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'fkTest1Trigger' AND type = 'TR')
    DROP TRIGGER dbo.fkTest1Trigger
go
create table fktest1 (id int primary key, anQId int identity)
go  
    create table fktest2 (id1 int, id2 int, anQId int identity,
        FOREIGN KEY (id1) REFERENCES fktest1 (id)
            ON DELETE CASCADE
            ON UPDATE CASCADE/*,    
        FOREIGN KEY (id2) REFERENCES fktest1 (id) this causes compile error so we have to use triggers
            ON DELETE CASCADE
            ON UPDATE CASCADE*/ 
            )
go

CREATE TRIGGER fkTest1Trigger
ON fkTest1
AFTER INSERT, UPDATE, DELETE
AS
    if @@ROWCOUNT = 0
        return
    set nocount on

    -- This code is replacement for foreign key cascade (auto update of field in destination table when its referenced primary key in source table changes.
    -- Compiler complains only when you use multiple cascased. It throws this compile error:
    -- Rrigger Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, 
    -- or modify other FOREIGN KEY constraints.
    IF ((UPDATE (id) and exists(select 1 from fktest1 A join deleted B on B.anqid = A.anqid where B.id <> A.id)))
    begin       
        update fktest2 set id2 = i.id
            from deleted d
            join fktest2 on d.id = fktest2.id2
            join inserted i on i.anqid = d.anqid        
    end         
    if exists (select 1 from deleted)       
        DELETE one FROM fktest2 one LEFT JOIN fktest1 two ON two.id = one.id2 where two.id is null -- drop all from dest table which are not in source table
GO

insert into fktest1 (id) values (1)
insert into fktest1 (id) values (2)
insert into fktest1 (id) values (3)

insert into fktest2 (id1, id2) values (1,1)
insert into fktest2 (id1, id2) values (2,2)
insert into fktest2 (id1, id2) values (1,3)

select * from fktest1
select * from fktest2

update fktest1 set id=11 where id=1
update fktest1 set id=22 where id=2
update fktest1 set id=33 where id=3
delete from fktest1 where id > 22

select * from fktest1
select * from fktest2

мое решение этой проблемы, возникшей с использованием ASP.NET Core 2.0 и EF Core 2.0 должны были выполнить следующее по порядку:

  1. Run update-database команда в консоли управления пакетами (PMC) для создания базы данных (это приводит к "введению ограничения внешнего ключа ... может вызывать циклы или несколько каскадных путей."об ошибках)

  2. Run script-migration -Idempotent команда в PMC для создания сценария, который может быть запущен независимо от существующего таблицы / ограничения

  3. возьмите полученный скрипт и найдите ON DELETE CASCADE и заменить на ON DELETE NO ACTION

  4. выполнить измененный SQL для базы данных

теперь ваши миграции должны быть актуальными, и каскадные удаления не должны происходить.

жаль, что я не смог найти способ сделать это в Entity Framework Core 2.0.

удачи!