Compartir a través de


API fluida: configuración y asignación de propiedades y tipos

Cuando se trabaja con Code First de Entity Framework, el comportamiento predeterminado es asignar las clases POCO a las tablas mediante un conjunto de convenciones respaldadas en EF. A veces, sin embargo, no puede o no desea seguir esas convenciones y necesita asignar entidades a algo distinto de lo que dictan las convenciones.

Hay dos formas principales de configurar EF para que usen algo distinto de las convenciones, es decir, anotaciones o API fluida de EF. Las anotaciones solo cubren un subconjunto de la funcionalidad de API fluida, por lo que hay escenarios de asignación que no se pueden lograr mediante anotaciones. Este artículo está diseñado para demostrar cómo usar la API fluida para configurar las propiedades.

Normalmente se accede a la API fluida del código reemplazando el método OnModelCreating en el DbContext derivado. Los ejemplos siguientes están diseñados para mostrar cómo realizar varias tareas con la API fluida y le permiten copiar el código y personalizarlo para adaptarlo a su modelo. Si desea ver el modelo con el que se pueden usar tal cual, este se proporciona al final de este artículo.

Configuración de todo el modelo

Esquema predeterminado (EF6 en adelante)

A partir de EF6, puede usar el método HasDefaultSchema en DbModelBuilder para especificar el esquema de base de datos que se va a usar para todas las tablas, procedimientos almacenados, etc. Esta configuración predeterminada se invalidará para todos los objetos para los que configure explícitamente un esquema diferente.

modelBuilder.HasDefaultSchema("sales");

Convenciones personalizadas (de EF6 en adelante)

A partir de EF6, puede crear sus propias convenciones para complementar las incluidas en Code First. Para obtener más detalles, consulte Convenciones de Code First personalizadas.

Asignación de propiedades

El método Property se usa para configurar atributos para cada propiedad que pertenezca a una entidad o a un tipo complejo. El método Property se usa para obtener un objeto de configuración para una propiedad determinada. Las opciones del objeto de configuración son específicas del tipo que se está configurando. IsUnicode solo está disponible en las propiedades de cadena, por ejemplo.

Configurar una clave principal

La convención de Entity Framework para las claves principales es:

  1. La clase define una propiedad cuyo nombre es “ID” o “Id”
  2. o un nombre de clase seguido de “ID” o “id.”

Para establecer explícitamente una propiedad como una clave principal, puede usar el método HasKey. En el ejemplo siguiente, el método HasKey se usa para configurar la clave principal InstructorID en el tipo OfficeAssignment.

modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

Configuración de una clave principal compuesta

En el ejemplo siguiente se configuran las propiedades DepartmentID y Name para que sean la clave principal compuesta del tipo Department.

modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });

Desactivación de la identidad de las claves principales numéricas

En el ejemplo siguiente se establece la propiedad DepartmentID en System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None para indicar que la base de datos no generará el valor.

modelBuilder.Entity<Department>().Property(t => t.DepartmentID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

Especificar la longitud máxima en una propiedad

En el ejemplo siguiente, la propiedad Name no debe tener más de 50 caracteres. Si hace que el valor tenga más de 50 caracteres, obtendrá una excepción DbEntityValidationException. Si Code First crea una base de datos a partir de este modelo, también establecerá la longitud máxima de la columna Nombre en 50 caracteres.

modelBuilder.Entity<Department>().Property(t => t.Name).HasMaxLength(50);

Configurar la propiedad para que sea obligatoria

En el ejemplo siguiente, se requiere la propiedad Name. Si no especifica el nombre, obtendrá una excepción DbEntityValidationException. Si Code First crea una base de datos a partir de este modelo, la columna que se usa para almacenar esta propiedad normalmente no acepta valores NULL.

Nota:

Puede que en algunos casos no sea posible que la columna de la base de datos no acepte valores NULL, aunque se requiera la propiedad. Por ejemplo, al usar datos de estrategia de herencia de la tabla por jerarquía para varios tipos se almacena en una sola tabla. Si un tipo derivado incluye una propiedad necesaria, no se puede hacer que la columna no admita valores NULL, ya que no todos los tipos de la jerarquía tendrán esta propiedad.

modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();

Configuración de un índice en una o varias propiedades

Nota:

Solo de EF 6.1 en adelante: el atributo Index se introdujo en Entity Framework 6.1. Si usa una versión anterior, la información de esta sección no es aplicable.

La creación de índices no es compatible de forma nativa con la API fluida, pero puede usar la compatibilidad con IndexAttribute a través de la API fluida. Los atributos de índice se procesan mediante la inclusión de una anotación en el modelo, que luego se convierte en un índice en la base de datos más adelante en la canalización. Puede agregar manualmente estas mismas anotaciones mediante la API fluida.

La manera más fácil de hacerlo es crear una instancia de IndexAttribute que contenga toda la configuración del nuevo índice. A continuación, puede crear una instancia de IndexAnnotation que es un tipo específico de EF que convertirá la configuración de IndexAttribute en una anotación de modelo que se puede almacenar en el modelo de EF. Esto se puede pasar al método HasColumnAnnot en API fluida, si especifica el nombre Index para la anotación.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));

