He identificado 3 situaciones.
- Un estudiante sin matrículas.
- Un estudiante con matrículas pero sin calificaciones.
- Un estudiante con matrículas y calificaciones.
Hay un disparador en la tabla de inscripciones para calcular el GPA. Si un estudiante tiene calificaciones, actualizará o insertará una entrada en la tabla de GPA; sin calificaciones, sin entrada en la tabla de GPA.
Puedo eliminar a un estudiante sin inscripciones (# 1). Puedo eliminar a un estudiante con inscripciones y calificaciones (# 3 arriba). Pero no puedo eliminar a un estudiante con matrículas pero sin calificaciones (# 2). Me sale una violación de restricción de referencia.
La instrucción DELETE está en conflicto con la restricción REFERENCE "FK_dbo.GPA_dbo.Student_StudentID". El conflicto ocurrió en la base de datos "", tabla "dbo.GPA", columna 'StudentID'.
Si no pudiera eliminar a un nuevo estudiante sin inscripciones (y sin ingreso de GPA), entonces entendería la violación de la restricción, pero puedo eliminar a ese estudiante. Es un estudiante con inscripciones y sin calificaciones (y aún sin ingreso de GPA) que no puedo eliminar.
He parchado mi gatillo para poder seguir adelante. Ahora, si tiene inscripciones, el disparador lo inserta en la tabla de GPA sin importar qué. Pero no entiendo el problema subyacente. Cualquier explicación sería muy apreciada.
Por lo que vale:
- Visual Studio 2013 Profesional.
- IIS express (interno a VS2013).
- Aplicación web ASP.NET con EntityFramework 6.1.1.
- MS SQL Server 2014 Enterprise.
- GPA.Value es anulable.
- Enrollment.GradeID es anulable.
Aquí hay un fragmento de la base de datos:
- EDITAR -
Todas las tablas son creadas por EntityFramework, utilicé SQL Server Management Studio para producirlas.
Aquí están las instrucciones de crear tabla con restricciones .:
GPA
mesa:
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
mesa:
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
mesa:
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]
Aquí están los desencadenantes :
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
El parche para avanzar era comentar esas líneas en el AFTER INSERT
gatillo.
Aquí está el procedimiento almacenado :
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)));
Aquí está la función de base de datos :
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
Aquí está la salida de depuración del método de eliminación del controlador, la instrucción select es el método que consulta qué eliminar. Este estudiante tiene 3 inscripciones, el REFERENCE
problema de restricción ocurre cuando se elimina la tercera inscripción. Supongo que EF está utilizando una transacción porque las inscripciones no se eliminan.
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.
fuente
Sin leer todo, solo del diagrama: tiene una entrada en Inscripción o una en GPA que apunta al Estudiante que desea eliminar.
Las entradas con las claves foráneas deben eliminarse primero (o las claves deben configurarse como nulas, pero eso es una mala práctica) antes de que pueda eliminar la entrada del Estudiante.
Además, algunas bases de datos tienen ON DELETE CASCADE, que eliminará cualquier entrada con claves externas a la que desea eliminar.
Otra forma es no declararlas como claves foráneas y solo usar el valor de la clave, pero eso tampoco se recomienda.
fuente
ON DELETE CASCADE
declaraciones. Ninguna de estas declaraciones de creación de tablas, ni las declaraciones de eliminación están escritas a mano, todas son generadas por el marco de la entidad. Las cascadas se deben a que la inscripción tiene claves externas que no son su clave principal; La restricción de clave externa de GPA es su clave principal, por lo que no debería necesitar una cascada. He probado esto, si eliminas a un estudiante con una entrada de tabla de GPA, la entrada se elimina. El único problema es un estudiante con matrículas pero sin gpa.