it-swarm.com.de

Problem mit der Verletzung von Fremdschlüsseleinschränkungen

Ich habe 3 Situationen identifiziert.

  1. Ein Student ohne Einschreibungen.
  2. Ein Student mit Einschreibungen, aber ohne Noten.
  3. Ein Student mit Einschreibungen und Noten.

In der Registrierungstabelle befindet sich ein Auslöser zur Berechnung des GPA. Wenn ein Schüler Noten hat, wird er aktualisiert oder einen Eintrag in die GPA-Tabelle einfügen. Keine Noten, kein GPA-Tabelleneintrag.

Ich kann einen Studenten ohne Einschreibung löschen (# 1). Ich kann einen Schüler mit Einschreibungen und Noten löschen (Nr. 3 oben). Aber ich kann keinen Studenten mit Einschreibungen, aber ohne Noten löschen (# 2). Ich erhalte eine Verletzung der Referenzbedingung.

Die DELETE-Anweisung stand in Konflikt mit der REFERENCE-Einschränkung "FK_dbo.GPA_dbo.Student_StudentID". Der Konflikt trat in der Datenbank "", Tabelle "dbo.GPA", Spalte 'StudentID' auf.

Wenn ich einen neuen Schüler ohne Einschreibungen (und ohne GPA-Eintrag) nicht löschen könnte, würde ich die Verletzung der Einschränkung verstehen, aber ich kann diesen Schüler löschen. Es ist ein Student mit Einschreibungen und ohne Noten (und immer noch ohne GPA-Eintrag), den ich nicht löschen kann.

Ich habe meinen Abzug gepatcht, damit ich vorwärts gehen kann. Wenn Sie jetzt Registrierungen haben, fügt der Trigger Sie in die GPA-Tabelle ein, egal was passiert. Aber ich verstehe das zugrunde liegende Problem nicht. Jede Erklärung wäre sehr dankbar.

Für das, was es wert ist:

  1. Visual Studio 2013 Professional.
  2. IIS Express (intern für VS2013).
  3. ASP.NET Web App mit EntityFramework 6.1.1.
  4. MS SQL Server 2014 Enterprise.
  5. GPA.Value ist nullbar.
  6. Enrollment.GradeID ist nullwertfähig.

Hier ist ein Ausschnitt aus der Datenbank:

database image

-- EDIT -

Die Tabellen werden alle von EntityFramework erstellt. Ich habe SQL Server Management Studio verwendet, um diese zu erstellen.

Hier sind die Anweisungen zum Erstellen von Tabellen mit Einschränkungen:

GPA Tabelle:

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

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

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]

Hier sind die 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

Der Patch, um vorwärts zu kommen, bestand darin, diese Zeilen im AFTER INSERT auslösen.

Hier ist die gespeicherte Prozedur:

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

Hier ist die Datenbank Funktion:

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

Hier ist die Debug-Ausgabe der Löschmethode des Controllers. Die select-Anweisung ist die Methode, die abfragt, was gelöscht werden soll. Dieser Schüler hat 3 Anmeldungen. Das Problem mit der Einschränkung REFERENCE tritt auf, wenn die 3. Anmeldung gelöscht wird. Ich gehe davon aus, dass EF eine Transaktion verwendet, da die Registrierungen nicht gelöscht werden.

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.
10
DowntownHippie

Es ist eine Frage des Timings. Löschen Sie möglicherweise die StudentID # 1:

  1. Die Zeile wird aus der Tabelle Student gelöscht
  2. Durch das Löschen der Kaskade werden entsprechende Zeilen aus Enrollment entfernt
  3. Die Fremdschlüsselbeziehung GPA -> Student wird überprüft
  4. Der Trigger wird ausgelöst und ruft MergeGPA auf

Zu diesem Zeitpunkt prüft MergeGPA, ob in der Tabelle GPA ein Eintrag für Student # 1 vorhanden ist. Dies ist nicht der Fall (andernfalls hätte die FK-Prüfung in Schritt 3 einen Fehler ausgelöst).

Also, die WHEN NOT MATCHED Klausel in MergeGPA versucht INSERT eine Zeile in GPA für StudentID # 1 _. Dieser Versuch schlägt fehl (mit dem FK-Fehler), da StudentID # 1 bereits aus der Tabelle Student gelöscht wurde (in Schritt 1).

7
Paul White 9