Fluent API: Relaciones

Nota:

Esta página proporciona información sobre cómo establecer relaciones en su modelo Code First utilizando la API fluent. Para obtener información general sobre las relaciones en EF y sobre cómo acceder a los datos y manipularlos mediante relaciones, consulte Relaciones ypropiedades de navegación.

Cuando se trabaja con Code First, se define el modelo definiendo las clases CLR del dominio. Por defecto, Entity Framework utiliza las convenciones de Code First para asignar sus clases al esquema de la base de datos. Si utiliza las convenciones de nomenclatura de Code First, en la mayoría de los casos puede confiar en Code First para establecer relaciones entre sus tablas basándose en las claves externas y las propiedades de navegación que defina en las clases. Si no sigue las convenciones al definir sus clases, o si desea cambiar la forma en que funcionan las convenciones, puede utilizar la API fluida o las anotaciones de datos para configurar sus clases de forma que Code First pueda asignar las relaciones entre sus tablas.

Introducción

Cuando se configura una relación con la API fluent, se comienza con la instancia EntityTypeConfiguration y luego se utiliza el método HasRequired, HasOptional, o HasMany para especificar el tipo de relación en la que participa esta entidad. Los métodos HasRequired y HasOptional toman una expresión lambda que representa una propiedad de navegación de referencia. El método HasMany toma una expresión lambda que representa una propiedad de navegación de la colección. A continuación, puede configurar una propiedad de navegación inversa utilizando los métodos WithRequired, WithOptional y WithMany. Estos métodos tienen sobrecargas que no toman argumentos y pueden utilizarse para especificar la cardinalidad con navegaciones unidireccionales.

A continuación, puede configurar las propiedades de clave externa mediante el método HasForeignKey. Este método toma una expresión lambda que representa la propiedad que se utilizará como clave externa.

Configuración de una relación obligatorio-opcional (uno a cero o uno)

El siguiente ejemplo configura una relación uno a cero o uno. El OfficeAssignment tiene la propiedad InstructorID que es una clave primaria y una clave foránea, debido a que el nombre de la propiedad no sigue la convención se utiliza el método HasKey para configurar la clave 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);

Configuración de una relación en la que se requieren ambos extremos (uno a uno)

En la mayoría de los casos, Entity Framework puede deducir qué tipo es el dependiente y cuál es el principal en una relación. Sin embargo, cuando ambos extremos de la relación son obligatorios o ambos son opcionales, Entity Framework no puede identificar al dependiente y al principal. Cuando ambos extremos de la relación son necesarios, utilice WithRequiredPrincipal o WithRequiredDependent después del método HasRequired. Cuando ambos extremos de la relación son opcionales, utilice WithOptionalPrincipal o WithOptionalDependent después del método 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);

Configuración de una relación varios a varios

El siguiente código configura una relación varios a varios entre los tipos Curso e Instructor. En el ejemplo siguiente, las convenciones predeterminadas de Code First se usan para crear una tabla de combinación. Como resultado, se crea la tabla CourseInstructor con las columnas Course_CourseID e Instructor_InstructorID.

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

Si desea especificar el nombre de la tabla de combinación y los nombres de las columnas de la tabla, deberá realizar una configuración adicional mediante el método Map. El siguiente código genera la tabla CourseInstructor con las columnas 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");
    });

Configuración de una relación con una propiedad de navegación

Una relación unidireccional (también llamada unidireccional) es cuando una propiedad de navegación se define solo en uno de los extremos de la relación y no en ambos. Por convención, Code First siempre interpreta una relación unidireccional como uno a varios. Por ejemplo, si desea una relación de uno a uno entre Instructor y OfficeAssignment, donde tiene una propiedad de navegación solo en el tipo Instructor, necesita utilizar la API fluent para configurar esta relación.

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

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

Habilitar la eliminación en cascada

Puede configurar la eliminación en cascada en una relación utilizando el método WillCascadeOnDelete. Si una clave externa en la entidad dependiente no es anulable, entonces Code First establece la eliminación en cascada en la relación. Si una clave ajena en la entidad dependiente es anulable, Code First no establece la eliminación en cascada en la relación, y cuando se elimine la entidad principal, la clave ajena se establecerá como nula.

Puede eliminar estas convenciones de eliminación en cascada utilizando:

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

El siguiente código configura la relación para que sea necesaria y luego deshabilita la eliminación en cascada.

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

Configuración de una clave foránea compuesta

Si la clave primaria en el tipo Departamento consistiera en las propiedades DepartmentID y Name, configuraría la clave primaria para el Departamento y la clave externa en los tipos de Curso de la siguiente manera:

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

Cambio de nombre de una clave externa no definida en el modelo

Si decide no definir una clave externa en el tipo CLR, pero desea especificar qué nombre debe tener en la base de datos, haga lo siguiente:

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

Configuración de un nombre de clave externa que no sigue la convención del primer código

Si la propiedad de clave externa de la clase Curso se llamara CiertoDepartamentoID en lugar de DepartamentoID, tendría que hacer lo siguiente para especificar que desea que CiertoDepartamentoID sea la clave externa:

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

Modelo utilizado en las muestras

Para los ejemplos de esta página se utiliza el siguiente modelo de Code First.

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