外键约束违规问题


10

我确定了3种情况。

  1. 没有入学的学生。
  2. 有入学但没有成绩的学生。
  3. 具有入学和成绩的学生。

注册表上有一个触发器来计算GPA。如果学生有成绩,它将更新或在GPA表中插入一个条目;没有成绩,没有GPA表条目。

我可以删除没有注册的学生(#1)。我可以删除具有入学和成绩的学生(以上#3)。但是我不能删除没有注册但没有成绩的学生(#2)。我收到了参考约束违规。

DELETE语句与REFERENCE约束“ FK_dbo.GPA_dbo.Student_StudentID”冲突。在数据库“”,表“ dbo.GPA”的“ StudentID”列中发生了冲突。

如果我无法删除没有注册(也没有GPA条目)的新学生,那么我会理解违反约束的条件,但是我可以删除该学生。这是一个学生,没有注册并且没有成绩(我也没有GPA条目),我无法删除。

我已修补好扳机,因此可以继续前进。现在,如果您已注册,则无论如何触发器都会将您插入GPA表。但是我不理解潜在的问题。任何解释将不胜感激。

物有所值:

  1. Visual Studio 2013专业版。
  2. IIS Express(VS2013内部)。
  3. 使用EntityFramework 6.1.1的ASP.NET Web App。
  4. MS SQL Server 2014企业版。
  5. GPA.Value可为空。
  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

这是控制器的delete方法的调试输出,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.

Answers:


7

这是时间问题。考虑删除StudentID#1:

  1. 该行从Student表中删除
  2. 级联删除从中删除相应的行 Enrollment
  3. 外键关系GPA-> Student已检查
  4. 触发器触发,呼叫 MergeGPA

此时,MergeGPA检查表中是否有学生#1的条目GPA。没有(否则,步骤3中的FK检查将引发错误)。

所以,在WHEN NOT MATCHED条款中MergeGPA尝试INSERT一排GPA的StudentID#1。该尝试失败(带有FK错误),因为已经从Student表中删除了StudentID#1 (在步骤1)。


1
我想你正在做某事。如果创建一个有注册的学生,但未分配成绩,则该学生在GPA表中没有任何条目。当数据库删除该学生时,它查看数据库,看到要删除的注册,但没有GPA条目。因此,它着手删除注册,这会导致触发触发器,从而创建GPA条目,然后导致违反约束?因此,解决方案是在我创建学生时创建GPA条目。然后,我的插入触发器将不需要条件,并且我的存储过程将不需要合并,只需更新即可。
DowntownHippie

-1

无需阅读全部内容,仅从图中即可:您在“注册”中有一个条目,或在GPA中有一个条目指向您要删除的学生。

在删除学生条目之前,需要先删除具有外键的条目(或将键设置为null,但这是不正确的做法)。

还有一些数据库具有ON DELETE CASCADE,它将删除所有带有外键的条目,这些条目都指向您要删除的条目。

另一种方法是不将其声明为外键,而仅使用键值,但这也不是必需的。


如果失败,则在“注册”中有一个条目,但在GPA中没有一个。
DowntownHippie

使用ON DELETE CASCADE有一些约束,而没有约束。尝试将该行添加到所有约束。之后,请尝试禁用所有触发器,然后在进行最小设置的测试之后。祝你好运
user44286

我看到那些ON DELETE CASCADE陈述。这些表创建语句,删除语句都不是手写的,它们都是由entityframework生成的。级联是因为注册具有的外键不是主键,而是外键。GPA的外键约束是它的主键,因此它不需要级联。我已经对此进行了测试,如果您删除具有GPA表条目的学生,则该条目将被删除。唯一的问题是有入学率但没有gpa的学生。
DowntownHippie
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.