проблема нарушения ограничения внешнего ключа

10

Я определил 3 ситуации.

  1. Студент без зачисления.
  2. Студент с зачислением, но без оценок.
  3. Студент с зачислениями и оценками.

В таблице регистрации есть триггер для расчета среднего балла. Если у ученика есть оценки, он обновит или вставит запись в таблицу GPA; нет оценок, нет записи в таблице GPA.

Я могу удалить студента без зачисления (# 1). Я могу удалить студента с зачислениями и оценками (№ 3 выше). Но я не могу удалить студента с зачислениями, но без оценок (# 2). Я получил нарушение ссылочного ограничения.

Оператор DELETE конфликтует с ограничением REFERENCE "FK_dbo.GPA_dbo.Student_StudentID". Конфликт произошел в базе данных "", таблица "dbo.GPA", столбец "StudentID".

Если бы я не мог удалить нового студента без регистрации (и без записи GPA), я бы понял нарушение ограничения, но я могу удалить этого студента. Это студент с зачислением и без оценок (и все еще без записи GPA), который я не могу удалить.

Я исправил мой курок, чтобы я мог идти вперед. Теперь, если у вас есть регистрации, триггер вставляет вас в таблицу GPA, несмотря ни на что. Но я не понимаю основной проблемы. Любое объяснение будет наиболее ценно.

Для чего это стоит:

  1. Visual Studio 2013 Professional.
  2. IIS express (внутренний для VS2013).
  3. ASP.NET Web App с использованием EntityFramework 6.1.1.
  4. MS SQL Server 2014 Enterprise.
  5. GPA. Значение обнуляется.
  6. Enrollment.GradeID обнуляется.

Вот фрагмент базы данных:

образ базы данных

- РЕДАКТИРОВАТЬ -

Все таблицы созданы EntityFramework, я использовал SQL Server Management Studio для их создания.

Вот операторы создания таблицы с ограничениями.

GPA стол:

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 стол:

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 стол:

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]

Вот триггеры :

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

Патч для продвижения вперед заключался в том, чтобы закомментировать эти строки в AFTER INSERTтриггере.

Вот хранимая процедура :

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

Вот функция базы данных :

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

Вот выходные данные отладки из метода удаления контроллера, оператор выбора - метод, запрашивающий, что удалить. У этого студента 3 зачисления, REFERENCEпроблема ограничения возникает при удалении 3-го зачисления. Я предполагаю, что EF использует транзакцию, потому что записи не удалены.

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
источник

Ответы:

7

Это вопрос времени. Подумайте об удалении StudentID # 1:

  1. Строка удалена из Studentтаблицы
  2. Каскадное удаление удаляет соответствующие строки из Enrollment
  3. Отношение внешнего ключа GPA-> Studentпроверено
  4. Триггер стреляет, зовет MergeGPA

На этом этапе MergeGPAпроверяет, есть ли запись для ученика № 1 в GPAтаблице. Нет (в противном случае проверка FK на шаге 3 вызвала бы ошибку).

Итак, WHEN NOT MATCHEDпредложение в MergeGPAпопытках INSERTгрести GPAдля StudentID # 1. Эта попытка не удалась (с ошибкой FK), поскольку StudentID # 1 уже был удален из Studentтаблицы (на шаге 1).

Пол Уайт 9
источник
1
Я думаю, что вы к чему-то. Когда ученик создается с зачислением, но оценки не назначены, этот ученик не имеет записи в таблице GPA. Когда база данных отправляется на удаление этого ученика, она просматривает базу данных, видит записи для удаления, но нет записи GPA. Таким образом, он приступает к удалению регистрации, что вызывает срабатывание триггера, который создает запись GPA, которая затем вызывает нарушение ограничения? Таким образом, решение заключается в создании записи GPA, когда я создаю студента. Тогда моему триггеру вставки не понадобится условие, и моя хранимая процедура не должна быть слиянием, просто обновлением.
Центр города
-1

Не читая все, только из диаграммы: у вас есть запись в Enrollment или запись в GPA, указывающая на студента, которого вы хотите удалить.

Записи с внешними ключами необходимо сначала удалить (или ключи, для которых установлено значение null, но это плохая практика), прежде чем вы сможете удалить запись Student.

Также некоторые базы данных имеют ON DELETE CASCADE, который удалит любые записи с внешними ключами к той, которую вы хотите удалить.

Другой способ - не объявлять их как внешние ключи и использовать только значение ключа, но это также не рекомендуется.

user44286
источник
В тех случаях, когда это не удается, есть запись в Enrollment, но нет записи в GPA.
DowntownHippie
у вас есть некоторые ограничения с ON DELETE CASCADE, а некоторые без. попробуйте добавить эту строку ко всем ограничениям. после этого попытался бы отключить все триггеры и после этого теста с минимальной настройкой. удачи
user44286
Я вижу эти ON DELETE CASCADEзаявления. Ни эти операторы создания таблиц, ни операторы удаления не написаны от руки, все они генерируются структурой сущностей. Каскады объясняются тем, что регистрация имеет внешние ключи, которые не являются ее первичными ключами; Ограничение внешнего ключа GPA - это первичный ключ, поэтому ему не нужен каскад. Я проверял это, если вы удаляете студента с записью в таблице GPA, запись удаляется. Единственная проблема - студент с зачислением, но без гпа.
Даунтаун