Share via


API Fluent - Relazioni

Nota

Questa pagina fornisce informazioni sulla configurazione delle relazioni nel modello Code First usando l'API Fluent. Per informazioni generali sulle relazioni in Entity Framework e su come accedere e modificare i dati tramite relazioni, vedere Relazioni e proprietà di navigazione.

Quando si usa Code First, si definisce il modello definendo le classi CLR del dominio. Per impostazione predefinita, Entity Framework usa le convenzioni Code First per eseguire il mapping delle classi allo schema del database. Se si usano le convenzioni di denominazione Code First, nella maggior parte dei casi è possibile fare affidamento su Code First per configurare le relazioni tra le tabelle in base alle chiavi esterne e alle proprietà di navigazione definite nelle classi. Se non si seguono le convenzioni durante la definizione delle classi o se si vuole modificare il funzionamento delle convenzioni, è possibile usare l'API Fluent o le annotazioni dei dati per configurare le classi in modo che Code First possa eseguire il mapping delle relazioni tra le tabelle.

Introduzione

Quando si configura una relazione con l'API Fluent, si inizia con l'istanza entityTypeConfiguration e quindi si usa il metodo HasRequired, HasOptional o HasMany per specificare il tipo di relazione a cui partecipa questa entità. I metodi HasRequired e HasOptional accettano un'espressione lambda che rappresenta una proprietà di navigazione di riferimento. Il metodo HasMany accetta un'espressione lambda che rappresenta una proprietà di navigazione della raccolta. È quindi possibile configurare una proprietà di navigazione inversa usando i metodi WithRequired, WithOptional e WithMany. Questi metodi hanno overload che non accettano argomenti e possono essere usati per specificare la cardinalità con spostamenti unidirezionali.

È quindi possibile configurare le proprietà della chiave esterna usando il metodo HasForeignKey. Questo metodo accetta un'espressione lambda che rappresenta la proprietà da utilizzare come chiave esterna.

Configurazione di una relazione obbligatoria-facoltativa (uno-a-zero-o-uno)

Nell'esempio seguente viene configurata una relazione uno-a-zero-o-uno. OfficeAssignment ha la proprietà InstructorID che è una chiave primaria e una chiave esterna, perché il nome della proprietà non segue la convenzione utilizzata dal metodo HasKey per configurare la chiave primaria.

// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

// Map one-to-zero or one relationship
modelBuilder.Entity<OfficeAssignment>()
    .HasRequired(t => t.Instructor)
    .WithOptional(t => t.OfficeAssignment);

Configurazione di una relazione in cui sono necessarie entrambe le estremità (uno-a-uno)

Nella maggior parte dei casi Entity Framework può dedurre quale tipo è dipendente e che è l'entità in una relazione. Tuttavia, quando entrambe le estremità della relazione sono obbligatorie o entrambi i lati sono facoltativi Entity Framework non è in grado di identificare l'entità dipendente e . Quando sono necessarie entrambe le estremità della relazione, usare WithRequiredPrincipal o WithRequiredDependent dopo il metodo HasRequired. Quando entrambe le estremità della relazione sono facoltative, usare WithOptionalPrincipal o WithOptionalDependent dopo il metodo HasOptional.

// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
    .HasRequired(t => t.OfficeAssignment)
    .WithRequiredPrincipal(t => t.Instructor);

Configurazione di una relazione molti-a-molti

Il codice seguente configura una relazione molti-a-molti tra i tipi Course e Instructor. Nell'esempio seguente vengono usate le convenzioni Code First predefinite per creare una tabella join. Di conseguenza, la tabella CourseInstructor viene creata con colonne Course_CourseID e Instructor_InstructorID.

modelBuilder.Entity<Course>()
    .HasMany(t => t.Instructors)
    .WithMany(t => t.Courses)

Se si desidera specificare il nome della tabella join e i nomi delle colonne nella tabella, è necessario eseguire una configurazione aggiuntiva usando il metodo Map. Il codice seguente genera la tabella CourseInstructor con le colonne CourseID e InstructorID.

modelBuilder.Entity<Course>()
    .HasMany(t => t.Instructors)
    .WithMany(t => t.Courses)
    .Map(m =>
    {
        m.ToTable("CourseInstructor");
        m.MapLeftKey("CourseID");
        m.MapRightKey("InstructorID");
    });

Configurazione di una relazione con una proprietà di navigazione

Una relazione unidirezionale (detta anche unidirezionale) è quando una proprietà di navigazione viene definita solo su una delle estremità della relazione e non su entrambi. Per convenzione, Code First interpreta sempre una relazione unidirezionale come uno-a-molti. Ad esempio, se si vuole una relazione uno-a-uno tra Instructor e OfficeAssignment, in cui si dispone di una proprietà di navigazione solo sul tipo Instructor, è necessario usare l'API Fluent per configurare questa relazione.

