Ограничение внешнего ключа может вызвать циклы или несколько каскадных путей?
у меня есть проблема, когда я пытаюсь добавить ограничения к своим таблицам. Я получаю ошибку:
введение ограничения внешнего ключа 'FK74988DB24B3C886' в таблице 'Employee' может вызвать циклы или несколько каскадных путей. Укажите параметр Удалить без действия или обновить без действия, а также измените другие ограничения внешнего ключа.
мое ограничение находится между Code и employee таблица. Элемент Code таблица содержит Id,Name,FriendlyName,Type и a Value. Элемент employee имеет ряд полей, которые ссылаются на коды, так что может быть ссылка для каждого типа кода.
мне нужно, чтобы поля были установлены в null, если код, на который ссылается, удален.
любые идеи, как я могу это сделать?
9 ответов:
SQL Server делает простой подсчет каскадных путей и вместо того, чтобы пытаться выяснить, действительно ли существуют какие-либо циклы, он предполагает худшее и отказывается создавать ссылочные действия (каскад): вы можете и должны по-прежнему создавать ограничения без ссылочных действий. Если вы не можете изменить свой дизайн (или сделать это будет компрометировать вещи), то вы должны рассмотреть возможность использования триггеров в качестве последнего средства.
fwiw разрешение каскадных путей является сложной проблемой. Другие SQL продукты просто проигнорируют проблему и позволят вам создавать циклы, и в этом случае это будет гонка, чтобы увидеть, что будет перезаписывать значение последним, вероятно, до незнания дизайнера (например, ACE/Jet делает это). Я понимаю, что некоторые продукты SQL будут пытаться решить простые случаи. Факт остается фактом, SQL Server даже не пытается, играет его ультра безопасно, запрещая более одного пути, и по крайней мере он говорит вам об этом.
типичная ситуация с несколькими каскадными путями будет такой: Мастер-таблица с двумя деталями, скажем " 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 и некоторые из записи в некоторой другой таблице связаны с записями 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 предлагает "лучший" способ представления иерархий, который не вводит циклы, но есть компромиссный.
есть статья, в которой объясняется, как выполнить несколько путей удаления с помощью триггеров. Возможно, это полезно для сложных сценариев.
по звукам у вас есть действие 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);
это ошибка типа политики триггера базы данных. триггер является кодом и может добавить некоторые интеллекты или условия к каскадному отношению, такому как каскадное удаление. возможно, вам придется специализироваться на связанных опциях таблиц вокруг этого, как отключение 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 должны были выполнить следующее по порядку:
выполнить
update-databaseкоманда в консоли управления пакетами (PMC) для создания базы данных (это приводит к "введению ограничения внешнего ключа ... может вызвать циклы или несколько каскадных путей."ошибка)выполнить
script-migration -Idempotentкоманда в PMC для создания сценария, который может быть запущен независимо от существующего таблицы / ограничениявозьмите полученный скрипт и найдите
ON DELETE CASCADEи заменить наON DELETE NO ACTIONвыполнить измененный SQL против базы данных
теперь ваши миграции должны быть актуальными, и каскадные удаления не должны происходить.
жаль, что я не смог найти способ сделать это в Entity Framework Core 2.0.
удачи!
Comments