Para obtener una lista completa de la configuración disponible en IndexAttribute, vea la sección Index de Anotaciones de datos de Code First. Esto incluye personalizar el nombre del índice, crear índices únicos y crear índices de varias columnas.

Puede especificar varias anotaciones de índice en una sola propiedad si pasa una matriz de IndexAttribute al constructor de IndexAnnotation.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation(
        "Index",  
        new IndexAnnotation(new[]
            {
                new IndexAttribute("Index1"),
                new IndexAttribute("Index2") { IsUnique = true }
            })));

Especificar no asignar una propiedad CLR a una columna de la base de datos

En el ejemplo siguiente se muestra cómo especificar que una propiedad de un tipo CLR no está asignada a una columna de la base de datos.

modelBuilder.Entity<Department>().Ignore(t => t.Budget);

Asignación de una propiedad CLR a una columna específica de la base de datos

En el ejemplo siguiente se asigna la propiedad NAME CLR a la columna de base de datos DepartmentName.

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .HasColumnName("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"));

Configurar si una propiedad de cadena admite contenido Unicode

De forma predeterminada, las cadenas son Unicode (nvarchar en SQL Server). Puede usar el método IsUnicode para obligar a que una cadena sea de tipo varchar.

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .IsUnicode(false);

Configuración del tipo de datos de una columna de base de datos

El método HasColumnType permite la asignación a diferentes representaciones del mismo tipo básico. El uso de este método no permite realizar ninguna conversión de los datos en tiempo de ejecución. Tenga en cuenta que IsUnicode es la forma preferida de establecer columnas en varchar, ya que es independiente de la base de datos.

modelBuilder.Entity<Department>()   
    .Property(p => p.Name)   
    .HasColumnType("varchar");

Configuración de propiedades en un tipo complejo

Hay dos maneras de configurar propiedades escalares en un tipo complejo.

Puede llamar a Property en ComplexTypeConfiguration.

modelBuilder.ComplexType<Details>()
    .Property(t => t.Location)
    .HasMaxLength(20);

También puede usar la notación de puntos para tener acceso a una propiedad de un tipo complejo.

modelBuilder.Entity<OnsiteCourse>()
    .Property(t => t.Details.Location)
    .HasMaxLength(20);

Configuración de una propiedad que se usará como token de simultaneidad optimista

Para especificar que una propiedad de una entidad representa un token de simultaneidad, puede usar el atributo ConcurrencyCheck o el método IsConcurrencyToken.

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsConcurrencyToken();

También puede usar el método IsRowVersion para configurar la propiedad para que sea una versión de fila en la base de datos. Si se establece la propiedad para que sea una versión de fila, se configurará automáticamente para que sea un token de simultaneidad optimista.

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsRowVersion();

Asignación de tipos

Especificar que una clase es un tipo complejo

Por convención, un tipo que no tiene ninguna clave principal especificada se trata como un tipo complejo. Hay algunos escenarios en los que Code First no detectará un tipo complejo (por ejemplo, si tiene una propiedad denominada ID, pero no significa que sea una clave principal). En tales casos, usaría la API fluida para especificar explícitamente que un tipo es un tipo complejo.

modelBuilder.ComplexType<Details>();

Especificar que no se asigne un tipo de entidad CLR a una tabla de la base de datos

