Memperkenalkan kendala KUNCI ASING dapat menyebabkan siklus atau beberapa jalur kaskade - mengapa?

295

Saya sudah bergumul dengan hal ini untuk sementara waktu dan tidak tahu apa yang terjadi. Saya memiliki entitas Kartu yang mengandung Sisi (biasanya 2) - dan kedua Kartu dan Sisi memiliki Panggung. Saya menggunakan migrasi EF Codefirst dan migrasi gagal dengan kesalahan ini:

Memperkenalkan kendala KUNCI ASING 'FK_dbo.Sides_dbo.Cards_CardId' pada tabel 'Sisi' dapat menyebabkan siklus atau beberapa jalur kaskade. Tentukan HAPUS TANPA TINDAKAN atau DIPERBARUI TANPA TINDAKAN, atau ubah batasan KUNCI ASING lainnya.

Inilah entitas Kartu saya :

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

Ini milik saya entitas sisi :

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

}

Dan ini milik saya entitas Panggung :

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

Apa yang aneh adalah bahwa jika saya menambahkan yang berikut ke kelas Stage saya:

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

Migrasi berjalan dengan sukses. Jika saya membuka SSMS dan melihat tabel, saya bisa melihatnyaStage_StageId telah ditambahkan ke Cards(seperti yang diharapkan / diinginkan), namun Sidestidak mengandung referensi ke Stage(tidak diharapkan).

Jika saya tambahkan

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

Untuk kelas sampingku, begitu StageId kolom ditambahkan ke Sidemeja saya .

Ini berfungsi, tetapi sekarang di seluruh aplikasi saya, referensi apa pun yang Stagemengandung SideId, yang dalam beberapa kasus sama sekali tidak relevan. Saya ingin memberikan properti Carddan Sideentitas saya Stageberdasarkan kelas Stage di atas tanpa mencemari kelas stage dengan properti referensi jika memungkinkan ... apa yang saya lakukan salah?

SB2055
sumber
7
Nonaktifkan cascading delete dengan mengizinkan nilai nol di referensi ... jadi di SideClass tambahkan integer Nullable dan hapus [Required]atribut =>public int? CardId { get; set; }
Jaider
2
Di EF Core, Anda harus menonaktifkan penghapusan kaskade dengan DeleteBehavior.Restrictatau DeleteBehavior.SetNull.
Sina Lotfi

Jawaban:

371

Karena Stageyang diperlukan , semua hubungan satu-ke-banyak mana Stageyang terlibat akan memiliki Cascading menghapus diaktifkan secara default. Artinya, jika Anda menghapus suatu Stageentitas

  • penghapusan akan langsung mengalir ke Side
  • hapus akan mengalir langsung ke Carddan karena Carddan Sidememiliki hubungan satu-ke-banyak yang diperlukan dengan penghapusan kaskade diaktifkan secara default lagi maka kaskade akan mengalir dari CardkeSide

Jadi, Anda memiliki dua path penghapusan cascading dari StagekeSide - yang menyebabkan pengecualian.

Anda harus membuat Stageopsional setidaknya di salah satu entitas (yaitu menghapus [Required]atribut dari Stageproperti) atau menonaktifkan penghapusan cascading dengan Fluent API (tidak mungkin dengan anotasi data):

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

modelBuilder.Entity<Side>()
    .HasRequired(s => s.Stage)
    .WithMany()
    .WillCascadeOnDelete(false);
Slauma
sumber
2
Terima kasih Slauma. Jika saya menggunakan API lancar seperti yang telah Anda tunjukkan di atas, akankah bidang lain mempertahankan perilaku penghapusan kaskade mereka? Saya masih perlu Sisi untuk dihapus ketika kartu dihapus, misalnya.
SB2055
1
@ SB2055: Ya, itu hanya akan memengaruhi hubungan dari Stage. Hubungan lainnya tetap tidak berubah.
Slauma
2
Apakah ada cara untuk mengetahui properti yang menyebabkan kesalahan? Saya mengalami masalah yang sama, dan melihat kelas saya, saya tidak bisa melihat di mana siklusnya
Rodrigo Juarez
4
Apakah ini batasan dalam implementasinya? Tampak baik bagi saya untuk Stagepenghapusan turun ke Sidelangsung dan melaluiCard
aaaaaa
1
Misalkan kita menetapkan CascadeOnDelete ke false. Kemudian kami menghapus catatan panggung yang terkait dengan salah satu catatan Kartu. Apa yang terjadi dengan Card.Stage (FK)? Apakah tetap sama? atau apakah diatur ke Null?
ninbit
61

