Yabancı anahtar kısıtı döngülere veya birden çok basamaklı yola neden olabilir mi?


176

Tablolarıma kısıtlama eklemeye çalıştığımda bir sorunum var. Hatayı alıyorum:

'Çalışanlar' tablosundaki YABANCI ANAHTAR kısıtlaması 'FK74988DB24B3C886' ile giriş, döngülere veya birden çok basamaklı yola neden olabilir. AÇIK SİLME EYLEMİ SİLME veya GÜNCELLEŞTİRME EYLEMİ YOK belirtin veya diğer YABANCI TUŞ kısıtlamalarını değiştirin.

Benim kısıtlamam bir Codetablo ile bir employeetablo arasında. CodeMasa içeren Id, Name, FriendlyName, Typeve Value. Her employeebir kod türü için bir referans olabilmesi için, kodlara başvuran bir dizi alan vardır.

Başvurulan kod silinirse, alanların null olarak ayarlanması gerekir.

Bunu nasıl yapabileceğime dair bir fikrin var mı?


Çözümlerden biri burada
IsmailS

Yanıtlar:


180

SQL Server, basamaklı yolların basit sayımını yapar ve herhangi bir döngünün gerçekten var olup olmadığını anlamaya çalışmak yerine, en kötüsünü alır ve referans eylemleri (CASCADE) oluşturmayı reddeder: referans eylemleri olmadan kısıtlamalar oluşturabilir ve oluşturmalısınız. Tasarımınızı değiştiremiyorsanız (veya bir şeyleri tehlikeye atacaksa), tetikleyicileri son çare olarak kullanmayı düşünmelisiniz.

FWIW basamaklı yolları çözümlemek karmaşık bir sorundur. Diğer SQL ürünleri sorunu görmezden gelir ve döngüler oluşturmanıza izin verir, bu durumda, muhtemelen tasarımcının bilgisizliğine (örneğin ACE / Jet bunu yapar) son değerin üzerine yazacağını görmek için bir yarış olacaktır. Bazı SQL ürünlerinin basit vakaları çözmeye çalışacağını anlıyorum. Gerçek şu ki, SQL Server denemiyor, birden fazla yola izin vermeyerek son derece güvenli oynuyor ve en azından size söylüyor.

Microsoft , FK kısıtlamaları yerine tetikleyicilerin kullanılmasını önerir .


2
hala anlayamıyorum bir şey, eğer bu "sorun" bir tetikleyici kullanılarak çözülebilir, o zaman nasıl bir tetikleyici "döngüleri veya birden çok basamaklı yol neden ..." olmayacak?
armen

5
@armen: tetikleyiciniz, sistemin örtülü olarak kendi başına çözemediği mantığını açıklayacağından, örneğin bir silme referans eylemi için birden fazla yol varsa, tetik kodunuz hangi tabloların ve hangi sırayla silineceğini tanımlayacaktır.
oneday5

6
Ayrıca, ilk işlem tamamlandıktan sonra tetikleyici yürütülür, böylece yarış devam etmez.
Bon

2
@dumbledad: Yani, sadece kısıtlamalar (belki kombinasyon halinde) işi yapamazsa tetikleyiciler kullanın. Kısıtlamalar beyan edicidir ve uygulamaları sistemin sorumluluğundadır. Tetikleyiciler yordamsal koddur ve uygulamayı kodlamanız (ve hata ayıklamanız) ve dezavantajlarına (kötü performans vb.) Katlanmanız gerekir.
oneday

1
Bununla ilgili sorun, tetikleyicinin yalnızca yabancı anahtar kısıtlamasını kaldırdığınız sürece çalışmasıdır, bu da veritabanı eklerinde herhangi bir referans bütünlük denetimine sahip olmadığınız anlamına gelir ve bu nedenle bunu işlemek için daha fazla tetikleyiciye ihtiyacınız vardır. Tetikleyici çözüm, dejenere bir veritabanı tasarımına yol açan bir tavşan deliğidir.
Neutrino

99

