web-dev-qa-db-fra.com

Entity Framework Code First - deux clés étrangères de la même table

Je viens tout juste de commencer à utiliser le code EF, je suis donc un débutant dans ce sujet.

Je voulais créer des relations entre les équipes et les matchs:

1 match = 2 équipes (domicile, invité) et résultat.

J'ai pensé qu'il était facile de créer un tel modèle, alors j'ai commencé à coder:

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

Et je reçois une exception:

La relation référentielle générera une référence cyclique non autorisée. [Nom de contrainte = Match_GuestTeam]

Comment créer un tel modèle, avec 2 clés étrangères à la même table?

244
Jarek

Essaye ça:

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

Les clés primaires sont mappées par convention. L’équipe doit avoir deux séries de matches. Vous ne pouvez pas avoir une seule collection référencée par deux FK. La correspondance est mappée sans la suppression en cascade, car elle ne fonctionne pas dans ces références auto-référentes plusieurs à plusieurs.

281
Ladislav Mrnka

Il est également possible de spécifier l'attribut ForeignKey() sur la propriété de navigation:

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

De cette façon, vous n'avez pas besoin d'ajouter de code à la méthode OnModelCreate

51
ShaneA

Je sais que cela date de plusieurs années et que vous pouvez résoudre votre problème avec la solution ci-dessus. Cependant, je veux simplement suggérer d'utiliser InverseProperty pour quelqu'un qui en a encore besoin. Au moins, vous n'avez rien à changer dans OnModelCreating.

Le code ci-dessous n'a pas été testé.

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

Pour plus d'informations sur InverseProperty sur MSDN: https://msdn.Microsoft.com/en-us/data/jj591583?f=255&MSPPError=-2147217396#Relationships

39
khoa_chung_89

Vous pouvez aussi essayer ceci:

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

Lorsque vous faites une colonne FK autoriser NULLS, vous interrompez le cycle. Ou nous trichons simplement le générateur de schéma EF.

Dans mon cas, cette simple modification résout le problème.

13
Maico

En effet, les suppressions en cascade sont activées par défaut. Le problème est que lorsque vous appelez une suppression sur l'entité, elle supprime également chacune des entités référencées par la touche f. Vous ne devez pas rendre les valeurs "obligatoires" nulles pour résoudre ce problème. Une meilleure option serait de supprimer la convention de suppression Cascade de EF Code First:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); 

Il est probablement plus sûr d'indiquer explicitement à quel moment effectuer une suppression en cascade pour chacun des enfants lors du mappage/config. l'entité.

13
juls

InverseProperty dans EF Core rend la solution simple et propre.

InverseProperty

La solution souhaitée serait donc:

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; }
}
0
pritesh agrawal