Código de marco de entidad primero: dos claves externas de la misma tabla
Acabo de empezar a usar el código EF, por lo que soy un principiante total en este tema.
Quería crear relaciones entre equipos y partidos:
1 partido = 2 equipos (local, invitado) y resultado.
Pensé que sería fácil crear un modelo así, así que comencé a codificar:
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; }
}
Y me sale una excepción:
La relación referencial dará lugar a una referencia cíclica que no está permitida. [Nombre de restricción = Match_GuestTeam]
¿Cómo puedo crear un modelo de este tipo con 2 claves externas para la misma tabla?
Prueba esto:
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);
}
}
Las claves primarias se asignan por convención predeterminada. El equipo debe tener dos colecciones de partidos. No puede tener una sola colección a la que hagan referencia dos FK. La coincidencia se asigna sin eliminación en cascada porque no funciona en estas autorreferencias de muchos a muchos.
También es posible especificar el ForeignKey()
atributo en la propiedad de navegación:
[ForeignKey("HomeTeamID")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("GuestTeamID")]
public virtual Team GuestTeam { get; set; }
De esa manera no necesitas agregar ningún código al OnModelCreate
método.
Puedes probar esto también:
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; }
}
Cuando haces que una columna FK permita NULLS, estás rompiendo el ciclo. O simplemente estamos engañando al generador de esquemas EF.
En mi caso, esta simple modificación soluciona el problema.
InverseProperty
en EF Core hace que la solución sea fácil y limpia.
Propiedad inversa
Entonces la solución deseada sería:
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; }
}
Esto se debe a que las eliminaciones en cascada están habilitadas de forma predeterminada. El problema es que cuando realiza una eliminación en la entidad, también eliminará cada una de las entidades a las que se hace referencia con la tecla F. No debe hacer que los valores "requeridos" sean anulables para solucionar este problema. Una mejor opción sería eliminar la convención de eliminación en cascada de EF Code First:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Probablemente sea más seguro indicar explícitamente cuándo realizar una eliminación en cascada para cada uno de los elementos secundarios al mapear/configurar. la entidad.