// Configure the primary Key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
    .HasRequired(t => t.OfficeAssignment)
    .WithRequiredPrincipal();

Abilitazione dell'eliminazione a catena

È possibile configurare l'eliminazione a catena in una relazione usando il metodo WillCascadeOnDelete. Se una chiave esterna nell'entità dipendente non è nullable, Code First imposta l'eliminazione a catena sulla relazione. Se una chiave esterna nell'entità dipendente è nullable, Code First non imposta l'eliminazione a catena sulla relazione e quando l'entità viene eliminata la chiave esterna verrà impostata su Null.

È possibile rimuovere queste convenzioni di eliminazione a catena usando:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()

Il codice seguente configura la relazione da richiedere e quindi disabilita l'eliminazione a catena.

modelBuilder.Entity<Course>()
    .HasRequired(t => t.Department)
    .WithMany(t => t.Courses)
    .HasForeignKey(d => d.DepartmentID)
    .WillCascadeOnDelete(false);

Configurazione di una chiave esterna composita

Se la chiave primaria nel tipo Department è costituita dalle proprietà DepartmentID e Name, è necessario configurare la chiave primaria per il reparto e la chiave esterna nei tipi Course come indicato di seguito:

// Composite primary key
modelBuilder.Entity<Department>()
.HasKey(d => new { d.DepartmentID, d.Name });

// Composite foreign key
modelBuilder.Entity<Course>()  
    .HasRequired(c => c.Department)  
    .WithMany(d => d.Courses)
    .HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });

Ridenominazione di una chiave esterna non definita nel modello

Se si sceglie di non definire una chiave esterna nel tipo CLR, ma si vuole specificare il nome che deve avere nel database, eseguire le operazioni seguenti:

modelBuilder.Entity<Course>()
    .HasRequired(c => c.Department)
    .WithMany(t => t.Courses)
    .Map(m => m.MapKey("ChangedDepartmentID"));

Configurazione di un nome di chiave esterna che non segue la convenzione Code First

Se la proprietà di chiave esterna nella classe Course è stata denominata SomeDepartmentID anziché DepartmentID, è necessario eseguire le operazioni seguenti per specificare che si vuole che SomeDepartmentID sia la chiave esterna:

modelBuilder.Entity<Course>()
         .HasRequired(c => c.Department)
         .WithMany(d => d.Courses)
         .HasForeignKey(c => c.SomeDepartmentID);

Modello usato negli esempi

Il modello Code First seguente viene usato per gli esempi in questa pagina.

using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
// add a reference to System.ComponentModel.DataAnnotations DLL
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System;

public class SchoolEntities : DbContext
{
    public DbSet<Course> Courses { get; set; }
    public DbSet<Department> Departments { get; set; }
    public DbSet<Instructor> Instructors { get; set; }
    public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configure Code First to ignore PluralizingTableName convention
        // If you keep this convention then the generated tables will have pluralized names.
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

public class Department
{
    public Department()
    {
        this.Courses = new HashSet<Course>();
    }
    // Primary key
    public int DepartmentID { get; set; }
    public string Name { get; set; }
    public decimal Budget { get; set; }
    public System.DateTime StartDate { get; set; }
    public int? Administrator { get; set; }

    // Navigation property
    public virtual ICollection<Course> Courses { get; private set; }
}

public class Course
{
    public Course()
    {
        this.Instructors = new HashSet<Instructor>();
    }
    // Primary key
    public int CourseID { get; set; }

    public string Title { get; set; }
    public int Credits { get; set; }

    // Foreign key
    public int DepartmentID { get; set; }

    // Navigation properties
    public virtual Department Department { get; set; }
    public virtual ICollection<Instructor> Instructors { get; private set; }
}

public partial class OnlineCourse : Course
{
    public string URL { get; set; }
}

public partial class OnsiteCourse : Course
{
    public OnsiteCourse()
    {
        Details = new Details();
    }

    public Details Details { get; set; }
}

public class Details
{
    public System.DateTime Time { get; set; }
    public string Location { get; set; }
    public string Days { get; set; }
}

public class Instructor
{
    public Instructor()
    {
        this.Courses = new List<Course>();
    }

    // Primary key
    public int InstructorID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public System.DateTime HireDate { get; set; }

    // Navigation properties
    public virtual ICollection<Course> Courses { get; private set; }
}

public class OfficeAssignment
{
    // Specifying InstructorID as a primary
    [Key()]
    public Int32 InstructorID { get; set; }

    public string Location { get; set; }

    // When Entity Framework sees Timestamp attribute
    // it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
    [Timestamp]
    public Byte[] Timestamp { get; set; }

    // Navigation property
    public virtual Instructor Instructor { get; set; }
}