FOREIGN KEY kısıtlamasına giriş, döngülere veya çoklu basamaklı yollara neden olabilir - neden?


295

Bir süredir bununla güreşiyorum ve neler olduğunu tam olarak anlayamıyorum. Taraflar (genellikle 2) içeren bir Kart varlığım var ve hem Kartların hem de Tarafların bir Sahne Alanı var. EF Codefirst geçişlerini kullanıyorum ve geçişler bu hatayla başarısız oluyor:

'Taraflar' tablosundaki 'FK_dbo.Sides_dbo.Cards_CardId' FOREIGN KEY kısıtlamasına giriş, döngülere veya çoklu basamaklı yollara 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.

İşte Kart varlığım:

public class Card
{
    public Card()
    {
        Sides = new Collection<Side>();
        Stage = Stage.ONE;
    }

    [Key]
    [Required]
    public virtual int CardId { get; set; }

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    [ForeignKey("CardId")]
    public virtual ICollection<Side> Sides { get; set; }
}

İşte benim Side varlığım:

public class Side
{
    public Side()
    {
        Stage = Stage.ONE;
    }

    [Key]
    [Required]     
    public virtual int SideId { get; set; } 

    [Required]
    public virtual Stage Stage { get; set; }

    [Required]
    public int CardId { get; set; }

    [ForeignKey("CardId")]
    public virtual Card Card { get; set; }

}

Ve işte Sahne varlığım:

public class Stage
{
    // Zero
    public static readonly Stage ONE = new Stage(new TimeSpan(0, 0, 0), "ONE");
    // Ten seconds
    public static readonly Stage TWO = new Stage(new TimeSpan(0, 0, 10), "TWO");

    public static IEnumerable<Stage> Values
    {
        get
        {
            yield return ONE;
            yield return TWO;
        }

    }

    public int StageId { get; set; }
    private readonly TimeSpan span;
    public string Title { get; set; }

    Stage(TimeSpan span, string title)
    {
        this.span = span;
        this.Title = title;
    }

    public TimeSpan Span { get { return span; } }
}

Garip olan şey, Stage sınıfıma aşağıdakileri eklersem:

    public int? SideId { get; set; }
    [ForeignKey("SideId")]
    public virtual Side Side { get; set; }

Taşıma başarıyla çalışır. SSMS'yi açar ve tablolara bakarsam, bunun Stage_StageIdeklendiğini Cards(beklendiği / istendiği gibi) görebilirim, ancak (beklenmeyen) Sidesreferans içermez Stage.

Eğer eklersem

    [Required]
    [ForeignKey("StageId")]
    public virtual Stage Stage { get; set; }
    public int StageId { get; set; }

Side sınıfıma, StageIdtabloma eklenen sütunu görüyorum Side.

Bu çalışıyor, ama şimdi benim uygulama boyunca, herhangi bir referans Stagebir içerdiği SideIdbazı durumlarda tamamen ilgisiz olan. Sadece vermek istiyorum Cardve Sidekuruluşlar bir Stagereferans özelliklerine mümkünse sahne sınıfını kirletmeden Sahne yukarıdaki sınıfını esas özelliği ... Ben yanlış yapıyorum?


7
Referanslarda null değerlere izin vererek basamaklı silme Side[Required]public int? CardId { get; set; }
işlevini devre dışı

2
EF Çekirdeğinde, DeleteBehavior.Restrictveya ile basamaklı silmeyi devre dışı bırakmalısınız DeleteBehavior.SetNull.
Sina Lotfi

Yanıtlar:


371

Gerekli olduğundan Stage, dahil olan tüm bire çok ilişkilerinde varsayılan olarak basamaklı silme etkinleştirilir. Bir varlığı silerseniz,StageStage

  • silme doğrudan Side
  • Silme doğrudan basamakla Cardve çünkü Cardve Sidesahip bir silme yine o andan itibaren basamakla varsayılan olarak etkin basamaklı ile bire çok ilişki gereklidir CardiçinSide

Yani, iki basamaklı silme yollara sahip Stageüzere Sideözel duruma neden olur -.

Varlıkları Stageen az bir tanesinde isteğe bağlı yapmalısınız (yani [Required]özelliği özelliklerden kaldırmalısınız Stage) veya Akıcı API ile basamaklı silmeyi devre dışı bırakmalısınız (veri ek açıklamalarıyla mümkün değildir):

modelBuilder.Entity<Card>()
    .HasRequired(c => c.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);

2
Teşekkürler Slauma. Yukarıda gösterdiğin gibi akıcı API kullanırsam, diğer alanlar basamaklı silme davranışlarını korur mu? Örneğin kartlar silindiğinde tarafların silinmesine hala ihtiyacım var.
SB2055

1
@ SB2055: Evet, sadece arasındaki ilişkileri etkileyecek Stage. Diğer ilişkiler değişmeden kalır.
Slauma

2
Hangi özelliklerin hataya neden olduğunu bilmenin bir yolu var mı? Aynı sorunu yaşıyorum ve derslerime baktığımda döngünün nerede olduğunu göremiyorum
Rodrigo Juarez

