yabancı anahtar kısıtlaması ihlali sorunu


10

3 durum belirledim.

  1. Kaydı olmayan bir öğrenci.
  2. Kayıtları olan ancak notları olmayan bir öğrenci.
  3. Kayıt ve notları olan bir öğrenci.

Kayıtlar tablosunda GPA'yı hesaplamak için bir tetikleyici vardır. Bir öğrencinin notları varsa not ortalamasına girer veya not ortalamasına girer; not yok, GPA tablo girişi yok.

Kaydı olmayan bir öğrenciyi silebilirim (# 1). Kayıtları ve notları olan bir öğrenciyi silebilirim (yukarıdaki # 3). Ancak kaydı olan ancak notu olmayan bir öğrenciyi silemiyorum (# 2). Referans kısıtlaması ihlali alıyorum.

DELETE deyimi "FK_dbo.GPA_dbo.Student_StudentID" REFERENCE kısıtlamasıyla çakıştı. Çakışma "", tablo "dbo.GPA", 'StudentID' sütununda meydana geldi.

Kayıt olmadan (ve not ortalaması girmeden) yeni bir öğrenciyi silemezsem, o zaman kısıtlama ihlalini anlardım, ancak o öğrenciyi silebilirim. Kayıtları olmayan ve silemediğim notları (ve hala GPA girişi olmayan) bir öğrenci.

İleri gidebilmem için tetiğimi yamaladım. Şimdi, kayıtlarınız varsa, tetikleyici ne olursa olsun sizi GPA tablosuna ekler. Ama altta yatan problemi anlamıyorum. Herhangi bir açıklama en çok takdir edilecektir.

Değeri için:

  1. Visual Studio 2013 Professional.
  2. IIS express (VS2013 için dahili).
  3. EntityFramework kullanan ASP.NET Web Uygulaması 6.1.1.
  4. MS SQL Server 2014 Kurumsal.
  5. GPA.Değer geçersiz.
  6. Enrollment.GradeID geçersizdir.

İşte bir veritabanı snippet'i:

veritabanı resmi

- DÜZENLE -

Tablolar tüm EntityFramework tarafından oluşturulur, bunları üretmek için SQL Server Management Studio kullandım.

İşte kısıtlamalar ile tablo ifadeleri oluşturma:

GPA tablosu:

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

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

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]

İşte tetikleyiciler :

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

İlerlemek için yama, AFTER INSERTtetikleyicideki bu satırları yorumlamaktı .

İşte saklı yordam :

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

Veritabanı işlevi şöyledir :

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

İşte denetleyicinin silme yönteminden hata ayıklama çıktısı, select deyimi neyi sileceğini sorgulayan yöntemdir. Bu öğrencinin 3 kaydı vardır, REFERENCEkısıtlama sorunu 3. kayıt silindiğinde ortaya çıkar. Kayıtların silinmediği için EF bir işlem kullanıyor.

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.

Yanıtlar:


7

Bu bir zamanlama meselesi. Öğrenci No 1'i silmeyi düşünün:

  1. Satır Studenttablodan silinir
  2. Basamaklama silme, ilgili satırları Enrollment
  3. Yabancı anahtar ilişkisi GPA-> Studentişaretlenir
  4. Tetikleyici ateş ediyor, MergeGPA

Bu noktada, tabloda MergeGPAÖğrenci # 1 için bir giriş olup olmadığını kontrol eder GPA. Yok (aksi takdirde 3. adımdaki FK kontrolü bir hata oluşturur).

Yani, WHEN NOT MATCHEDiçinde fıkra MergeGPAgirişimlerine INSERTde üst üste GPAStudentID 1. için. StudentID # 1 zaten Studenttablodan (1. adımda) silindiği için bu girişim başarısız olur (FK hatasıyla ).


1
Bence sen bir şeylerin var. Bir öğrenci kayıtlarla oluşturulmuş ancak hiçbir not atanmamışsa, o öğrencinin GPA tablosuna girişi yoktur. Veritabanı bu öğrenciyi silmeye gittiğinde veritabanına bakar, silinecek kayıtları görür ancak GPA girişi görmez. Böylece, GPA girişini oluşturan ve daha sonra kısıtlama ihlaline neden olan bir tetikleyicinin tetiklenmesine neden olan kayıtların silinmesine neden olur? Bu yüzden çözüm, bir öğrenci oluşturduğumda bir GPA girişi oluşturmaktır. Daha sonra ekleme tetikleyicimin koşullu olması gerekmez ve saklı yordamın birleştirme olması gerekmez, sadece bir güncelleme.
DowntownHippie

-1

Tümünü okumadan, sadece diyagramdan: Kayıtta veya GPA'da silmek istediğiniz Öğrenci'yi işaret eden bir girişiniz var.

Öğrenci girişini silebilmeniz için, önce yabancı anahtarlı girişlerin (veya null olarak ayarlanmış anahtarların silinmesi gerekir, ancak bu kötü bir uygulamadır).

Ayrıca bazı veritabanlarında, silmek istediğiniz veritabanına yabancı anahtar içeren tüm girişleri silen ON DELETE CASCADE vardır.

Başka bir yol onları yabancı anahtar olarak tanımlamamak ve yalnızca anahtar değerini kullanmaktır, ancak bu da yeniden düzenlenmez.


Başarısız olduğu durumlarda, Kayıt'ta bir giriş vardır ancak GPA'da bir giriş yoktur.
DowntownHippie

ON DELCE CASCADE ile bazı kısıtlamalarınız var. bu satırı tüm kısıtlamalara eklemeyi deneyin. Bundan sonra tüm tetikleyicileri devre dışı bırakmayı deneyebilir ve bu testten sonra minimum kurulumla. iyi şanslar
user44286

Bu ON DELETE CASCADEifadeleri görüyorum . Bu tablo oluşturma deyimlerinin hiçbiri veya silme deyimleri elle yazılmaz, hepsi varlık çerçevesi tarafından oluşturulur. Kaskadlar, kayıt işleminin birincil anahtar olmayan yabancı anahtarlara sahip olması; GPA'nın yabancı anahtar kısıtı birincil anahtardır, bu yüzden bir kaskad gerektirmez. Bunu test ettim, GPA tablosu girişine sahip bir öğrenciyi silerseniz giriş silinir. Tek sorun kaydı olan ama gpa olmayan bir öğrencidir.
DowntownHippie
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.