รหัส Entity Framework First - สอง Foreign Keys จากตารางเดียวกัน


260

ฉันเพิ่งเริ่มใช้รหัส EF ก่อนดังนั้นฉันจึงเป็นผู้เริ่มต้นทั้งหมดในหัวข้อนี้

ฉันต้องการสร้างความสัมพันธ์ระหว่างทีมและการแข่งขัน:

1 การแข่งขัน = 2 ทีม (บ้านผู้เยี่ยมชม) และผลการแข่งขัน

ฉันคิดว่ามันง่ายที่จะสร้างแบบจำลองดังนั้นฉันจึงเริ่มเขียนโค้ด:

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

และฉันก็ได้รับการยกเว้น:

ความสัมพันธ์ผู้อ้างอิงจะส่งผลให้มีการอ้างอิงตามวัฏจักรที่ไม่ได้รับอนุญาต [ข้อ จำกัด ชื่อ = Match_GuestTeam]

ฉันจะสร้างรูปแบบดังกล่าวโดยมี 2 foreign keys ไปยังตารางเดียวกันได้อย่างไร

คำตอบ:


296

ลองสิ่งนี้:

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

คีย์หลักถูกแมปโดยการประชุมเริ่มต้น ทีมต้องมีการรวบรวมการแข่งขันสองครั้ง คุณไม่สามารถมีหนึ่งคอลเลกชันอ้างอิงโดยสอง FKs การจับคู่ถูกแมปโดยไม่มีการลบซ้อนเนื่องจากไม่ทำงานในการอ้างอิงตนเองเหล่านี้หลายต่อหลายคน


3
จะเกิดอะไรขึ้นถ้าสองทีมได้รับอนุญาตให้เล่นเพียงครั้งเดียว
ca9163d9

4
@NickW: นั่นคือสิ่งที่คุณต้องจัดการในแอปพลิเคชันของคุณไม่ใช่ในการจับคู่ จากมุมมองของการทำแผนที่อนุญาตให้คู่เล่นสองครั้ง (แต่ละครั้งจะเป็นแขกและกลับบ้านหนึ่งครั้ง)
Ladislav Mrnka

2
ฉันมีรูปแบบที่คล้ายกัน เป็นวิธีที่เหมาะสมในการจัดการลบน้ำตกถ้าทีมจะถูกลบอย่างไร ฉันค้นหาการสร้างทริกเกอร์ INSTEAD OF DELETE แต่ไม่แน่ใจว่ามีวิธีแก้ปัญหาที่ดีกว่านี้หรือไม่ ฉันต้องการจัดการกับสิ่งนี้ในฐานข้อมูลไม่ใช่แอปพลิเคชัน
Woodchipper

1
@mrshickadance: มันเหมือนกัน วิธีการหนึ่งใช้ API ที่คล่องแคล่วและหมายเหตุประกอบข้อมูลอื่น
Ladislav Mrnka

1
ถ้าฉันใช้ WillCascadeOnDelete false แล้วถ้าฉันต้องการลบทีมจากนั้นก็มีข้อผิดพลาดในการขว้างปา ความสัมพันธ์จาก 'Team_HomeMatches' AssociationSet อยู่ในสถานะ 'ลบแล้ว' ได้รับข้อ จำกัด หลายหลาก 'Team_HomeMatches_Target' ที่สอดคล้องกันจะต้องอยู่ในสถานะ 'ถูกลบ' ด้วย
Rupesh Kumar Tiwari

55

เป็นไปได้ที่จะระบุForeignKey()คุณสมบัติในคุณสมบัติการนำทาง:

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

ด้วยวิธีนี้คุณไม่จำเป็นต้องเพิ่มรหัสใด ๆ กับOnModelCreateวิธีการนั้น


4
ฉันได้รับข้อยกเว้นเดียวกันทั้งสองวิธี
Jo Smo

11
นี่เป็นวิธีมาตรฐานในการระบุคีย์ต่างประเทศซึ่งใช้ได้กับทุกกรณียกเว้นเมื่อเอนทิตีมีคุณสมบัติการนำทางมากกว่าหนึ่งประเภท (คล้ายกับสถานการณ์ HomeTeam และ GuestTeam) ซึ่งในกรณีนี้ EF สับสนในการสร้าง SQL วิธีแก้ไขคือเพิ่มรหัสOnModelCreateตามคำตอบที่ยอมรับรวมถึงคอลเลกชันทั้งสองสำหรับความสัมพันธ์ทั้งสอง
Steven Manuel