Saya punya meja yang memiliki hubungan melingkar dengan orang lain dan saya mendapatkan kesalahan yang sama. Ternyata ini tentang kunci asing yang tidak dapat dibatalkan. Jika kunci tidak dapat dibatalkan, obyek terkait harus dihapus dan relasi melingkar tidak mengizinkannya. Jadi gunakan kunci asing yang dapat dibatalkan.

[ForeignKey("StageId")]
public virtual Stage Stage { get; set; }
public int? StageId { get; set; }
Cem Mutlu
sumber
5
Saya menghapus tag [Wajib] tetapi yang penting adalah untuk menggunakan int?alih-alih intmembiarkannya menjadi nol.
VSB
1
Saya mencoba berbagai cara untuk mematikan penghapusan kaskade dan tidak ada yang berhasil - ini memperbaikinya!
ambog36
5
Anda seharusnya tidak melakukan ini jika Anda tidak ingin mengizinkan Stage disetel ke null (Stage adalah bidang wajib dalam pertanyaan awal).
cfwall
35

Siapa pun yang bertanya-tanya bagaimana melakukannya di inti EF:

      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.....
Nexus23
sumber
3
Itu akan mematikan penghapusan kaskade pada semua hubungan. Hapus kaskade mungkin fitur yang diinginkan untuk beberapa kasus penggunaan.
Blaze
15
Atau,builder.HasOne(x => x.Stage).WithMany().HasForeignKey(x => x.StageId).OnDelete(DeleteBehavior.Restrict);
Biskuit
@Biut Entah metode ekstensi berubah dari waktu ke waktu atau Anda lupa builder _ .Entity<TEntity>() _sebelumnya HasOne() dapat dipanggil ...
ViRuSTriNiTy
1
@ViRuSTriNiTy, cuplikan saya berusia 2 tahun. Tapi, saya pikir Anda benar - saat ini adalah saat Anda memilih untuk mengimplementasikan IEntityTypeConfiguration<T>. Saya tidak ingat melihat builder.Entity<T>metode itu hari itu, tetapi saya bisa saja salah. Meskipun demikian, keduanya akan bekerja :)
Biskuit
21

Saya mendapatkan kesalahan ini untuk banyak entitas ketika saya bermigrasi dari model EF7 ke versi EF6. Saya tidak ingin harus melalui setiap entitas satu per satu, jadi saya menggunakan:

builder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
builder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Sean
sumber
2
Ini harus ditambahkan di kelas (es) yang mewarisi dari DbContext misalnya dalam metode OnModelCreating. Builder adalah tipe DbModelBuilder
CodingYourLife
Ini bekerja untuk saya; .NET 4.7, EF 6. Satu batu sandungan adalah saya mendapatkan kesalahan, jadi ketika saya membuat ulang dengan skrip migrasi dengan konvensi ini dihapus, itu tidak MENDAPAT untuk membantu. Menjalankan "Add-Migration" dengan "-Force" menghapus semuanya, dan membangunnya kembali termasuk konvensi ini di atas. Masalah terpecahkan ...
James Joyce
Itu tidak ada di .net core, ada yang setara?
jjxtra
20

Anda dapat mengatur cascadeDelete ke false atau true (dalam migrasi Anda metode Up ()). Tergantung pada kebutuhan Anda.

AddForeignKey("dbo.Stories", "StatusId", "dbo.Status", "StatusID", cascadeDelete: false);
Musakkhir Sayyed
sumber
2
@Mussakkhir terima kasih atas jawaban Anda. Cara Anda sangat elegan dan lebih - ini lebih akurat dan ditargetkan langsung ke masalah yang saya hadapi!
Nozim Turakulov
Hanya saja, jangan lupa UPmetode ini dapat dimodifikasi oleh operasi eksternal.
Demensik
8

Di .NET Core saya mengubah opsi onDelete menjadi ReferencialAction.NoAction

         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);
            });
Mike Jones
sumber
7

Saya memiliki masalah ini juga, saya langsung menyelesaikannya dengan jawaban ini dari utas yang sama

Dalam kasus saya, saya tidak ingin menghapus catatan tergantung pada penghapusan kunci. Jika hal ini terjadi dalam situasi Anda, cukup ubah nilai Boolean dalam migrasi menjadi false:

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

Kemungkinannya adalah, jika Anda membuat hubungan yang membuat kesalahan kompiler ini tetapi DO ingin mempertahankan cascade delete; Anda memiliki masalah dengan hubungan Anda.

