Mã khung thực thể Đầu tiên - hai khóa ngoại từ cùng một bảng


260

Tôi mới bắt đầu sử dụng mã EF trước tiên, vì vậy tôi là người mới bắt đầu trong chủ đề này.

Tôi muốn tạo mối quan hệ giữa Đội và Trận đấu:

1 trận đấu = 2 đội (nhà, khách) và kết quả.

Tôi nghĩ thật dễ dàng để tạo ra một mô hình như vậy, vì vậy tôi bắt đầu viết mã:

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

Và tôi nhận được một ngoại lệ:

Mối quan hệ tham chiếu sẽ dẫn đến một tham chiếu theo chu kỳ không được phép. [Tên ràng buộc = Match_GuestTeam]

Làm thế nào tôi có thể tạo một mô hình như vậy, với 2 khóa ngoại vào cùng một bảng?

Câu trả lời:


297

Thử cái này:

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

Các khóa chính được ánh xạ theo quy ước mặc định. Đội phải có hai bộ sưu tập các trận đấu. Bạn không thể có bộ sưu tập duy nhất được tham chiếu bởi hai FK. Trận đấu được ánh xạ mà không xóa tầng vì nó không hoạt động trong các tự tham chiếu nhiều-nhiều.


3
Nếu hai đội chỉ được chơi một lần thì sao?
ca9163d9

4
@NickW: Đó là điều bạn phải xử lý trong ứng dụng của mình chứ không phải trong ánh xạ. Từ góc độ ánh xạ, các cặp được phép chơi hai lần (mỗi lần là khách và nhà một lần).
Ladislav Mrnka

2
Tôi có một mô hình tương tự. Cách thích hợp để xử lý xóa tầng nếu một nhóm bị xóa là gì? Tôi đã xem xét việc tạo ra một trình kích hoạt INSTEAD OF DELETE nhưng không chắc có giải pháp nào tốt hơn không? Tôi muốn xử lý việc này trong DB, không phải ứng dụng.
Woodchipper

1
@mrshickadance: Nó giống nhau. Một cách tiếp cận sử dụng API trôi chảy và các chú thích dữ liệu khác.
Ladislav Mrnka

1
Nếu tôi sử dụng WillCascadeOnDelete false thì Nếu tôi muốn xóa Đội thì đó là lỗi ném. Mối quan hệ từ Hiệp hội 'Team_HomeMatches' ở trạng thái 'Đã xóa'. Với các ràng buộc về bội số, một 'Team_HomeMatches_Target' tương ứng cũng phải ở trạng thái 'Đã xóa'.
Rupesh Kumar Tiwari

55

Cũng có thể chỉ định ForeignKey()thuộc tính trên thuộc tính điều hướng:

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

Bằng cách đó, bạn không cần thêm bất kỳ mã nào vào OnModelCreatephương thức


4
Tôi cũng có ngoại lệ tương tự.
Jo Smo

11
Đây là cách tiêu chuẩn của tôi để chỉ định các khóa ngoại hoạt động cho tất cả các trường hợp NGOẠI TRỪ khi một thực thể chứa nhiều thuộc tính điều hướng cùng loại (tương tự như kịch bản HomeTeam và GuestTeam), trong trường hợp đó, EF bị nhầm lẫn khi tạo SQL. Giải pháp là thêm mã OnModelCreatetheo câu trả lời được chấp nhận cũng như hai bộ sưu tập cho cả hai mặt của mối quan hệ.
Steven Manuel

Tôi sử dụng onmodelcreating trong tất cả các trường hợp ngoại trừ trường hợp được đề cập, tôi sử dụng khóa ngoại chú thích dữ liệu, cũng như tôi không biết tại sao nó không được chấp nhận !!
hosam hem hàng

48

Tôi biết đó là một bài viết cũ vài năm và bạn có thể giải quyết vấn đề của bạn với giải pháp trên. Tuy nhiên, tôi chỉ muốn đề xuất sử dụng InverseProperty cho một người vẫn cần. Ít nhất bạn không cần thay đổi bất cứ điều gì trong OnModelCreating.

Các mã dưới đây là chưa được thử nghiệm.

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

Bạn có thể đọc thêm về InverseProperty trên MSDN: https://msdn.microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217394#Relationships


1
Cảm ơn câu trả lời này, tuy nhiên, nó làm cho các cột Khóa ngoài không thể hủy trong bảng Kết hợp.
RobHurd

Điều này làm việc rất tốt cho tôi trong EF 6 nơi cần các bộ sưu tập nullable.
Pynt

Nếu bạn muốn tránh api trôi chảy (vì bất kỳ lý do gì #differentdiscussion) thì điều này hoạt động rất tuyệt vời. Trong trường hợp của tôi, tôi cần bao gồm một chú thích foriegnKey bổ sung trên thực thể "Khớp", bởi vì các trường / bảng của tôi có các chuỗi cho PK.
Đệ

1
Điều này làm việc rất nhiều cho tôi. Btw. nếu bạn không muốn các cột rỗng, bạn chỉ có thể chỉ định khóa ngoại với thuộc tính [ForeignKey]. Nếu khóa không thể rỗng thì bạn đã thiết lập xong.
Jakub Holovsky

16

Bạn cũng có thể thử điều này:

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

Khi bạn tạo một cột FK cho phép NULLS, bạn đang phá vỡ chu kỳ. Hoặc chúng ta chỉ đang gian lận trình tạo lược đồ EF.

Trong trường hợp của tôi, sửa đổi đơn giản này giải quyết vấn đề.


3
Thận trọng độc giả. Mặc dù điều này có thể giải quyết vấn đề định nghĩa lược đồ, nhưng nó thay đổi ngữ nghĩa. Có lẽ không phải là trường hợp mà một trận đấu có thể có mà không có hai đội.
N8allan

14

Điều này là do Cascade Deletes được bật theo mặc định. Vấn đề là khi bạn gọi xóa trên thực thể, nó cũng sẽ xóa từng thực thể được tham chiếu khóa f. Bạn không nên biến các giá trị 'bắt buộc' thành null để khắc phục vấn đề này. Một lựa chọn tốt hơn sẽ là xóa quy ước xóa Cascade của EF Code First:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

Có thể an toàn hơn khi chỉ định rõ ràng khi nào thực hiện xóa tầng cho từng đứa trẻ khi ánh xạ / cấu hình. thực thể.


Vì vậy, nó là gì sau khi điều này được thực hiện? Restrictthay vì Cascade?
Jo Smo

4

InverseProperty trong EF Core làm cho giải pháp dễ dàng và sạch sẽ.

Nghịch đảo

Vì vậy, giải pháp mong muốn sẽ là:

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; }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.