problème de violation de contrainte de clé étrangère

10

J'ai identifié 3 situations.

  1. Un étudiant sans inscription.
  2. Un étudiant avec des inscriptions mais pas de notes.
  3. Un étudiant avec des inscriptions et des notes.

Il y a un déclencheur sur le tableau des inscriptions pour calculer la GPA. Si un étudiant a des notes, il mettra à jour ou insérera une entrée dans le tableau GPA; pas de notes, pas d'entrée de table GPA.

Je peux supprimer un étudiant sans inscription (# 1). Je peux supprimer un étudiant avec des inscriptions et des notes (# 3 ci-dessus). Mais je ne peux pas supprimer un étudiant avec des inscriptions mais pas de notes (# 2). J'obtiens une violation de contrainte de référence.

L'instruction DELETE est en conflit avec la contrainte REFERENCE "FK_dbo.GPA_dbo.Student_StudentID". Le conflit s'est produit dans la base de données "", table "dbo.GPA", colonne 'StudentID'.

Si je ne pouvais pas supprimer un nouvel étudiant sans inscription (et sans entrée GPA), je comprendrais la violation de contrainte, mais je peux supprimer cet étudiant. C'est un étudiant avec des inscriptions et aucune note (et toujours aucune entrée GPA) que je ne peux pas supprimer.

J'ai corrigé ma gâchette pour pouvoir avancer. Maintenant, si vous avez des inscriptions, le déclencheur vous insère dans la table GPA, quoi qu'il arrive. Mais je ne comprends pas le problème sous-jacent. Toute explication serait très appréciée.

Pour ce que ça vaut:

  1. Visual Studio 2013 Professional.
  2. IIS express (interne à VS2013).
  3. Application Web ASP.NET utilisant EntityFramework 6.1.1.
  4. MS SQL Server 2014 Enterprise.
  5. GPA.Value est nullable.
  6. Enrollment.GradeID est nullable.

Voici un extrait de la base de données:

image de base de données

- MODIFIER -

Les tables sont toutes créées par EntityFramework, j'ai utilisé SQL Server Management Studio pour les produire.

Voici les instructions de création de table avec des contraintes:

GPA table:

CREATE TABLE [dbo].[GPA](
    [StudentID] [int] NOT NULL,
    [Value] [float] NULL,
  CONSTRAINT [PK_dbo.GPA] PRIMARY KEY CLUSTERED 
  (
    [StudentID] ASC
  )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
         ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[GPA]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.GPA_dbo.Student_StudentID] 
  FOREIGN KEY([StudentID])
  REFERENCES [dbo].[Student] ([ID])

ALTER TABLE [dbo].[GPA] 
  CHECK CONSTRAINT [FK_dbo.GPA_dbo.Student_StudentID]

Enrollment table:

CREATE TABLE [dbo].[Enrollment](
    [EnrollmentID] [int] IDENTITY(1,1) NOT NULL,
    [CourseID] [int] NOT NULL,
    [StudentID] [int] NOT NULL,
    [GradeID] [int] NULL,
  CONSTRAINT [PK_dbo.Enrollment] PRIMARY KEY CLUSTERED 
  (
    [EnrollmentID] ASC
  )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
         ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[Enrollment]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.Enrollment_dbo.Course_CourseID] 
  FOREIGN KEY([CourseID])
  REFERENCES [dbo].[Course] ([CourseID])
  ON DELETE CASCADE

ALTER TABLE [dbo].[Enrollment] 
  CHECK CONSTRAINT [FK_dbo.Enrollment_dbo.Course_CourseID]

ALTER TABLE [dbo].[Enrollment]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.Enrollment_dbo.Grade_GradeID] 
  FOREIGN KEY([GradeID])
  REFERENCES [dbo].[Grade] ([GradeID])

ALTER TABLE [dbo].[Enrollment] 
  CHECK CONSTRAINT [FK_dbo.Enrollment_dbo.Grade_GradeID]

ALTER TABLE [dbo].[Enrollment]  WITH CHECK 
  ADD  CONSTRAINT [FK_dbo.Enrollment_dbo.Student_StudentID] 
  FOREIGN KEY([StudentID])
  REFERENCES [dbo].[Student] ([ID])
  ON DELETE CASCADE

ALTER TABLE [dbo].[Enrollment] 
  CHECK CONSTRAINT [FK_dbo.Enrollment_dbo.Student_StudentID]

Student table:

