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

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

Представление ограничения FOREIGN KEY "FK74988DB24B3C886" в таблице "Сотрудник" может вызывать циклы или несколько каскадных путей. Укажите ON DELETE NO ACTION или ON UPDATE NO ACTION или измените другие ограничения FOREIGN KEY.

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

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

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

Ответ 1

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

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

Сами Microsoft советует использовать триггеры вместо ограничений FK.

Ответ 2

Типичная ситуация с несколькими каскамированными путями будет таковой: Мастер-таблица с двумя деталями, пусть говорят "Мастер" и "Детали1" и "Деталь2". Обе детали являются каскадным удалением. Пока нет проблем. Но что, если обе детали имеют отношение "один ко многим" с какой-либо другой таблицей (например, "SomeOtherTable" ). SomeOtherTable имеет столбец Detail1ID и столбец Detail2ID.

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

Другими словами: некоторые записи в SomeOtherTable связаны с документами Detail1, а некоторые записи в 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-record AND для каждой записи detail2 также должна быть запись DetailMain-запись (которая фактически создается сначала, чтобы получить правильный и уникальный идентификатор).

Ответ 3

Я бы отметил, что (функционально) существует БОЛЬШАЯ разница между циклами и/или несколькими путями в SCHEMA и DATA. В то время как циклы и, возможно, многолучевые передачи в DATA, безусловно, могут усложнить обработку и вызвать проблемы с производительностью (стоимость "правильной" обработки), стоимость этих характеристик в схеме должна быть близка к нулю.

Так как наиболее очевидные циклы в RDB происходят в иерархических структурах (org chart, part, subpart и т.д.), то, к сожалению, SQL Server принимает худшее; т.е. цикл цикла цикла ==. На самом деле, если вы используете ограничения RI, вы не можете построить цикл в данных!

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

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

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

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

Ответ 5

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

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

Например, Обновление сотрудников вызывает изменение кодов с помощью действия при обновлении, изменение сотрудников с помощью действия при обновлении... и т. Д...

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

Ответ 6

Это потому, что у 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);

}

в этом случае есть цепочка от Employee to Qualification и от квалификации до университетов. Так что это бросало мне такое же исключение.

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

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

To

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

Ответ 7

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

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

Ответ 8

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

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

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

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

Ответ 9

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

  • Запустите команду update-database в консоли управления пакетами (PMC) для создания базы данных (это приводит к тому, что сообщение "Введение ограничения FOREIGN KEY... может вызвать ошибки цикла или несколько каскадных путей" ).

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

  • Возьмите полученный script и найдите ON DELETE CASCADE и замените на ON DELETE NO ACTION

  • Выполнить измененный SQL в базе данных

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

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

Удачи!