Clés étrangères au parent et au grand parent

J'ai 3 tables de database nommées "Projets", "Contrats" et "Incidents". La design est pour un système de maintenance basé sur un projet. Les clients sont en mesure d'établir des contrats sur un projet de maintenance de diverses installations. En outre, les incidents isolés qui peuvent ou ne peuvent pas être liés à un contrat sur un projet, doivent être déclarables, comme par exemple des installations défectueuses.

Les projets ont une relation de 1 à plusieurs avec les contrats (chaque projet peut avoir plusieurs contrats, ou aucun). Un dossier d'incidents doit finalement pouvoir être résolu en un projet, mais ne nécessite pas toujours la présence d'un contrat. Dans certains cas, il est possible qu'un projet n'ait pas de contrat, mais il devrait néanless pouvoir avoir des incidents.

Notre concepteur de database a proposé que les incidents contiennent une key étrangère pour les projets et les contrats. En effet, il s'agit d'une relation qui a des keys séparées pour les parents et les grands-parents, pour permettre l'absence d'un logging parent. L'alternative est de créer un contrat "factice". Aucune des deux solutions n'a ma preference.

Pour aggraver les choses, un contrat fait également reference à un "débiteur" d'une autre table. Donc, en l'absence d'un contrat, un incident devrait également être en mesure de referencer un débiteur.

Je ne peux m'empêcher de penser que l'approche proposée est une violation de toutes les forms normales et qu'elle peut créer des problèmes futurs, y compris un problème de maintenance. Je cherche donc une solution alternative capable de maintenir l'intégrité à travers les tables. En outre, quelqu'un connaît-il d'autres problèmes que cette approche peut causer?

Pour ce que ça vaut, je suis le développeur qui est responsable de l'écriture de l'application qui fonctionnera avec cette database. Le projet doit être créé dans WPF avec LINQ sur SQL. Une exigence est qu'il devrait être capable d'interroger un logging de projet pour tous ses incidents, y compris ceux qui sont référencés via les contrats.

J'ai cherché des questions similaires sur SO, et bien qu'il y en ait beaucoup qui traitent des keys des grands-parents, aucune d'entre elles ne semble correspondre à mon problème.

Voici une approche plus simple. Avoir un contrat factice pour chaque projet utilisé pour les incidents sans contrat formel. Ce contrat sera toujours utilisé pour résoudre le projet.

Cela simplifie la design de la database, mais introduit d'autres problèmes. Par exemple, pour searchr des incidents sans contrat, vous ne searchriez pas NULL dans la colonne du contrat. Vous searchriez «pas vraiment un contrat» dans la table de contrat. Selon les circonstances, cela pourrait être une solution plus élégante. Cela résout également le problème avec Debtor .

Cela soulève un autre problème, qui peut être lié à des contrats multiples. En fait, vous pourriez vous refind dans une direction où vous devez prendre en charge encore une autre table qui est un mappage NM entre les incidents et les projets.

Je n'ai pas de preference ni d'expérience non plus. À première vue, j'aime l'idée d'un contrat «factice». Si vous le faites, je suggère d'append une colonne spécifique au contrat afin que vous puissiez facilement voir s'il s'agit d'un contrat factice ou réel.

Un seul contrat factice peut contenir tous les incidents sans contrat. Le risque du contrat factice vient lorsque vous commencez à utiliser les champs du contrat factice (comme Debtor). S'il n'y a pas de contrat, le débiteur est-il le même pour tous les incidents du même projet? Sinon, cela signifierait que vous vous refindiez avec plusieurs contrats fictifs (un par débiteur). Peut-être qu'à l'avenir vous aurez d'autres domaines qui mèneront à un contrat factice par incident.

Je ne connais pas votre entreprise, mais ces incidents sans contrat peuvent devenir très dangereux pour votre design.

Une autre approche consiste à utiliser le contrat comme model / model pour les incidents. Dans ce cas, vous avez les attributes debtorId, contractId et projectId (…) au niveau de l'incident. Lorsque l'incident est créé et lié à un contrat, certaines informations sur le contrat sont copiées dans l'incident. Cela offre la plus grande flexibilité au niveau de l'incident, ce qui est nécessaire pour les incidents sans contrat. Vous pouvez décider de rendre ces champs d'incident en lecture seule et synchronisés s'il existe un contrat associé.