CREATE TABLE [dbo].[Student](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [EnrollmentDate] [datetime] NOT NULL,
    [LastName] [nvarchar](50) NOT NULL,
    [FirstName] [nvarchar](50) NOT NULL,
  CONSTRAINT [PK_dbo.Student] PRIMARY KEY CLUSTERED 
  (
    [ID] ASC
  )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
         ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

Voici les déclencheurs :

CREATE TRIGGER UpdateGPAFromUpdateDelete
ON Enrollment
AFTER UPDATE, DELETE AS
BEGIN
    DECLARE @UpdatedStudentID AS int
    SELECT @UpdatedStudentID = StudentID FROM DELETED
    EXEC MergeGPA @UpdatedStudentID
END

CREATE TRIGGER UpdateGPAFromInsert
ON Enrollment
AFTER INSERT AS
--DECLARE @InsertedGradeID AS int
--SELECT @InsertedGradeID = GradeID FROM INSERTED
--IF @InsertedGradeID IS NOT NULL
    BEGIN
        DECLARE @InsertedStudentID AS int
        SELECT @InsertedStudentID = StudentID FROM INSERTED
        EXEC MergeGPA @InsertedStudentID
    END

Le patch pour aller de l'avant était de commenter ces lignes dans le AFTER INSERTdéclencheur.

Voici la procédure stockée :

CREATE PROCEDURE MergeGPA @StudentID int AS
MERGE GPA AS TARGET
USING (SELECT @StudentID) as SOURCE (StudentID)
ON (TARGET.StudentID = SOURCE.StudentID)
WHEN MATCHED THEN
    UPDATE
        SET Value = (SELECT Value FROM GetGPA(@StudentID))
WHEN NOT MATCHED THEN
INSERT (StudentID, Value)
    VALUES(SOURCE.StudentID, (SELECT Value FROM GetGPA(@StudentID)));

Voici la fonction de base de données :

CREATE FUNCTION GetGPA (@StudentID int) 
RETURNS TABLE
AS RETURN
SELECT ROUND(SUM (StudentTotal.TotalCredits) / SUM (StudentTotal.Credits), 2) Value
    FROM (
        SELECT 
            CAST(Credits as float) Credits
            , CAST(SUM(Value * Credits) as float) TotalCredits
        FROM 
            Enrollment e 
            JOIN Course c ON c.CourseID = e.CourseID
            JOIN Grade g  ON e.GradeID = g.GradeID
        WHERE
            e.StudentID = @StudentID AND
            e.GradeID IS NOT NULL
        GROUP BY
            StudentID
            , Value
            , e.courseID
            , Credits
    ) StudentTotal

Voici la sortie de débogage de la méthode de suppression du contrôleur, l'instruction select est la méthode qui demande quoi supprimer. Cet étudiant a 3 inscriptions, le REFERENCEproblème de contrainte se produit lors de la suppression de la 3ème inscription. Je suppose qu'EF utilise une transaction car les inscriptions ne sont pas supprimées.

iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.ReaderExecuted;Timespan:00:00:00.0004945;Properties:
Command: SELECT 
    [Project2].[StudentID] AS [StudentID], 
    [Project2].[ID] AS [ID], 
    [Project2].[EnrollmentDate] AS [EnrollmentDate], 
    [Project2].[LastName] AS [LastName], 
    [Project2].[FirstName] AS [FirstName], 
    [Project2].[Value] AS [Value], 
    [Project2].[C1] AS [C1], 
    [Project2].[EnrollmentID] AS [EnrollmentID], 
    [Project2].[CourseID] AS [CourseID], 
    [Project2].[StudentID1] AS [StudentID1], 
    [Project2].[GradeID] AS [GradeID]
    FROM ( SELECT 
        [Limit1].[ID] AS [ID], 
        [Limit1].[EnrollmentDate] AS [EnrollmentDate], 
        [Limit1].[LastName] AS [LastName], 
        [Limit1].[FirstName] AS [FirstName], 
        [Limit1].[StudentID] AS [StudentID], 
        [Limit1].[Value] AS [Value], 
        [Extent3].[EnrollmentID] AS [EnrollmentID], 
        [Extent3].[CourseID] AS [CourseID], 
        [Extent3].[StudentID] AS [StudentID1], 
        [Extent3].[GradeID] AS [GradeID], 
        CASE WHEN ([Extent3].[EnrollmentID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
        FROM   (SELECT TOP (2) 
            [Extent1].[ID] AS [ID], 
            [Extent1].[EnrollmentDate] AS [EnrollmentDate], 
            [Extent1].[LastName] AS [LastName], 
            [Extent1].[FirstName] AS [FirstName], 
            [Extent2].[StudentID] AS [StudentID], 
            [Extent2].[Value] AS [Value]
            FROM  [dbo].[Student] AS [Extent1]
            LEFT OUTER JOIN [dbo].[GPA] AS [Extent2] ON [Extent1].[ID] = [Extent2].[StudentID]
            WHERE [Extent1].[ID] = @p__linq__0 ) AS [Limit1]
        LEFT OUTER JOIN [dbo].[Enrollment] AS [Extent3] ON [Limit1].[ID] = [Extent3].[StudentID]
    )  AS [Project2]
    ORDER BY [Project2].[StudentID] ASC, [Project2].[ID] ASC, [Project2].[C1] ASC: 
iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.NonQueryExecuted;Timespan:00:00:00.0012696;Properties:
Command: DELETE [dbo].[Enrollment]
WHERE ([EnrollmentID] = @0): 
iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.NonQueryExecuted;Timespan:00:00:00.0002634;Properties:
Command: DELETE [dbo].[Enrollment]
WHERE ([EnrollmentID] = @0): 
iisexpress.exe Information: 0 : Component:SQL Database;Method:SchoolInterceptor.NonQueryExecuted;Timespan:00:00:00.0002512;Properties:
Command: DELETE [dbo].[Enrollment]
WHERE ([EnrollmentID] = @0): 
iisexpress.exe Error: 0 : Error executing command: DELETE [dbo].[Student]
WHERE ([ID] = @0) Exception: System.Data.SqlClient.SqlException (0x80131904): The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.GPA_dbo.Student_StudentID". The conflict occurred in database "<databasename>", table "dbo.GPA", column 'StudentID'.
The statement has been terminated.
DowntownHippie
la source

Réponses:

7

C'est une question de timing. Pensez à supprimer StudentID # 1:

  1. La ligne est supprimée du Studenttableau
  2. La suppression en cascade supprime les lignes correspondantes de Enrollment
  3. La relation de clé étrangère GPA-> Studentest vérifiée
  4. Le déclencheur se déclenche, appelant MergeGPA

À ce stade, MergeGPAvérifie s'il existe une entrée pour l'élève n ° 1 dans le GPAtableau. Il n'y en a pas (sinon le contrôle FK à l'étape 3 aurait déclenché une erreur).

Ainsi, la WHEN NOT MATCHEDclause MergeGPAtente de INSERTune ligne GPApour StudentID # 1. Cette tentative échoue (avec l'erreur FK) car StudentID # 1 a déjà été supprimé de la Studenttable (à l'étape 1).

Paul White 9
la source
1
Je pense que vous êtes sur quelque chose. Lorsqu'un étudiant est créé avec des inscriptions, mais qu'aucune note n'a été attribuée, cet étudiant n'a aucune entrée dans le tableau GPA. Lorsque la base de données va supprimer cet élève, elle regarde la base de données, voit les inscriptions à supprimer mais aucune entrée GPA. Il s'agit donc de supprimer les inscriptions, ce qui provoque le déclenchement d'un déclencheur qui crée l'entrée GPA, ce qui provoque ensuite la violation de la contrainte? La solution consiste donc à créer une entrée GPA lorsque je crée un étudiant. Ensuite, mon déclencheur d'insertion n'aura pas besoin d'un conditionnel, et ma procédure stockée n'aura pas besoin d'être une fusion, juste une mise à jour.
DowntownHippie
-1

Sans tout lire, juste à partir du diagramme: vous avez soit une entrée dans l'inscription, soit une dans GPA pointant vers l'étudiant que vous souhaitez supprimer.

Les entrées avec les clés étrangères doivent d'abord être supprimées (ou les clés définies sur null, mais c'est une mauvaise pratique) avant de pouvoir supprimer l'entrée Student.

De plus, certaines bases de données ont ON DELETE CASCADE, qui supprimera toutes les entrées avec des clés étrangères à celle que vous souhaitez supprimer.

Une autre façon consiste à ne pas les déclarer comme clés étrangères et à n'utiliser que la valeur de clé, mais ce n'est pas recommandé non plus.

user44286
la source
Dans les cas où il échoue, il y a une entrée dans l'inscription mais pas une dans GPA.
DowntownHippie
vous avez des contraintes avec ON DELETE CASCADE et d'autres sans. essayez d'ajouter cette ligne à toutes les contraintes. après cela, essayez de désactiver tous les déclencheurs et après ce test avec une configuration minimale. bonne chance
user44286
Je vois ces ON DELETE CASCADEdéclarations. Aucune de ces instructions de création de table, ni les instructions de suppression ne sont écrites à la main, elles sont toutes générées par l'entitéframework. Les cascades sont dues au fait que l'inscription a des clés étrangères qui ne sont pas sa clé primaire; La contrainte de clé étrangère de GPA est sa clé primaire, donc elle ne devrait pas avoir besoin d'une cascade. J'ai testé cela, si vous supprimez un étudiant avec une entrée de table GPA, l'entrée est supprimée. Le seul problème est un étudiant avec des inscriptions mais pas de gpa.
DowntownHippie