Contrainte de clé étrangère peut provoquer des cycles ou plusieurs chemins en cascade?

, j'ai un problème lorsque j'essaie d'ajouter des contraintes à mes tables. Je reçois l'erreur:

L'introduction de la contrainte de clé étrangère 'FK74988DB24B3C886' sur la table 'Employee' peut provoquer des cycles ou plusieurs chemins en cascade. Spécifiez sur Supprimer aucune ACTION ou sur Mettre à jour aucune ACTION, ou modifiez d'autres contraintes de clé étrangère.

Ma contrainte est comprise entre une table Code et une table employee. Le Code table contient Id, Name, FriendlyName, Type et un Value. Le employee a un certain nombre de champs que les codes de référence, de sorte qu'il peut être une référence pour chaque type de code.

J'ai besoin que les champs soient définis sur null si le code référencé est supprimé.

Des idées comment je peux faire cela?

151
demandé sur Ricardo Altamirano 2009-05-12 11:47:58

9 réponses

SQL Server fait un simple comptage des chemins en cascade et, plutôt que d'essayer de déterminer si des cycles existent réellement, il suppose le pire et refuse de créer les actions référentielles (CASCADE): vous pouvez et devez toujours créer les contraintes sans les actions référentielles. Si vous ne pouvez pas modifier votre conception (ou si cela compromettrait les choses), vous devriez envisager d'utiliser des déclencheurs en dernier recours.

FWIW la résolution des chemins en cascade est un problème complexe. D'autres produits SQL seront ignorez simplement le problème et permettez-vous de créer des cycles, auquel cas ce sera une course pour voir qui écrasera la valeur en dernier, probablement à l'ignorance du concepteur (par exemple ACE/Jet fait cela). Je comprends que certains produits SQL tenteront de résoudre des cas simples. Le fait demeure, SQL Server n'essaie même pas, le joue ultra sûr en interdisant plus d'un chemin et au moins il vous le dit.

155
répondu onedaywhen 2009-05-12 10:53:57

Une situation typique avec plusieurs chemins en cascade sera la suivante: Une table principale avec deux détails, disons "Master" et "Detail1" et "Detail2". Les deux détails sont cascade supprimer. Jusqu'ici pas de problèmes. Mais que se passe-t-il si les deux détails ont une relation un à plusieurs avec une autre table (disons "SomeOtherTable"). SomeOtherTable a une Detail1ID-column et une Detail2ID-column.

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

En d'autres termes: certains des enregistrements de SomeOtherTable sont liés à Detail1-records et certains des enregistrements de SomeOtherTable sont liés aux enregistrements Detail2. Même s'il est garanti que SomeOtherTable-records n'appartient jamais aux deux détails, il est maintenant impossible de faire supprimer en cascade les enregistrements de SomeOhterTable pour les deux détails, car il existe plusieurs chemins en cascade de Master à SomeOtherTable (un Via Detail1 et un Via Detail2). Maintenant, vous pouvez déjà avoir compris cela. Voici une solution possible:

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

Tous les champs ID sont des champs clés et auto-incrémentation. Le point crucial réside dans le Champs DetailMainId des tables de détail. Ces champs sont à la fois la contrainte clé et la contrainte référentielle. Il est maintenant possible de tout supprimer en cascade en supprimant uniquement les enregistrements maîtres. L'inconvénient est que pour chaque enregistrement detail1 et pour chaque enregistrement detail2, il doit également y avoir un enregistrement DetailMain (qui est en fait créé en premier pour obtenir l'id correct et unique).

81
répondu hans riesebos 2012-03-27 12:23:24

Je voudrais souligner que (fonctionnellement) il y a une grande différence entre les cycles et/ou plusieurs chemins dans le schéma et les données. Alors que les cycles et peut-être les trajets multiples dans les données pourraient certainement compliquer le traitement et causer des problèmes de performance (coût de la manipulation "correctement"), le coût de ces caractéristiques dans le schéma devrait être proche de zéro.