Birden çok yayınlama yolu olan tipik bir durum şu şekildedir: İki ayrıntıya sahip bir ana tablo, diyelim "Master" ve "Detail1" ve "Detail2". Her iki ayrıntı da kademeli olarak silinir. Şimdiye kadar sorun yok. Ancak, her iki detayın başka bir tabloyla bire çok ilişkisi varsa ("SomeOtherTable" deyin). SomeOtherTable bir Detail1ID sütunu VE bir Detail2ID sütunu vardır.

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

Başka bir deyişle: SomeOtherTable'daki bazı kayıtlar Detail1 kayıtlarıyla, SomeOtherTable'daki bazı kayıtlar ise Detail2 kayıtlarıyla bağlantılıdır. SomeOtherTable-kayıtlarının hiçbir zaman iki Ayrıntılar'a ait olmadığı garanti edilse bile, Master'dan SomeOtherTable'a (bir Detay1 ile bir ve Detay2 ile bir) birden fazla basamaklı yol olduğu için, SomeOhterTable'ın kayıtlarının her iki ayrıntı için kademeli olarak silinmesi imkansızdır. Şimdi bunu zaten anlamış olabilirsiniz. İşte olası bir çözüm:

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

Tüm kimlik alanları anahtar alanlar ve otomatik artışlardır. Crux, Detay tablolarının DetailMainId alanlarındadır. Bu alanlar hem kilit hem de referans kontraintidir. Artık yalnızca ana kayıtları silerek her şeyi kademeli olarak silmek mümkündür. Dezavantajı, her bir detay1-kaydı VE her bir detay2 kaydı için, ayrıca bir DetailMain-kaydı (doğru ve benzersiz kimliği elde etmek için ilk önce yaratılmış) olması gerektiğidir.


1
Yorumunuz, karşılaştığım sorunu anlamama yardımcı oldu. Teşekkür ederim! Yollardan biri için basamaklı silme özelliğini kapatmayı, sonra diğer kayıtların silinmesini başka yollarla (saklı yordamlar; tetikleyiciler; kod vb.) İşlemeyi tercih ederim. Ama aynı sorunun olası farklı uygulamaları için çözümünüzü (tek bir yolda gruplama) aklımda
tutuyorum

1
Crux kelimesinin kullanımı için bir tane (ve ayrıca açıklamak için)
masterwok

Bu tetikleyici yazmaktan daha mı iyi? Kaskadın çalışmasını sağlamak için ek bir tablo eklemek tuhaf görünüyor.
dumbledad

Her şey tetikleyici yazmaktan daha iyidir. Mantıkları opak ve diğer her şeyle karşılaştırıldığında verimsiz. Daha iyi kontrol için büyük tabloları daha küçük tablolara bölmek, daha iyi normalleştirilmiş bir veritabanının doğal bir sonucudur ve kendi başına ilgilenilecek bir şey değildir.
Neutrino

12

Ben (işlevsel olarak) SCHEMA ve VERİ döngüleri ve / veya çoklu yollar arasında BÜYÜK bir fark olduğunu işaret ediyorum. VERİ'deki döngüler ve belki de çoklu yolların işlenmesi kesinlikle karmaşık olabilir ve performans sorunlarına neden olabilir ("düzgün" işlem maliyeti), şemadaki bu özelliklerin maliyeti sıfıra yakın olmalıdır.

RDB'lerde en belirgin döngüler hiyerarşik yapılarda (org grafiği, bölüm, alt bölüm, vb.) Meydana geldiği için SQL Server'ın en kötüyü alması talihsizdir; yani, şema döngüsü == veri döngüsü. Aslında, RI kısıtlamaları kullanıyorsanız, verilerde gerçekten bir döngü oluşturamazsınız!

Çok yollu sorunun benzer olduğundan şüpheleniyorum; yani, şemadaki birden çok yol mutlaka verilerde birden çok yol anlamına gelmez, ancak çok yollu sorunla daha az deneyime sahibim.

SQL Server eğer Tabii ki hala 32 derinliğe konusu oluruz döngüsüne izin, ama muhtemelen çoğu durumda yeterli bu. (Ancak bu bir veritabanı ayarı değil çok kötü!)