4
Bu, uygulamalarında bir sınırlama mıdır? Hem doğrudan hem de üzerinden Stageaşağıya doğru bir silme için bir silme bana iyi görünüyorSideCard
aaaaaa

1
Diyelim ki CascadeOnDelete değerini false olarak ayarladık. Sonra Kart kayıtlarından biriyle ilgili bir sahne kaydını kaldırdık. Card.Stage'e (FK) ne olur? Aynı mı kalıyor? yoksa Null olarak mı ayarlanmış?
ninbit

61

Başkaları ile dairesel bir ilişkisi olan bir tablo vardı ve aynı hatayı alıyordum. Null olmayan yabancı anahtarla ilgili olduğu ortaya çıktı. Anahtar boş bırakılamazsa ilgili nesne silinmeli ve döngüsel ilişkiler buna izin vermez. Bu nedenle nullable yabancı anahtar kullanın.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }

5
[Gerekli] etiketini kaldırdım, ancak önemli başka bir şey, boş değerli olmasına izin vermek int?yerine kullanmaktı int.
VSB

1
Art arda sıralı silmeyi kapatmanın birçok farklı yolunu denedim ve hiçbir şey işe yaramadı - bu düzeltildi!
ambog36

5
Sahne Alanı'nın boş olarak ayarlanmasına izin vermek istemiyorsanız bunu yapmamalısınız (Sahne Alanı orijinal soruda zorunlu bir alandır).
cfwall

35

EF çekirdeğinde nasıl yapılacağını merak eden herkes:

      protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
                {
                    relationship.DeleteBehavior = DeleteBehavior.Restrict;
                }
           ..... rest of the code.....

3
Bu, tüm ilişkilerde basamaklı silme işlemini kapatır. Kademeli silme, bazı kullanım durumları için istenen bir özellik olabilir.
Blaze

15
Alternatif olarak,builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Bisküvi

@Biscuits Ya uzatma yöntemleri zamanla değişti ya da daha builder _ .Entity<TEntity>() _önce unuttun HasOne() ...
ViRuSTriNiTy

1
@ViRuSTriNiTy, snippet'im 2 yaşında. Ama bence haklısın - bugünlerde uygulamayı seçtiğin zamanlar olurdu IEntityTypeConfiguration<T>. builder.Entity<T>O günlerde yöntemi gördüğümü hatırlamıyorum , ama yanılmış olabilirim. Yine de, ikisi de çalışacak :)
Bisküvi

21

Bu hatayı EF7 modelinden EF6 sürümüne geçirirken birçok varlık için alıyordum. Her bir varlığa birer birer gitmek istemedim, bu yüzden kullandım:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

2
Bu, örneğin OnModelCreating yönteminde DbContext'ten devralan sınıf (lar) a eklenmelidir. Oluşturucu DbModelBuilder tipindedir
CodingYourLife

Bu benim için çalıştı; .NET 4.7, EF 6. Bir engelleme hatayı aldım, bu yüzden bu kurallar kaldırıldığında geçiş komut dosyası tarafından yeniden oluşturulduğumda, yardımcı olmak için APPEAR yoktu. "Add-Migration" ı "-Force" ile çalıştırmak her şeyi temizledi ve yukarıdaki bu sözleşmeler dahil olmak üzere yeniden oluşturdu. Sorun çözüldü ...
James Joyce

Bunlar .net çekirdeğinde bulunmuyor mu?
jjxtra


20

CascadeDelete öğesini false veya true olarak ayarlayabilirsiniz (geçiş Up () yönteminizde). Sizin ihtiyacına bağlıdır.

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);

2
@Mussakkhir cevap verdiğiniz için teşekkür ederim. Yolunuz çok zarif ve daha fazla - daha doğru ve doğrudan karşılaştığım soruna yönelik!
Nozim Turakulov

Sadece UPdış operasyonlar tarafından değiştirilebileceğini unutmayın .
Dementic

8

.NET Core'da onDelete seçeneğini ReferencialAction.NoAction olarak değiştirdim

         constraints: table =>
            {
                table.PrimaryKey("PK_Schedule", x => x.Id);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_HomeId",
                    column: x => x.HomeId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
                table.ForeignKey(
                    name: "FK_Schedule_Teams_VisitorId",
                    column: x => x.VisitorId,
                    principalTable: "Teams",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.NoAction);
            });

7

Ben de bu sorunu vardı, ben benzer bir konu bu cevap ile anında çözüldü

Benim durumumda, anahtar silme sırasında bağımlı kaydı silmek istemedim. Durumunuz buysa, taşıma işlemindeki Boole değerini false olarak değiştirmeniz yeterlidir:

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);

Bu derleyici hatası atmak ancak basamaklı silme korumak istiyor musunuz ilişkiler oluşturuyorsanız, büyük olasılıkla; ilişkilerinizde bir sorun var.


6

Bunu düzelttim. Geçişi eklediğinizde, Up () yönteminde şöyle bir satır olacaktır:

.ForeignKey("dbo.Members", t => t.MemberId, cascadeDelete:True)

