Entity Framework Code First - dua Kunci Asing dari tabel yang sama

260

Saya baru saja mulai menggunakan kode EF terlebih dahulu, jadi saya adalah pemula total dalam topik ini.

Saya ingin membuat hubungan antara Tim dan Pertandingan:

1 pertandingan = 2 tim (rumah, tamu) dan hasil.

Saya pikir mudah untuk membuat model seperti itu, jadi saya mulai mengkodekan:

public class Team
{
    [Key]
    public int TeamId { get; set;} 
    public string Name { get; set; }

    public virtual ICollection<Match> Matches { get; set; }
}


public class Match
{
    [Key]
    public int MatchId { get; set; }

    [ForeignKey("HomeTeam"), Column(Order = 0)]
    public int HomeTeamId { get; set; }
    [ForeignKey("GuestTeam"), Column(Order = 1)]
    public int GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}

Dan saya mendapatkan pengecualian:

Hubungan referensial akan menghasilkan referensi siklus yang tidak diizinkan. [Kendala nama = Match_GuestTeam]

Bagaimana saya bisa membuat model seperti itu, dengan 2 kunci asing ke tabel yang sama?

Jarek
sumber

Jawaban:

297

Coba ini:

public class Team
{
    public int TeamId { get; set;} 
    public string Name { get; set; }

    public virtual ICollection<Match> HomeMatches { get; set; }
    public virtual ICollection<Match> AwayMatches { get; set; }
}

public class Match
{
    public int MatchId { get; set; }

    public int HomeTeamId { get; set; }
    public int GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}


public class Context : DbContext
{
    ...

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Match>()
                    .HasRequired(m => m.HomeTeam)
                    .WithMany(t => t.HomeMatches)
                    .HasForeignKey(m => m.HomeTeamId)
                    .WillCascadeOnDelete(false);

        modelBuilder.Entity<Match>()
                    .HasRequired(m => m.GuestTeam)
                    .WithMany(t => t.AwayMatches)
                    .HasForeignKey(m => m.GuestTeamId)
                    .WillCascadeOnDelete(false);
    }
}

Kunci primer dipetakan oleh konvensi standar. Tim harus memiliki dua koleksi pertandingan. Anda tidak dapat memiliki satu koleksi dirujuk oleh dua FK. Pencocokan dipetakan tanpa penghapusan cascading karena tidak berfungsi dalam referensi banyak-ke-banyak ini.

Ladislav Mrnka
sumber
3
Bagaimana jika dua tim diizinkan bermain hanya sekali?
ca9163d9
4
@NickW: Itu adalah sesuatu yang harus Anda tangani dalam aplikasi Anda dan bukan dalam pemetaan. Dari perspektif pemetaan, pasangan diperbolehkan bermain dua kali (masing-masing adalah tamu dan rumah satu kali).
Ladislav Mrnka
2
Saya memiliki model yang serupa. Apa cara yang tepat untuk menangani penghapusan kaskade jika tim dihapus? Saya melihat ke dalam menciptakan pemicu BUKAN HAPUS tetapi tidak yakin apakah ada solusi yang lebih baik? Saya lebih suka menangani ini di DB, bukan aplikasi.
Woodchipper
1
@ mrshickadance: Sama saja. Satu pendekatan menggunakan API yang lancar dan anotasi data lainnya.
Ladislav Mrnka
1
Jika saya menggunakan WillCascadeOnDelete false maka Jika saya ingin menghapus Tim maka itu adalah kesalahan melempar. Hubungan dari AssociationSet 'Team_HomeMatches' dalam status 'Dihapus'. Dengan batasan multiplisitas, 'Team_HomeMatches_Target' yang terkait juga harus dalam status 'Dihapus'.
Rupesh Kumar Tiwari
55

Dimungkinkan juga untuk menentukan ForeignKey()atribut pada properti navigasi:

[ForeignKey("HomeTeamID")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("GuestTeamID")]
public virtual Team GuestTeam { get; set; }

Dengan begitu Anda tidak perlu menambahkan kode apa pun ke OnModelCreatemetode ini

ShaneA
sumber
4
Saya juga mendapatkan pengecualian yang sama.
Jo Smo
11
Ini adalah cara standar saya untuk menentukan kunci asing yang berfungsi untuk semua kasus KECUALI ketika entitas berisi lebih dari satu properti nav dari jenis yang sama (mirip dengan skenario HomeTeam dan GuestTeam), dalam hal ini EF menjadi bingung dalam menghasilkan SQL. Solusi adalah menambahkan kode OnModelCreatesesuai jawaban yang diterima serta dua koleksi untuk kedua sisi hubungan.
Steven Manuel
saya menggunakan onmodelcreating dalam semua kasus kecuali kasus yang disebutkan, saya menggunakan kunci asing penjelasan data, juga saya tidak tahu mengapa itu tidak diterima !!
hosam hemaily
48