ฉันใช้ onmodelcreating ในทุกกรณียกเว้นกรณีที่กล่าวถึงฉันใช้ data foreign foreign key รวมทั้งฉันไม่รู้ว่าทำไมมันถึงไม่ยอมรับ !!
hosam hemaily

48

ฉันรู้ว่ามันเป็นโพสต์เก่าหลายปีและคุณอาจแก้ปัญหาของคุณด้วยวิธีการแก้ไข อย่างไรก็ตามฉันแค่ต้องการแนะนำให้ใช้ InverseProperty สำหรับคนที่ยังต้องการ อย่างน้อยคุณไม่จำเป็นต้องเปลี่ยนแปลงอะไรใน OnModelCreating

โค้ดด้านล่างนี้ไม่ได้ทำการทดสอบ

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

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ InverseProperty ได้บน MSDN: https://msdn.microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217396#Relationships


1
ขอบคุณสำหรับคำตอบนี้ แต่มันทำให้คอลัมน์คีย์ต่างประเทศเป็นโมฆะในตารางการแข่งขัน
RobHurd

สิ่งนี้ใช้งานได้ดีสำหรับฉันใน EF 6 ที่ต้องการคอลเลกชันที่ต้องใช้ค่า null
Pynt

หากคุณต้องการหลีกเลี่ยง API ที่คล่องแคล่ว (ด้วยเหตุผลใดก็ตาม #differentdiscussion) สิ่งนี้จะทำงานได้อย่างน่าอัศจรรย์ ในกรณีของฉันฉันจำเป็นต้องเพิ่มหมายเหตุประกอบ foriegnKey เพิ่มเติมในเอนทิตี "จับคู่" เนื่องจากฟิลด์ / ตารางของฉันมีสตริงสำหรับ PK
DiscipleMichael

1
มันใช้งานได้ดีมากสำหรับฉัน Btw หากคุณไม่ต้องการให้คอลัมน์เป็นโมฆะคุณสามารถระบุ foreign key ด้วยแอตทริบิวต์ [ForeignKey] หากคีย์ไม่เป็นโมฆะแสดงว่าคุณพร้อมแล้ว
Jakub Holovsky

16

คุณสามารถลองเช่นกัน:

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

เมื่อคุณสร้างคอลัมน์ FK อนุญาตให้ NULLS แสดงว่าคุณกำลังทำลายวงรอบ หรือเราแค่โกงเครื่องกำเนิดสคีมาของ EF

ในกรณีของฉันการแก้ไขแบบง่ายนี้แก้ปัญหาได้


3
ข้อควรระวังผู้อ่าน แม้ว่าสิ่งนี้อาจช่วยแก้ปัญหาคำจำกัดความของสคีมา แต่ก็เปลี่ยนความหมาย อาจไม่ใช่กรณีที่มีการแข่งขันโดยไม่มีสองทีม
N8allan

14

นี่เป็นเพราะการลบเรียงซ้อนถูกเปิดใช้งานตามค่าเริ่มต้น ปัญหาคือเมื่อคุณเรียกใช้การลบในเอนทิตีมันจะลบเอนทิตีที่อ้างอิง f-key แต่ละรายการเช่นกัน คุณไม่ควรทำให้ค่า 'จำเป็น' เป็นโมฆะในการแก้ไขปัญหานี้ ตัวเลือกที่ดีกว่าคือการลบแบบแผนลบ Cascade ของ EF Code First:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

มันอาจจะปลอดภัยกว่าที่จะระบุอย่างชัดเจนเมื่อใดที่จะทำการลบแบบเรียงซ้อนสำหรับแต่ละลูกเมื่อทำการแมป / ตั้งค่า นิติบุคคล


ดังนั้นหลังจากดำเนินการแล้วจะเกิดอะไรขึ้น? RestrictแทนCascade?
Jo Smo

4

InverseProperty ใน EF Core ทำให้การแก้ปัญหาเป็นเรื่องง่ายและสะอาด

InverseProperty

ดังนั้นทางออกที่ต้องการคือ:

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; }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.