203 votes

Une contrainte de clé étrangère peut provoquer des cycles ou des cascades multiples ?

Je rencontre un problème lorsque j'essaie d'ajouter des contraintes à mes tables. J'obtiens l'erreur suivante :

L'introduction de la contrainte FOREIGN KEY 'FK74988DB24B3C886' sur la table 'Employee' peut provoquer des cycles ou des cascades multiples. Spécifiez ON DELETE NO ACTION ou ON UPDATE NO ACTION, ou modifiez les autres contraintes FOREIGN KEY.

Ma contrainte est entre un Code table et un employee table. Le site Code Le tableau contient Id , Name , FriendlyName , Type et un Value . Le site employee possède un certain nombre de champs qui font référence aux codes, de sorte qu'il peut y avoir une référence pour chaque type de code.

J'ai besoin que les champs soient mis à zéro si le code référencé est supprimé.

Avez-vous une idée de la façon dont je peux faire cela ?

0 votes

Une des solutions est aquí

206voto

onedaywhen Points 24594

SQL Server effectue un simple comptage des chemins de 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 risque de compromettre les choses), vous devriez envisager d'utiliser les déclencheurs en dernier recours.

Pour information, la résolution des cascades est un problème complexe. D'autres produits SQL vont simplement ignorer le problème et vous permettre de créer des cycles, auquel cas ce sera une course pour voir lequel écrasera la valeur en dernier, probablement à l'ignorance du concepteur (par exemple, ACE/Jet fait cela). Je comprends que certains produits SQL tentent de résoudre les cas simples. Le fait est que SQL Server n'essaie même pas, il joue la carte de la sécurité en interdisant plus d'un chemin d'accès et au moins il vous le dit.

0 votes

J'en suis arrivé à la même conclusion et j'ai fini par résoudre le problème en utilisant des déclencheurs. Je suis content de savoir qu'il existe une différence entre les SGBD et que c'est en fait une décision de conception sur laquelle je suis bloqué. Merci beaucoup :)

6 votes

Une chose que je n'arrive toujours pas à comprendre est que, si ce "problème" peut être résolu en utilisant un trigger, alors comment se fait-il qu'un trigger ne va pas "causer des cycles ou des chemins multiples en cascade ..." ? ?

8 votes

@armen : parce que votre déclencheur fournira explicitement la logique que le système ne pourrait pas comprendre implicitement par lui-même. Par exemple, s'il y a plusieurs chemins pour une action référentielle de suppression, votre code de déclencheur définira quelles tables sont supprimées et dans quel ordre.

121voto

hans riesebos Points 401

Une situation typique avec plusieurs chemins de cascades sera la suivante : Une table maître avec deux détails, disons "Maître" et "Détail1" et "Détail2". Les deux détails sont supprimés en cascade. Jusqu'à présent, aucun problème. Mais que se passe-t-il si les deux détails ont une relation un-à-un avec une autre table (disons "SomeOtherTable"). SomeOtherTable a une colonne Detail1ID ET une colonne Detail2ID.

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 aux enregistrements Detail1 et certains des enregistrements de SomeOtherTable sont liés aux enregistrements Detail2. Même s'il est garanti que les enregistrements de SomeOtherTable n'appartiennent jamais aux deux détails, il est maintenant impossible de faire en sorte que les enregistrements de SomeOhterTable soient supprimés en cascade pour les deux détails, parce qu'il y a plusieurs chemins en cascade de Master à SomeOtherTable (un via Detail1 et un via Detail2). Maintenant, vous avez peut-être déjà 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 d'identification sont des champs clés et s'incrémentent automatiquement. Le point crucial réside dans les champs DetailMainId des tables Detail. Ces champs sont à la fois des clés et des contraintes référentielles. Il est maintenant possible de tout supprimer en cascade en ne supprimant que les enregistrements principaux. 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).

1 votes

Votre commentaire m'a beaucoup aidé à comprendre le problème auquel je suis confronté. Merci ! Je préférerais désactiver la suppression en cascade pour l'un des chemins, puis gérer la suppression des autres enregistrements d'une autre manière (procédures stockées, triggers, par code, etc.). Mais je garde votre solution (regroupement dans un chemin) à l'esprit pour d'éventuelles applications différentes du même problème...

1 votes

Un bon point pour l'utilisation du mot crux (et aussi pour l'explication).

0 votes

Est-ce mieux que d'écrire des déclencheurs ? Il semble étrange d'ajouter une table supplémentaire juste pour que la cascade fonctionne.

15voto

Bill Cohagan Points 84

Je tiens à souligner que (fonctionnellement) il y a une GRANDE différence entre les cycles et/ou les chemins multiples dans le SCHEMA et les DONNÉES. Alors que les cycles et peut-être les chemins multiples dans les DONNÉES pourraient certainement compliquer le traitement et causer des problèmes de performance (coût du traitement "correct"), le coût de ces caractéristiques dans le schéma devrait être proche de zéro.

Étant donné que la plupart des cycles apparents dans les BDR se produisent dans les structures hiérarchiques (organigramme, partie, sous-partie, etc.), il est regrettable que SQL Server suppose le pire, c'est-à-dire que le cycle du schéma = = cycle des 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 des chemins multiples est similaire, c'est-à-dire que des chemins multiples dans le schéma n'impliquent pas nécessairement des chemins multiples dans les données, mais j'ai moins d'expérience avec le problème des chemins multiples.

Bien sûr, si le serveur SQL a fait permettre des cycles, il serait toujours soumis à une profondeur de 32, mais c'est probablement suffisant pour la plupart des cas. (Dommage que ce ne soit pas un paramètre de base de données cependant !)

Les déclencheurs "Instead of Delete" 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 devez utiliser des procédures stockées en présence de cycles. Le déclencheur "Instead-of-Delete" fonctionnerait cependant pour les cas de trajets multiples.

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

0 votes

"si vous utilisez des contraintes RI, vous ne pouvez pas réellement construire un cycle dans les données !" -- bon point !

0 votes

Bien sûr, vous pouvez créer une circularité des données, mais avec MSSQL, uniquement en utilisant UPDATE. D'autres RDBMs supportent les contraintes différées (l'intégrité est assurée au moment du commit, pas au moment de l'insertion/mise à jour/suppression).

4voto

Eoin Campbell Points 22861

Il semble que vous ayez une action OnDelete/OnUpdate sur l'une de vos clés étrangères existantes, qui va modifier votre table de codes.

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

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

Si vous publiez vos définitions de table pour les deux tables, ainsi que vos définitions de clés étrangères/constractions, nous devrions être en mesure de vous dire où se situe le problème...

1 votes

Ils sont assez longs, donc je ne pense pas pouvoir les poster ici, mais j'apprécierais beaucoup votre aide - je ne sais pas s'il y a un moyen de vous les envoyer ? Je vais essayer de le décrire : Les seules contraintes qui existent proviennent de 3 tables qui ont toutes des champs qui font référence à des codes par une simple clé d'identité INT. Le problème semble être que l'employé a plusieurs champs qui font référence à la table des codes et que je veux qu'ils se transforment tous en SET NULL. Tout ce dont j'ai besoin, c'est que lorsque les codes sont supprimés, les références à ces codes soient partout mises à zéro.

0 votes

Les poster quand même... Je pense que personne ici n'y verra d'inconvénient, et la fenêtre de code les mettra en forme correctement dans un bloc défilant :)

2voto

RAJ Points 261

En effet, Emplyee peut avoir une collection d'une autre entité, par exemple Qualifications, et Qualification peut avoir une autre collection Universités. 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. La même exception s'est donc produite.

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

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

A

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X