Puisque la plupart des cycles apparents dans les Bdr se produisent dans des structures hiérarchiques (organigramme, partie,sous-partie, etc.) il est regrettable que SQL Le serveur suppose le pire; c'est-à-dire, cycle de schéma == cycle de données. En fait, si vous utilisez des contraintes RI, vous ne pouvez pas réellement construire un cycle dans les données!

Je soupçonne que le problème multipath est similaire; c'est-à-dire que plusieurs chemins dans le schéma n'impliquent pas nécessairement plusieurs chemins dans les données, mais j'ai moins d'expérience avec le problème multipath.

Bien sûr, si SQL Server autorisait les cycles, il serait toujours soumis à une profondeur de 32, mais c'est probablement suffisant pour la plupart des cas. (Trop mauvais ce n'est pas un paramètre de base de données, cependant!)

Les déclencheurs" au lieu de supprimer " ne fonctionnent pas non plus. La deuxième fois qu'une table est visitée, le déclencheur est ignoré. Donc, si vous voulez vraiment simuler une cascade, vous devrez utiliser des procédures stockées en présence de cycles. Le déclencheur à la place de Delete fonctionnerait cependant pour les cas multipath.

Celko suggère une "meilleure" façon de représenter les hiérarchies qui n'introduit pas de cycles, mais il y a des compromis.

11
répondu Bill Cohagan 2010-05-04 19:29:02

Il y a un article disponible dans lequel explique comment effectuer plusieurs chemins de suppression à l'aide de déclencheurs. Peut-être que cela est utile pour des scénarios complexes.

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

4
répondu Javier 2015-05-28 14:38:50

Par le son de celui-ci, vous avez une action OnDelete/OnUpdate sur l'une de vos clés étrangères existantes, qui modifiera votre table de codes.

Donc, en créant cette clé étrangère, vous créeriez un problème cyclique,

Par exemple, la mise à jour des employés entraîne la modification des Codes par une Action de mise à jour, entraîne la modification des employés par une Action de mise à jour... etc...

Si vous publiez vos définitions de Table pour les deux tables, et vos définitions de clé étrangère/contrainte, nous devrions être en mesure de dire - vous où est le problème...

2
répondu Eoin Campbell 2018-10-02 08:50:33

C'est parce que Emplyee pourrait avoir une Collection d'autres entités dites Qualifications et Qualification pourrait avoir d'autres universités de collection par exemple

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;}

}

Sur DataContext, cela pourrait être comme ci-dessous

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);

}

Dans ce cas, il y a une chaîne de L'employé à la Qualification et de la Qualification aux universités. Donc, il me lançait la même exception.

Ça a marché pour moi quand j'ai changé

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

À

    modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);
1
répondu RAJ 2013-12-16 17:11:01

Il s'agit d'une erreur de type database trigger policies. un déclencheur est du code et peut ajouter des intelligences ou des conditions à une relation en Cascade comme la suppression en Cascade. vous devrez peut-être spécialiser les options de tables connexes autour de cela comme Désactiver CascadeOnDelete :

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

Ou désactivez complètement cette fonction:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
0
répondu Amirhossein Mehrvarzi 2014-10-10 11:35:32

Trigger est une solution pour ce problème:

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
0
répondu Tone Škoda 2015-12-03 14:18:10

Ma solution à ce problème rencontré en utilisant ASP.NET Core 2.0 et EF Core 2.0 devaient effectuer les opérations suivantes dans l'ordre:

  1. Exécutez la commande update-database dans la console de gestion des paquets (PMC) pour créer la base de données (ceci entraîne la contrainte "introduction de clé étrangère ... peut provoquer des cycles ou plusieurs chemins en cascade."erreur)

  2. Exécutez la commande script-migration -Idempotent dans PMC pour créer un script qui peut être exécuté indépendamment des tables/contraintes existantes

  3. Prenez le script résultant et trouver ON DELETE CASCADE et remplacer par ON DELETE NO ACTION

  4. Exécutez le SQL modifié sur la base de données

Maintenant, vos migrations devraient être à jour et les suppressions en cascade ne devraient pas se produire.

Dommage que je n'ai pas pu trouver un moyen de le faire dans Entity Framework Core 2.0.

Bonne chance!

-2
répondu user1477388 2017-11-28 20:37:46