Je ne suis pas fan de toutes datatables "factices". Si un incident ne peut concerner qu'un seul contrat, ou aucun contrat, alors j'adopterais une approche comme celle-ci:

 CREATE TABLE dbo.Project ( ProjectID INT IDENTITY, Filler CHAR(1) NULL, CONSTRAINT PK_Project__ProjectID PRIMARY KEY (ProjectID) ); CREATE TABLE dbo.Contract ( ContractID INT IDENTITY, ProjectID INT NOT NULL, Filler CHAR(1) NULL, CONSTRAINT PK_Contract__ContractID PRIMARY KEY (ContractID), CONSTRAINT FK_Contract__ProjectID FOREIGN KEY (ProjectID) REFERENCES dbo.Project (ProjectID), CONSTRAINT UQ_Contract__ContractID_ProjectID UNIQUE (ContractID, ProjectID) ); CREATE TABLE dbo.Incident ( IncidentID INT IDENTITY, ProjectID INT NOT NULL, ContractID INT NULL, Filler CHAR(1) NULL, CONSTRAINT PK_Incident__IncidentID PRIMARY KEY (IncidentID), CONSTRAINT FK_Incident__ProjectID FOREIGN KEY (ProjectID) REFERENCES dbo.Project (ProjectID), CONSTRAINT FK_Incident__ContractID FOREIGN KEY (ContractID, ProjectID) REFERENCES dbo.Contract (ContractID, ProjectID) ); -- CREATE TWO DUMMY PROJECTS INSERT dbo.Project DEFAULT VALUES; INSERT dbo.Project DEFAULT VALUES; -- ADD A CONTRACT TWO EACH INSERT dbo.Contract (ProjectID) SELECT ProjectID FROM Project; -- ADD AN INCIDENT TO EACH WITH NO CONTRACT INSERT dbo.Incident (ProjectID) SELECT ProjectID FROM Project; -- ADD A VALID INCIDENT TO EACH CONTRACT INSERT dbo.Incident (ContractID, ProjectID) SELECT ContractID, ProjectID FROM dbo.Contract; -- TRY AND ADD INVALID CONTRACT TO FIRST PROJECT INSERT dbo.Incident (ContractID, ProjectID) SELECT c.ContractID, p.ProjectID FROM dbo.Project AS p CROSS JOIN dbo.Contract AS c WHERE c.ProjectID != p.ProjectID; 

Cela échouera avec l'erreur:

 >The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Incident__ContractID". The conflict occurred in database "TestDB", table "dbo.Contract". 

Une key étrangère peut faire reference à la contrainte unique de Contract , qui vous permet d'appliquer l'intégrité dans dbo.Incident , c'est-à-dire que vous ne pouvez pas entrer un projet qui ne correspond pas au contrat entré. La seule vraie chute du scénario est que vous dupliquez ProjectID lorsque ContractID est rempli, mais je ne pense pas que ce soit un gros problème. Certainement (à mon avis) less d'un problème que des données factices.

Il est alors très simple d'identifier les contrats fictifs:

 SELECT * FROM dbo.Incident WHERE ContractID IS NULL; 

Je pense que l'introduction d'un contrat factice dans le système est la voie à suivre. Ce que je fais normalement est que je ferais un contrat factice, en le marquant comme supprimé (de sorte qu'il n'est pas ramassé dans l'une de vos requêtes). Ensuite, je voudrais créer un autre projet dans votre solution pour tenir les constantes. Ensuite, j'aurais une ligne comme celle-ci:

 public static readonly int DummyContractId = 25; // Or whatever the ID is of your dummy contract ID. 

Vous pouvez désormais créer dans votre code des requêtes qui excluent ou incluent des incidents ayant un contrat factice.

J'utiliserais un triggersur pour imposer l'intégrité. Maintenant, un ProjectID est toujours requirejs dans un Incident. Quand un ContractID est ajouté en tant que key étrangère, le triggersur vérifie si le ProjectID du ContractID inséré correspond au ProjectID déjà inséré, sinon n'autorise pas l'insertion. Cela garantit toujours que vous ne finissez pas avec des relations corrompues. En outre, cela facilite grandement la génération de rapports sur tous les incidents dans un projet ou tous les incidents dans un contrat spécifique.