En el ejemplo siguiente se muestra cómo excluir un tipo CLR de la asignación a una tabla de la base de datos.

modelBuilder.Ignore<OnlineCourse>();

Asignación de un tipo de entidad a una tabla específica de la base de datos

Todas las propiedades de Department se asignarán a columnas de una tabla denominada t_ Department.

modelBuilder.Entity<Department>()  
    .ToTable("t_Department");

También puede especificar el nombre del esquema de la siguiente manera:

modelBuilder.Entity<Department>()  
    .ToTable("t_Department", "school");

Asignación de la herencia de tabla por jerarquía (TPH)

En este escenario de asignación de tabla por jerarquía, todos los tipos de una jerarquía de herencia se asignan a una única tabla. Se usa una columna discriminadora para identificar el tipo de cada fila. Al crear el modelo con Code First, la estrategia predeterminada para los tipos que participan en la jerarquía de herencia es la tabla por jerarquía. De forma predeterminada, la columna discriminadora se agrega a la tabla con el nombre "Discriminador” y el nombre de tipo CLR de cada tipo de la jerarquía se usa para los valores discriminadores. Puede modificar el comportamiento predeterminado mediante la API fluida.

modelBuilder.Entity<Course>()  
    .Map<Course>(m => m.Requires("Type").HasValue("Course"))  
    .Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));

Asignación de la herencia de tabla por tipo (TPT)

En este escenario de asignación de tabla por tipo, todos los tipos se asignan a tablas individuales. Las propiedades que pertenecen solamente a un tipo base o a un tipo derivado se almacenan en una tabla que se asigna a ese tipo. Las tablas que se asignan a tipos derivados también almacenan una clave externa que combina la tabla derivada con la tabla base.

modelBuilder.Entity<Course>().ToTable("Course");  
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");

Asignación de la herencia de tabla por clase concreta (TPC)

En el escenario de asignación de TPC, todos los tipos no abstractos de la jerarquía se asignan a tablas individuales. Las tablas que se asignan a las clases derivadas no tienen ninguna relación con la tabla que se asigna a la clase base de la base de datos. Todas las propiedades de una clase, incluidas las propiedades heredadas, se asignan a columnas de la tabla correspondiente.

Llame al método MapInheritedProperties para configurar cada tipo derivado. MapInheritedProperties vuelve a asignar todas las propiedades que se heredaron de la clase base a nuevas columnas de la tabla para la clase derivada.

Nota:

Dado que las tablas que participan en la jerarquía de herencia de TPC no comparten una clave principal, habrá claves de entidad duplicadas al insertar en tablas asignadas a subclases si tiene valores generados por la base de datos con la misma inicialización de identidad. Para solucionar este problema, puede especificar un valor de inicialización diferente para cada tabla o desactivar la identidad en la propiedad de clave principal. La identidad es el valor predeterminado para las propiedades de clave de número entero al trabajar con Code First.

modelBuilder.Entity<Course>()
    .Property(c => c.CourseID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

modelBuilder.Entity<OnsiteCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnsiteCourse");
});

modelBuilder.Entity<OnlineCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnlineCourse");
});

Asignación de propiedades de un tipo de entidad a varias tablas en la base de datos (división de entidades)

La división de entidades permite distribuir las propiedades de un tipo de entidad entre varias tablas. En el ejemplo siguiente, la entidad Department se divide en dos tablas: Department y DepartmentDetails. La división de entidades usa varias llamadas al método Map para asignar un subconjunto de propiedades a una tabla específica.

modelBuilder.Entity<Department>()
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Name });
        m.ToTable("Department");
    })
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Administrator, t.StartDate, t.Budget });
        m.ToTable("DepartmentDetails");
    });

Asignación de varios tipos de entidad a una tabla en la base de datos (división de tablas)

En el ejemplo siguiente se asignan dos tipos de entidad que comparten una clave principal a una tabla.

modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

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

modelBuilder.Entity<Instructor>().ToTable("Instructor");

modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");

Asignación de un tipo de entidad para insertar, actualizar o eliminar procedimientos almacenados (de EF6 en adelante)

A partir de EF6, puede asignar una entidad para usar procedimientos almacenados para insertar actualizaciones y eliminar. Para obtener más información, consulte Procedimientos almacenados Insertar, Actualizar y Eliminar de Code First.

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