"Sil yerine" tetikleyicileri de çalışmaz. Bir tablo ikinci kez ziyaret edildiğinde tetikleyici yoksayılır. Bu nedenle, gerçekten bir çağlayanı simüle etmek istiyorsanız, döngüler varlığında saklı yordamları kullanmanız gerekir. Sil yerine Tetikleyici, çok yollu durumlar için çalışır.

Celko, döngüleri tanımayan hiyerarşileri temsil etmenin "daha iyi" bir yolunu önerir, ancak ödünleşmeler vardır.


"RI kısıtlamaları kullanıyorsanız, verilerde bir döngü oluşturamazsınız!" -- iyi bir nokta!
oneday23-21

Tabii veri daireselliği inşa edebilirsiniz, ancak MSSQL ile sadece UPDATE kullanarak. Diğer RDBM'ler ertelenmiş kısıtlamaları destekler (ekleme / güncelleme / silme zamanında değil, taahhüt sırasında sağlanan bütünlük).
Carl Krig


3

Sesleriyle, mevcut Yabancı Anahtarlarınızdan birinde kodlar tablonuzu değiştirecek bir OnDelete / OnUpdate eyleminiz var.

Bu Yabancı Anahtarı yaratarak, döngüsel bir sorun yaratacaksınız,

Örneğin Çalışanların Güncellenmesi, Kodların Bir Güncelleme İşlemi ile değiştirilmesine neden olur, Çalışanların bir Güncelleme İşlemi ile değiştirilmesine neden olur ... vb ...

Her iki tablo için Tablo Tanımlarınızı ve Yabancı Anahtar / kısıtlama tanımlarınızı yayınlarsanız, sorunun nerede olduğunu söyleyebilmeliyiz ...


1
Oldukça uzunlar, bu yüzden onları buraya gönderebileceğimi sanmıyorum, ama yardımınızı çok takdir ediyorum - bunları size gönderebileceğim bir yol olup olmadığını bilmiyorum? Bunu denemeye ve açıklamaya çalışacağım: Varolan tek kısıtlama, basit bir INT Id anahtarıyla kodlara başvuran alanlara sahip 3 tablodan kaynaklanmaktadır. Sorun, çalışanın kod tablosuna başvuruda bulunan çeşitli alanlara sahip olması ve hepsinin SET NULL için basamaklandırılmasını istemek gibi görünüyor. Tek ihtiyacım olan kodlar silindiğinde, bunlara yapılan referanslar her yerde null değerine ayarlanmalıdır.

Neyse onları yayınla ... Burada kimsenin aklına gelmeyeceğini sanmıyorum ve kod penceresi onları kaydırma bloğunda düzgün bir şekilde biçimlendirecek :)
Eoin Campbell

2

Bunun nedeni, Emplyee'nin başka bir kurumun Koleksiyonunda Yeterlilikler ve Yeterliliklerin başka koleksiyon üniversiteleri olabileceğini söylemesi olabilir.

public class Employee{
public virtual ICollection<Qualification> Qualifications {get;set;}

}

public class Qualification{

public Employee Employee {get;set;}

public virtual ICollection<University> Universities {get;set;}

}

public class University{

public Qualification Qualification {get;set;}

}

DataContext üzerinde aşağıdaki gibi olabilir

protected override void OnModelCreating(DbModelBuilder modelBuilder){

modelBuilder.Entity<Qualification>().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications);
modelBuilder.Entity<University>.HasRequired(x => x.Qualification).WithMany(e => e.Universities);

}

bu durumda Çalışandan Yeterliliğe ve Yeterlilikten Üniversitelere kadar bir zincir vardır. Yani bana aynı istisnayı veriyordu.

Değiştiğimde benim için çalıştı

    modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications); 

için

    modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);

1

Tetikleyici bu sorunun çözümüdür:

IF OBJECT_ID('dbo.fktest2', 'U') IS NOT NULL
    drop table fktest2
IF OBJECT_ID('dbo.fktest1', 'U') IS NOT NULL
    drop table fktest1
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'fkTest1Trigger' AND type = 'TR')
    DROP TRIGGER dbo.fkTest1Trigger
