Проблема порушення закордонних обмежень


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-експрес (внутрішній для VS2013).
  3. Веб-додаток ASP.NET за допомогою EntityFramework 6.1.1.
  4. MS SQL Server 2014 Enterprise.
  5. GPA.Value є нульовим.
  6. Зарахування.GradeID є нульовим.

Ось фрагмент бази даних:

зображення бази даних

- EDIT -

Усі таблиці створені EntityFramework, для їх створення я використовував студію управління SQL Server.

Ось такі заяви створення таблиць із обмеженнями:

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

Ось вихід налагодження з методу видалення контролера, оператор select - це метод, що запитує, що видалити. У цього студента є 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.

Відповіді:


7

Це питання часу. Спробуйте видалити StudentID №1:

  1. Рядок видалено з Studentтаблиці
  2. Каскадне видалення видаляє відповідні рядки з Enrollment
  3. Зв'язок із зовнішнім ключем GPA-> Studentперевіряється
  4. Курок спрацьовує, дзвонить MergeGPA

У цей момент MergeGPAперевірте, чи є в GPAтаблиці запис Студент №1 . Немає (інакше перевірка FK на кроці 3 призвела б до помилки).

Отже, WHEN NOT MATCHEDзастереження у MergeGPAспробах вступити INSERTв рядку GPAдля StudentID №1. Ця спроба не вдається (з помилкою FK), оскільки StudentID №1 вже видалено з Studentтаблиці (на кроці 1).


1
Я думаю, ти щось на те. Коли студент створений із записами, але оцінки не присвоєні, він не має запису в таблиці GPA. Коли база даних переходить до цього студента, вона дивиться на базу даних, бачить реєстрації для видалення, але немає запису GPA. Отже, він налаштовує на видалення записів, які спричиняють тригер, що створює запис GPA, що потім спричиняє порушення обмеження? Тож рішення - створити запис GPA, коли я створюю студента. Тоді мій тригер не потребуватиме умовного, і моя збережена процедура не потребує злиття, а лише оновлення.
DowntownHippie

-1

Не читаючи всіх, лише з діаграми: у вас є запис у реєстрації або запис у GPA, який вказує на студента, якого ви хочете видалити.

Записи із зовнішніми ключами потрібно спочатку видалити (або ж клавіші встановлені на нульовому рівні, але це погана практика), перш ніж ви зможете видалити запис Студент.

Також у деяких базах даних є НАДОБРИТИ КАСКАД, який видалить усі записи із зовнішніми ключами до того, який ви хочете видалити.

Інший спосіб - не оголошувати їх як сторонні ключі та використовувати лише значення ключа, але це теж не рекомендується.


У тих випадках, коли він не вдається, є запис у зарахуванні, але не один у ГПД.
DowntownHippie

у вас є деякі обмеження щодо ON DELETE CASCADE, а деякі - без. спробуйте додати цей рядок до всіх обмежень. після цього спробуйте відключити всі тригери та після цього тесту з мінімальними налаштуваннями. удачі
user44286

Я бачу ці ON DELETE CASCADEзаяви. Жодна з цих заяв про створення таблиці, а також операції видалення не написані вручну, всі вони генеруються суттюфреймом. Каскади є тому, що в реєстрації є іноземні ключі, які не є первинним ключем; Обмеження зовнішнього ключа GPA - це первинний ключ, тому йому не потрібно каскаду. Я перевірив це, якщо ви видалите студента із записом таблиці GPA, запис видаляється. Єдине питання - це студент із записами, але без gpa.
DowntownHippie
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.