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

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

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

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

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

Есть идеи, как мне это сделать?

Ответы [ 9 ]

167 голосов
/ 12 мая 2009

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

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

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

85 голосов
/ 23 августа 2010

Типичная ситуация с несколькими каскадными путями будет такой: Мастер-таблица с двумя деталями, скажем, «Мастер» и «Деталь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 }

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

12 голосов
/ 04 мая 2010

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

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

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

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

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

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

5 голосов
/ 28 мая 2015

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

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

3 голосов
/ 12 мая 2009

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

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

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

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

1 голос
/ 03 декабря 2015

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

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
1 голос
/ 16 декабря 2013

Это потому, что у 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);
0 голосов
/ 28 ноября 2017

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

  1. Выполнить команду update-database в консоли управления пакетами (PMC) для создания базы данных (это приводит к ошибке «Введение ограничения FOREIGN KEY ... может вызвать циклы или несколько путей каскада.» 1007 *

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

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

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

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

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

Удачи!

0 голосов
/ 10 октября 2014

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

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

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

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...