problema di violazione del vincolo di chiave esterna


10

Ho identificato 3 situazioni.

  1. Uno studente senza iscrizioni.
  2. Uno studente con iscrizioni ma senza voti.
  3. Uno studente con iscrizioni e voti.

C'è un trigger nella tabella delle iscrizioni per calcolare GPA. Se uno studente ha voti, aggiornerà o inserirà una voce nella tabella GPA; nessun voto, nessuna voce nella tabella GPA.

Posso eliminare uno studente senza iscrizioni (n. 1). Posso eliminare uno studente con iscrizioni e voti (n. 3 sopra). Ma non posso cancellare uno studente con iscrizioni ma senza voti (# 2). Ottengo una violazione del vincolo di riferimento.

L'istruzione DELETE è in conflitto con il vincolo REFERENCE "FK_dbo.GPA_dbo.Student_StudentID". Il conflitto si è verificato nel database "", tabella "dbo.GPA", colonna 'StudentID'.

Se non potessi cancellare un nuovo studente senza iscrizioni (e nessuna iscrizione GPA), comprenderei la violazione del vincolo, ma posso eliminare quello studente. È uno studente con iscrizioni e nessun voto (e ancora nessuna iscrizione GPA) che non posso cancellare.

Ho rattoppato il grilletto in modo da poter andare avanti. Ora, se hai iscrizioni, il trigger ti inserisce nella tabella GPA, non importa quale. Ma non capisco il problema di fondo. Qualsiasi spiegazione sarebbe molto apprezzata.

Per quello che vale:

  1. Visual Studio 2013 Professional.
  2. IIS express (interno a VS2013).
  3. Applicazione Web ASP.NET mediante EntityFramework 6.1.1.
  4. MS SQL Server 2014 Enterprise.
  5. GPA.Value è nullable.
  6. Enrollment.GradeID è nullable.

Ecco uno snippet del database:

immagine del database

- MODIFICA -

Le tabelle sono tutte create da EntityFramework, ho usato SQL Server Management Studio per produrle.

Ecco le istruzioni per la creazione della tabella con vincoli .:

GPA tavolo:

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 tavolo:

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 tavolo:

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]

Ecco i trigger :

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

La patch per andare avanti era commentare quelle righe nel AFTER INSERTtrigger.

Ecco la procedura memorizzata :

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

Ecco la funzione del database :

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

Ecco l'output di debug dal metodo di eliminazione del controller, l'istruzione select è il metodo che richiede cosa eliminare. Questo studente ha 3 iscrizioni, il REFERENCEproblema di vincolo si verifica quando viene eliminata la 3a iscrizione. Presumo che EF stia utilizzando una transazione perché le iscrizioni non vengono cancellate.

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.

Risposte:


7

È una questione di tempismo. Valuta di eliminare StudentID n. 1:

  1. La riga viene eliminata dalla Studenttabella
  2. L'eliminazione a cascata rimuove le righe corrispondenti da Enrollment
  3. La relazione chiave esterna GPA-> Studentè selezionata
  4. Il grilletto si attiva, chiamando MergeGPA

A questo punto, MergeGPAcontrolla se esiste una voce per lo Studente n. 1 nella GPAtabella. Non esiste (altrimenti il ​​controllo FK nel passaggio 3 avrebbe generato un errore).

Quindi, la WHEN NOT MATCHEDclausola nei MergeGPAtentativi di eseguire INSERTuna riga GPAper StudentID # 1. Questo tentativo fallisce (con l'errore FK) perché StudentID # 1 è già stato eliminato dalla Studenttabella (al passaggio 1).


1
Penso che tu stia per qualcosa. Quando viene creato uno studente con iscrizioni, ma non sono stati assegnati voti, tale studente non ha accesso alla tabella GPA. Quando il database va a eliminare quello studente, guarda il database, vede le registrazioni da eliminare ma nessuna voce GPA. Quindi si tratta di eliminare le registrazioni, che provocano l'attivazione di un trigger che crea la voce GPA, che quindi provoca la violazione del vincolo? Quindi la soluzione è creare una voce GPA quando creo uno studente. Quindi il mio trigger di inserimento non avrà bisogno di un condizionale, e la mia procedura memorizzata non dovrà essere un'unione, ma solo un aggiornamento.
DowntownHippie,

-1

Senza leggere tutto, solo dal diagramma: hai una voce in Iscrizione o una in GPA che punta allo Studente che desideri eliminare.

Le voci con le chiavi esterne devono essere prima eliminate (o le chiavi impostate su null, ma è una cattiva pratica) prima di poter eliminare la voce Studente.

Inoltre alcuni database hanno ON DELETE CASCADE, che eliminerà tutte le voci con chiavi esterne a quella che si desidera eliminare.

Un altro modo è quello di non dichiararli come chiavi esterne e utilizzare solo il valore della chiave, ma neanche quello è consigliato.


Nei casi in cui fallisce, c'è una voce in Iscrizione ma non una in GPA.
DowntownHippie il

hai alcuni vincoli con ON DELETE CASCADE e alcuni senza. prova ad aggiungere quella linea a tutti i vincoli. dopodiché proverebbe a disabilitare tutti i trigger e dopo quel test con una configurazione minima. buona fortuna
user44286

Vedo quelle ON DELETE CASCADEdichiarazioni. Nessuna di queste istruzioni per la creazione di tabelle, né le istruzioni di eliminazione sono scritte a mano, sono tutte generate da entityframework. Le cascate sono perché la registrazione ha chiavi esterne che non sono la chiave primaria; Il vincolo della chiave esterna di GPA è la chiave primaria, quindi non dovrebbe aver bisogno di una cascata. Ho provato questo, se si elimina uno studente con una voce di tabella GPA la voce viene eliminata. L'unico problema è uno studente con iscrizioni ma senza gpa.
DowntownHippie,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.