jonc.js
sumber
6

Saya memperbaiki ini. Saat Anda menambahkan migrasi, dalam metode Naik () akan ada baris seperti ini:

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

Jika Anda hanya menghapus cascadeDelete dari akhir itu akan berhasil.

Usman Khan
sumber
5

Hanya untuk keperluan dokumentasi, untuk seseorang yang datang di masa depan, hal ini dapat diselesaikan sesederhana ini, dan dengan metode ini, Anda dapat melakukan metode yang dinonaktifkan satu kali, dan Anda dapat mengakses metode Anda secara normal

Tambahkan metode ini ke kelas basis data konteks:

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

Ini terdengar aneh dan saya tidak tahu mengapa, tetapi dalam kasus saya itu terjadi karena ConnectionString saya menggunakan "." dalam atribut "sumber data". Setelah saya mengubahnya menjadi "localhost" itu bekerja seperti pesona. Tidak ada perubahan lain yang diperlukan.

Marco Alves
sumber
1

Dalam .NET Core saya bermain dengan semua jawaban atas - tetapi tanpa hasil. Saya membuat banyak perubahan dalam struktur DB dan setiap kali menambahkan upaya migrasi baru update-database, tetapi menerima kesalahan yang sama.

Kemudian saya mulai remove-migrationsatu per satu sampai Package Manager Console memberi saya pengecualian:

Migrasi '20170827183131 _ ***' telah diterapkan ke basis data

Setelah itu, saya menambahkan migrasi baru ( add-migration) dan update-database berhasil

Jadi saran saya adalah: hapus semua migrasi temporer Anda, hingga status DB Anda saat ini.

rock_walker
sumber
1

Jawaban yang ada sangat bagus. Saya hanya ingin menambahkan bahwa saya mengalami kesalahan ini karena alasan yang berbeda. Saya ingin membuat migrasi EF awal pada DB yang sudah ada tetapi saya tidak menggunakan -IgnoreChanges flag dan menerapkan perintah Update-Database pada Database yang kosong (juga gagal yang ada).

Sebaliknya saya harus menjalankan perintah ini ketika struktur db saat ini adalah yang sekarang:

Add-Migration Initial -IgnoreChanges

Mungkin ada masalah nyata dalam struktur db tetapi menyelamatkan dunia selangkah demi selangkah ...

CodingYourLife
sumber
1

Cara sederhana adalah dengan, Sunting berkas migrasi (cascadeDelete: true)ke (cascadeDelete: false)kemudian setelah menetapkan perintah Update-Database di Anda Package Manager Console.if masalah itu dengan migrasi terakhir Anda kemudian kanan semua. Kalau tidak, periksa riwayat migrasi Anda sebelumnya, salin hal-hal itu, tempel ke file migrasi terakhir Anda, setelah itu lakukan hal yang sama. itu sangat cocok untuk saya.

Niroshan Kumarasamy
sumber
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");
    }
}

Ketika migrasi Anda gagal, Anda diberikan beberapa opsi: 'Memperkenalkan kendala KUNCI ASING' FK_dbo.Rekomendasi Book_dbo.Department_DepartmentID 'pada tabel' RecommendedBook 'dapat menyebabkan siklus atau beberapa jalur kaskade. Tentukan HAPUS TANPA TINDAKAN atau DIPERBARUI TANPA TINDAKAN, atau ubah batasan KUNCI ASING lainnya. Tidak dapat membuat batasan atau indeks. Lihat kesalahan sebelumnya. '

Berikut adalah contoh penggunaan 'modifikasi batasan ASE lainnya' dengan menetapkan 'cascadeDelete' menjadi false dalam file migrasi dan kemudian jalankan 'update-database'.

Christopher Govender
sumber
0

Tidak ada solusi yang disebutkan di atas yang berhasil untuk saya. Apa yang harus saya lakukan adalah menggunakan int nullable (int?) Pada kunci asing yang tidak diperlukan (atau bukan kunci kolom bukan nol) dan kemudian menghapus beberapa migrasi saya.

Mulai dengan menghapus migrasi, lalu coba int nullable.

Masalahnya adalah modifikasi dan desain model. Tidak diperlukan perubahan kode.

Ayson Baxter
sumber
-1

Buat atribut kunci Asing Anda nullable. Itu akan bekerja.

Umair Javed
sumber
1
bahwa jawaban dalam komentar di bawah pertanyaan tolong
uraikan di