Comparteix a través de


Fluent API: relaciones

Nota:

En esta página se proporciona información sobre cómo configurar relaciones en el modelo de Code First mediante la API fluida. Para obtener información general sobre las relaciones en EF y cómo acceder y manipular datos mediante relaciones, vea Relaciones y propiedades de navegación.

Al trabajar con Code First, defina el modelo definiendo las clases CLR de dominio. De forma predeterminada, Entity Framework usa las convenciones de Code First para asignar las clases al esquema de la base de datos. Si usa las convenciones de nomenclatura de Code First, en la mayoría de los casos puede confiar en Code First para configurar relaciones entre las tablas en función de las claves externas y las propiedades de navegación que defina en las clases. Si no sigue las convenciones al definir las clases, o si desea cambiar la forma en que funcionan las convenciones, puede usar la API fluida o las anotaciones de datos para configurar las clases para que Code First pueda asignar las relaciones entre las tablas.

Introducción

Al configurar una relación con la API fluida, comienza con la instancia entityTypeConfiguration y, a continuación, usa 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 una colección. A continuación, puede configurar una propiedad de navegación inversa mediante los métodos WithRequired, WithOptional y WithMany. Estos métodos tienen sobrecargas que no toman argumentos y se pueden usar 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 va a usar como clave externa.

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

En el ejemplo siguiente se configura una relación uno a cero o uno. OfficeAssignment tiene la propiedad InstructorID, que es una clave principal y también una externa. Dado que el nombre de la propiedad no sigue la convención, se utiliza el método HasKey para configurar la clave principal.

// 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 donde ambos extremos son obligatorios (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 se requieren ambos extremos de la relación o ambos lados son opcionales, Entity Framework no puede identificar la entidad dependiente y la entidad principal. Cuando se requieren ambos extremos de la relación, use WithRequiredPrincipal o WithRequiredDependent después del método HasRequired. Cuando ambos extremos de la relación son opcionales, use 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 de varios a varios

El código siguiente configura una relación de muchos a muchos entre los tipos Course e Instructor. En el ejemplo siguiente, las convenciones predeterminadas de Code First se usan para crear una tabla de combinación. Como resultado, la tabla CourseInstructor se crea con columnas Course_CourseID y 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, debe realizar una configuración adicional mediante el método Map. El código siguiente 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 denominada unidireccional) es cuando se define una propiedad de navegación en solo 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 uno a uno entre Instructor y OfficeAssignment, donde tiene una propiedad de navegación solo en el tipo Instructor, debe usar 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();

Habilitación de la eliminación en cascada

Puede configurar la eliminación en cascada en una relación mediante el método WillCascadeOnDelete. Si una clave externa de la entidad dependiente no acepta valores NULL, Code First establece la eliminación en cascada en la relación. Si una clave externa de la entidad dependiente es nula, Code First no establece la eliminación en cascada en la relación y, cuando se elimina el principal, la clave externa se establecerá en nulo.

Puede quitar estas convenciones de eliminación en cascada mediante:

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

El código siguiente configura la relación que se va a requerir y, a continuación, 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 externa compuesta

Si la clave principal del tipo Department consta de las propiedades DepartmentID y Name, configuraría la clave principal para el Departamento y la clave externa en los tipos Course 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 });

Cambiar el nombre de una clave externa que no está definida en el modelo

Si decide no definir una clave externa en el tipo CLR, pero desea especificar el nombre que 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 Code First

Si la propiedad de clave externa de la clase Course se denominaBa SomeDepartmentID en lugar de DepartmentID, tendría que hacer lo siguiente para especificar que desea que SomeDepartmentID sea la clave externa:

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

Modelo usado en ejemplos

El siguiente modelo de Code First se usa para los ejemplos de esta página.

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