Entity Framework: modellare relazioni multiple tra 2 entità

pubblicato da il 17/09/2020 alle 7:44

Data Access 

Entity Framework permette di definire agevolmente l'esistenza di relazioni tra due entità; si consideri il caso di disporre di due entità, rispettivamente denominate Course e Person, volendo utilizzare quest'ultima per specificare i docenti di un corso: considerando che una singola persona può essere docente di corsi differenti e che un corso può avere docenti multipli, potremo modellare una relazione molti-a-molti tramite il seguente codice.

public class Course
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Person> Teachers { get; set; }
}
 
public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public ICollection<Course> CoursesHeld { get; set; }
}

In tal modo:

  • La proprietà Teachers della classe Course permetterà di conoscere i docenti del corso
  • La proprietà CoursesHeld della classe Person permetterà di conoscere quali siano i corsi che hanno avuto la persona come docente

Creando una migration per questo modello, potremo verificare che essa produrrà una tabella contenente le colonne CourseId e PersonId per permettere la memorizzazione delle associazioni.

Immaginiamo ora di voler creare, tra le due entità, un ulteriore legame per conoscere quali siano i corsi ai quali una persona ha partecipato. A tal fine, potremmo modificare il modello nel seguente modo:

public class Course
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Person> Attendees { get; set; }
    public ICollection<Person> Teachers { get; set; }
}
 
public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public ICollection<Course> CoursesAttended { get; set; }
    public ICollection<Course> CoursesHeld { get; set; }
}

Producendo ed applicando la migration, Entity Framework solleverà però una eccezione simile alla seguente:

Unable to determine the relationship represented by navigation property 'Course.Teachers' of type 'Person'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

L'errore è dovuto all'incapacità di Entity Framework di correlare le associazioni tra le classi: ad esempio, l'incapacità di comprendere se la propriètà Attendees sia correlata a CoursesAttended oppure a CoursesHeld.

Possiamo risolvere il problema utilizzando l'attributo InverseProperty per decorare, come mostrato dal codice seguente, le proprietà del modello al fine di specificare le correlazioni.

public class Course
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Person> Attendees { get; set; }
    public ICollection<Person> Teachers { get; set; }
}
 
public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [InverseProperty(nameof(CourseEdition.Attendees))]
    public ICollection<Course> CoursesAttended { get; set; }
    [InverseProperty(nameof(CourseEdition.Teachers))]
    public ICollection<Course> CoursesHeld { get; set; }
}

Potremo ora generare la migration che produrrà due tabelle distinte, ognuna dedicata ad una delle associazioni tra le due entità.
Ove volessimo personalizzare il nome delle suddette tabelle, potremo specificarlo effettuando l'override del metodo OnModelCreating del DbContext come mostrato nell'esempio seguente.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasMany(u => u.Courses)
        .WithMany(t => t.Teachers)
        .Map(x =>
        {
            x.MapLeftKey("PersonId");
            x.MapRightKey("CourseId");
            x.ToTable("CourseTeachers");
        });
 
    modelBuilder.Entity<Person><()
        .HasMany(u => u.CoursesAttended)
        .WithMany(t => t.Attendees)
        .Map(x =>
        {
            x.MapLeftKey("PersonId");
            x.MapRightKey("CourseId");
            x.ToTable("CourseAttendees");
        });
}