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

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

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

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

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

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

11 ответов

Решение

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

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

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

Типичная ситуация с несколькими каскадными путями будет такой: главная таблица с двумя деталями, скажем, "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, а некоторые записи в 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 (которая фактически создается первой, чтобы получить правильный и уникальный идентификатор).

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

Поскольку наиболее очевидные циклы в RDB происходят в иерархических структурах (оргструктура, деталь, подраздел и т. Д.), К сожалению, 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 для одного из ваших существующих внешних ключей, которое изменит вашу таблицу кодов.

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

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

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

Это связано с тем, что 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);

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

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

Некоторые базы данных, в первую очередь SQL Server, имеют ограничения на каскадное поведение, образующее циклы. Есть два способа справиться с этой ситуацией:1. Измените одно или несколько отношений, чтобы не удалять каскадное удаление.2. Настройте базу данных без одного или нескольких таких каскадных удалений, а затем убедитесь, что все зависимые сущности загружены, чтобы EF Core мог выполнять каскадное поведение. пожалуйста, обратитесь к этой ссылке:
Ограничения каскада базы данных

Массовое обновление базы данных для смещения PK: вместо этого сделайте копию базы данных.

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

Одним из решений могло быть определение внешних ключей как ON UPDATE CASCADE и смещение первичных ключей, за которыми следуют внешние ключи. Но если вы это сделаете, возникнет много препятствий (Msg 1785, Msg 8102, ...).

Поэтому лучшая идея, которая приходит мне в голову, - просто сделать копию базы данных, DROP и повторно СОЗДАТЬ таблицы, которые должны иметь смещение PK|FK, и скопировать данные (и при этом смещение первичных ключей и внешних ключи).

Избегайте лишних хлопот.

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

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

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

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

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

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

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

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

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

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

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

Удачи!

Другие вопросы по тегам