Eğer sadece cascadeDelete öğesini sondan silerseniz çalışır.


5

Sadece dokümantasyon amacıyla, gelecekteki birisine, bu şey bu kadar basit bir şekilde çözülebilir ve bu yöntemle, bir kez devre dışı bırakan bir yöntem yapabilir ve yönteminize normal olarak erişebilirsiniz

Bu yöntemi bağlam veritabanı sınıfına ekleyin:

protected override void OnModelCreating(DbModelBuilder modelBuilder) {
    modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}

1

Kulağa garip geliyor ve nedenini bilmiyorum, ancak benim durumumda bu gerçekleşti çünkü ConnectionString'im "." "veri kaynağı" özelliğinde. Bir kez "localhost" olarak değiştirdi bir cazibe gibi çalıştı. Başka bir değişikliğe gerek yoktu.


1

In .NET Çekirdek Bütün üst cevapları ile oynanan - fakat başarılı. DB yapısında çok fazla değişiklik yaptım ve her seferinde yeni geçiş yapmaya çalıştım update-database, ancak aynı hatayı aldım .

Sonra Paket Yöneticisi Konsolu beni istisna atıncaya remove-migrationkadar tek tek başladım :

'20170827183131 _ ***' taşıma zaten veritabanına uygulandı

Bundan sonra yeni geçiş ( add-migration) ekledim ve update-database başarıyla

Benim önerim şudur: mevcut DB durumunuza kadar tüm geçici geçişlerinizi temizleyin.


1

Mevcut cevaplar harika, sadece farklı bir nedenden dolayı bu hatayla karşılaştığımı eklemek istedim. Varolan bir DB üzerinde bir ilk EF geçiş oluşturmak istedim ama -IgnoreChanges kullanmadım bayrağını boş bir veritabanına Güncelleştirme-Veritabanı komutunu uyguladı (ayrıca mevcut başarısız).

Bunun yerine, geçerli db yapısı geçerli olduğunda bu komutu çalıştırmak zorunda kaldı:

Add-Migration Initial -IgnoreChanges

Muhtemelen db yapısında gerçek bir sorun var ama dünyayı birer birer kurtarın ...


1

Bunun basit yolu, Paket Yöneticisi Konsolunuzda Güncelleme-Veritabanı komutunu atadıktan sonra geçiş dosyanızı (cascadeDelete: true)düzenlemek (cascadeDelete: false)ve son taşıma işleminizle ilgili sorun varsa. Aksi takdirde, önceki taşıma geçmişinizi kontrol edin, bunları kopyalayın, son taşıma dosyanıza yapıştırın, daha sonra aynı şeyi yapın. benim için mükemmel çalışıyor.


1
public partial class recommended_books : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.RecommendedBook",
            c => new
                {
                    RecommendedBookID = c.Int(nullable: false, identity: true),
                    CourseID = c.Int(nullable: false),
                    DepartmentID = c.Int(nullable: false),
                    Title = c.String(),
                    Author = c.String(),
                    PublicationDate = c.DateTime(nullable: false),
                })
            .PrimaryKey(t => t.RecommendedBookID)
            .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: false) // was true on migration
            .ForeignKey("dbo.Department", t => t.DepartmentID, cascadeDelete: false) // was true on migration
            .Index(t => t.CourseID)
            .Index(t => t.DepartmentID);

    }

    public override void Down()
    {
        DropForeignKey("dbo.RecommendedBook", "DepartmentID", "dbo.Department");
        DropForeignKey("dbo.RecommendedBook", "CourseID", "dbo.Course");
        DropIndex("dbo.RecommendedBook", new[] { "DepartmentID" });
        DropIndex("dbo.RecommendedBook", new[] { "CourseID" });
        DropTable("dbo.RecommendedBook");
    }
}

Taşıma işleminiz başarısız olduğunda size birkaç seçenek sunulur: 'ÖnerilenKitap' tablosundaki 'FOREIGN KEY kısıtlamasına giriş' FK_dbo.RecommendedBook_dbo.Department_DepartmentID 'döngü 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. Kısıtlama veya dizin oluşturulamadı. Önceki hatalara bakın. '

Burada, geçiş dosyasında 'cascadeDelete' öğesini false olarak ayarlayıp 'update-database' komutunu çalıştırarak 'diğer FOREIGN KEY kısıtlamalarını değiştir' kullanımına bir örnek verilmiştir.


0

Yukarıda belirtilen çözümlerin hiçbiri benim için çalışmadı. Yapmam gereken yabancı anahtarda boş olmayan bir int (int?) Kullanmak (ya da boş bir sütun anahtarı değil) kullanmak ve ardından bazı geçişlerimi silmekti.

Taşıma işlemlerini silerek başlayın, ardından nullable int öğesini deneyin.

Sorun hem modifikasyon hem de model tasarımı idi. Kod değişikliği gerekli değildi.


-1

Yabancı anahtar özelliklerini geçersiz kılın. İşe yarayacak.


1
soruların altındaki yorumların cevabının lütfen burada ayrıntılı olarak ele alınmasını sağlayın
Kostia Mololkin
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.