go
create table fktest1 (id int primary key, anQId int identity)
go  
    create table fktest2 (id1 int, id2 int, anQId int identity,
        FOREIGN KEY (id1) REFERENCES fktest1 (id)
            ON DELETE CASCADE
            ON UPDATE CASCADE/*,    
        FOREIGN KEY (id2) REFERENCES fktest1 (id) this causes compile error so we have to use triggers
            ON DELETE CASCADE
            ON UPDATE CASCADE*/ 
            )
go

CREATE TRIGGER fkTest1Trigger
ON fkTest1
AFTER INSERT, UPDATE, DELETE
AS
    if @@ROWCOUNT = 0
        return
    set nocount on

    -- This code is replacement for foreign key cascade (auto update of field in destination table when its referenced primary key in source table changes.
    -- Compiler complains only when you use multiple cascased. It throws this compile error:
    -- Rrigger Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, 
    -- or modify other FOREIGN KEY constraints.
    IF ((UPDATE (id) and exists(select 1 from fktest1 A join deleted B on B.anqid = A.anqid where B.id <> A.id)))
    begin       
        update fktest2 set id2 = i.id
            from deleted d
            join fktest2 on d.id = fktest2.id2
            join inserted i on i.anqid = d.anqid        
    end         
    if exists (select 1 from deleted)       
        DELETE one FROM fktest2 one LEFT JOIN fktest1 two ON two.id = one.id2 where two.id is null -- drop all from dest table which are not in source table
GO

insert into fktest1 (id) values (1)
insert into fktest1 (id) values (2)
insert into fktest1 (id) values (3)

insert into fktest2 (id1, id2) values (1,1)
insert into fktest2 (id1, id2) values (2,2)
insert into fktest2 (id1, id2) values (1,3)

select * from fktest1
select * from fktest2

update fktest1 set id=11 where id=1
update fktest1 set id=22 where id=2
update fktest1 set id=33 where id=3
delete from fktest1 where id > 22

select * from fktest1
select * from fktest2

0

Bu, tür veritabanı tetikleme ilkelerinin bir hatadır. Bir tetikleyici koddur ve Kaskat Silme gibi bir Kaskat ilişkisine bazı zeka veya koşullar ekleyebilir. CascadeOnDelete'yi kapatma gibi ilgili tablo seçeneklerini uzmanlaşmanız gerekebilir :

protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
    modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false);
}

Veya bu özelliği tamamen kapatın:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

-2

ASP.NET Core 2.0 ve EF Core 2.0 kullanılarak karşılaşılan bu soruna yönelik çözümüm sırasıyla aşağıdakileri yapmaktı:

  1. update-databaseVeritabanını oluşturmak için Paket Yönetim Konsolu'nda (PMC) komutu çalıştırın (bu, "FOREIGN KEY kısıtlamasına giriş ... döngülere veya birden çok basamaklı yola neden olabilir." Hatasıyla sonuçlanır)

  2. Run script-migration -Idempotentmevcut tablolar / kısıtların bakılmaksızın çalıştırılabilir bir komut dosyası oluşturmak için PMC'de komut

  3. Ortaya çıkan komut dosyasını al ve bul ON DELETE CASCADEve değiştirON DELETE NO ACTION

  4. Değiştirilmiş SQL'i veritabanına karşı yürütün

Şimdi, taşıma işlemleriniz güncel olmalı ve basamaklı silmeler gerçekleşmemelidir.

Çok kötü Entity Framework Core 2.0'da bunu yapmanın bir yolunu bulamadım.

İyi şanslar!


Geçiş dosyanızı bunu yapmak için değiştirebilirsiniz (sql betiğini değiştirmeden), yani migration dosyasında, Cascade
Rushi Soni

Bunu, akıcı ek açıklamalar kullanarak belirtmek daha iyidir, böylece taşıma klasörünüzü silmeniz ve yeniden oluşturmanız durumunda bunu hatırlamanız gerekmez.
Allen Wang

Deneyimlerime göre, akıcı ek açıklamalar kullanılabilir ve kullanılmalıdır (onları kullanıyorum) ancak genellikle oldukça hatalıdırlar. Basitçe kodda belirtmek her zaman işe yaramaz beklenen sonucu üretmek.
user1477388
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.