Ho un'entità Ticket
:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTickets { get; set; }
}
Voglio configurare le auto-relazioni molti-a-molti in Entity Framework Core, quindi ho creato due relazioni uno-a-molti:
public class Relation
{
[Required, ForeignKey("TicketFrom")]
public int FromId { get; set; }
[Required, ForeignKey("TicketTo")]
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
Ho provato a creare la relazione utilizzando l'API fluente:
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.FromId);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
Ma in risultato ho un errore:
Impossibile creare una relazione tra "Ticket.RelatedTickets" e "Relation.TicketTo", perché esiste già una relazione tra "Ticket.RelatedTickets" e "Relation.TicketForm". Le proprietà di navigazione possono partecipare solo a una singola relazione.
La soluzione possibile è aggiungere la relazione Parent direttamente a TicketEntity
:
public class Ticket
{
public int Id { get; set; }
[Required, ForeignKey("ParentRelation")]
public Nullable<int> ParentRelationId { get; set; }
public virtual Ticket ParentRelation {get;set;}
public virtual ICollection<Ticket> RelatedTickets { get; set; }
...
}
Con una API fluente come questa:
modelBuilder.Entity<Ticket> =>
{
entity
.HasMany(e => e.RelatedTickets)
.WithOne(e => e.ParentRelation)
.HasForeignKey(e => e.ParentRelationId );
});
Ma sembra "sporco" memorizzare la relazione genitoriale come questa.
Qual è l'approccio giusto?
Non è possibile avere una sola collezione con relazioni. Hai bisogno di due - uno con le relazioni che il biglietto è uguale a TicketFrom
e in secondo luogo con le relazioni il biglietto è uguale a TicketTo
.
Qualcosa come questo:
Modello:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTo { get; set; }
public virtual ICollection<Relation> RelatedFrom { get; set; }
}
public class Relation
{
public int FromId { get; set; }
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
Configurazione:
modelBuilder.Entity<Relation>()
.HasKey(e => new { e.FromId, e.ToId });
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketFrom)
.WithMany(e => e.RelatedTo)
.HasForeignKey(e => e.FromId);
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketTo)
.WithMany(e => e.RelatedFrom)
.HasForeignKey(e => e.ToId);
Nota che una soluzione che usa Parent non è equivalente, perché creerebbe un'associazione one-to-many
, mentre se capisco correttamente stai cercando many-to-many
.
Ecco un'ottima spiegazione su come stabilire una relazione molti-a-molti in EF Core Relazione molti-a-molti autoreferenziale
Ogni proprietà di navigazione di raccolta o riferimento può essere solo una parte di una singola relazione. Mentre molte o molte relazioni con entità di join esplicite sono implementate con due relazioni da una a molte. L'entità join contiene due proprietà di navigazione di riferimento, ma l'entità principale ha una sola proprietà di navigazione della raccolta, che deve essere associata a una di esse, ma non a entrambe.
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany() // <-- one of this must be empty
.HasForeignKey(pc => pc.FromId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
Assicurati solo che WithMany corrisponda esattamente alla presenza / assenza della proprietà di navigazione corrispondente.
Si noti che è necessario disattivare l'eliminazione della cascata.