Saya tahu ini adalah postingan lama beberapa tahun dan Anda dapat memecahkan masalah Anda dengan solusi di atas. Namun, saya hanya ingin menyarankan menggunakan InverseProperty untuk seseorang yang masih membutuhkan. Setidaknya Anda tidak perlu mengubah apa pun di OnModelCreating.

Kode di bawah ini tidak diuji.

public class Team
{
    [Key]
    public int TeamId { get; set;} 
    public string Name { get; set; }

    [InverseProperty("HomeTeam")]
    public virtual ICollection<Match> HomeMatches { get; set; }

    [InverseProperty("GuestTeam")]
    public virtual ICollection<Match> GuestMatches { get; set; }
}


public class Match
{
    [Key]
    public int MatchId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}

Anda dapat membaca lebih lanjut tentang InverseProperty di MSDN: https://msdn.microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217396#Relationships

khoa_chung_89
sumber
1
Terima kasih atas jawaban ini, namun itu membuat kolom Kunci Asing dibatalkan dalam tabel Pertandingan.
RobHurd
Ini bekerja sangat baik bagi saya di EF 6 di mana koleksi nullable diperlukan.
Pynt
Jika Anda ingin menghindari api yang lancar (untuk alasan apa pun #differentdussussion) ini berfungsi dengan fantastis. Dalam kasus saya, saya perlu menyertakan anotasi foriegnKey tambahan pada entitas "Match", karena bidang / tabel saya memiliki string untuk PK.
DiscipleMichael
1
Ini sangat berhasil bagi saya. Btw. jika Anda tidak ingin kolom dapat dibatalkan, Anda dapat menentukan kunci asing dengan atribut [ForeignKey]. Jika kuncinya tidak dapat dibatalkan maka Anda sudah siap.
Jakub Holovsky
16

Anda juga dapat mencoba ini:

public class Match
{
    [Key]
    public int MatchId { get; set; }

    [ForeignKey("HomeTeam"), Column(Order = 0)]
    public int? HomeTeamId { get; set; }
    [ForeignKey("GuestTeam"), Column(Order = 1)]
    public int? GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public virtual Team HomeTeam { get; set; }
    public virtual Team GuestTeam { get; set; }
}

Saat Anda membuat kolom FK mengizinkan NULLS, Anda memutus siklus. Atau kita hanya menipu generator skema EF.

Dalam kasus saya, modifikasi sederhana ini menyelesaikan masalah.

Maico
sumber
3
Perhatian pembaca. Meskipun ini bisa mengatasi masalah definisi skema, ini mengubah semantik. Mungkin bukan itu yang cocok dengan pertandingan tanpa dua tim.
N8allan
14

Ini karena Penghapusan Cascade diaktifkan secara default. Masalahnya adalah ketika Anda memanggil delete pada entitas, itu akan menghapus masing-masing entitas yang direferensikan f-key juga. Anda seharusnya tidak membuat nilai-nilai 'wajib' dapat dibatalkan untuk memperbaiki masalah ini. Opsi yang lebih baik adalah menghapus konvensi penghapusan Cascade EF Code First:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

Mungkin lebih aman untuk secara eksplisit menunjukkan kapan melakukan penghapusan kaskade untuk masing-masing anak saat pemetaan / konfigurasi. entitas.

jul
sumber
Jadi apa yang terjadi setelah ini dieksekusi? Restrictbukan Cascade?
Jo Smo
4

InverseProperty di EF Core menjadikan solusi mudah dan bersih.

Properti Balik

Jadi solusi yang diinginkan adalah:

public class Team
{
    [Key]
    public int TeamId { get; set;} 
    public string Name { get; set; }

    [InverseProperty(nameof(Match.HomeTeam))]
    public ICollection<Match> HomeMatches{ get; set; }

    [InverseProperty(nameof(Match.GuestTeam))]
    public ICollection<Match> AwayMatches{ get; set; }
}


public class Match
{
    [Key]
    public int MatchId { get; set; }

    [ForeignKey(nameof(HomeTeam)), Column(Order = 0)]
    public int HomeTeamId { get; set; }
    [ForeignKey(nameof(GuestTeam)), Column(Order = 1)]
    public int GuestTeamId { get; set; }

    public float HomePoints { get; set; }
    public float GuestPoints { get; set; }
    public DateTime Date { get; set; }

    public Team HomeTeam { get; set; }
    public Team GuestTeam { get; set; }
}
pritesh agrawal
sumber