Compartir vía


Tutorial: Implementación de la herencia con EF en una aplicación de ASP.NET MVC 5

En el tutorial anterior, se trataron las excepciones de simultaneidad. En este tutorial se muestra cómo implementar la herencia en el modelo de datos.

En la programación orientada a objetos, puede usar la herencia para facilitar la reutilización del código. En este tutorial, cambiará las clases Instructor y Student para que deriven de una clase base Person que contenga propiedades como LastName, que son comunes tanto para los instructores como para los alumnos. No tendrá que agregar ni cambiar ninguna página web, sino que cambiará parte del código y esos cambios se reflejarán automáticamente en la base de datos.

En este tutorial ha:

  • Aprender a asignar la herencia a la base de datos
  • Creación de la clase Person
  • Actualiza Instructor y Student
  • Agregar Person al modelo
  • Crear y actualizar migraciones
  • Prueba la implementación
  • Implementación en Azure

Requisitos previos

Asigna la herencia a la base de datos

Las clases Instructor y Student del modelo de datos School tienen varias propiedades idénticas:

Student_and_Instructor_classes

Imagine que quiere eliminar el código redundante de las propiedades que comparten las entidades Instructor y Student. O que quiere escribir un servicio que pueda dar formato a los nombres sin preocuparse de si el nombre es de un instructor o de un alumno. Puede crear una clase base Person que solo contenga las propiedades compartidas y después hacer que las entidades Instructor y Student hereden de esa clase base, como se muestra en la siguiente ilustración:

Student_and_Instructor_classes_deriving_from_Person_class

Esta estructura de herencia se puede representar de varias formas en la base de datos. Puede tener una sola tabla Person que incluya información sobre los alumnos y los instructores. Algunas de las columnas solo podrían aplicarse a los instructores (HireDate), algunas solo a los alumnos (EnrollmentDate) y algunas a ambas (LastName, FirstName). Lo más común sería que tuviera una columna discriminadora para indicar qué tipo representa cada fila. Por ejemplo, en la columna discriminadora podría aparecer "Instructor" para los instructores y "Student" para los alumnos.

Table-per-hierarchy_example

Este patrón de generación de una estructura de herencia de la entidad a partir de una tabla de base de datos única, se denomina herencia de tabla por jerarquía (TPH) .

Una alternativa consiste en hacer que la base de datos se parezca más a la estructura de herencia. Por ejemplo, podría tener solo los campos de nombre en la tabla Person y tablas Instructor y Student independientes con los campos de fecha.

Table-per-type_inheritance

Este patrón de creación de una tabla de base de datos para cada clase de entidad se denomina herencia de tabla por tipo (TPT).

Y todavía otra opción pasa por asignar todos los tipos no abstractos a tablas individuales. Todas las propiedades de una clase, incluidas las propiedades heredadas, se asignan a columnas de la tabla correspondiente. Este patrón se denomina herencia de tabla por clase concreta (TPC) . Si implementara la herencia de TPC en las clases Person, Student e Instructor tal como se ha mostrado antes, las tablas Student e Instructor se verían igual antes que después de implementar la herencia.

Los patrones de herencia TPC y TPH suelen ofrecer un mejor rendimiento en Entity Framework que los patrones de herencia TPT, debido a que los patrones TPT pueden dar lugar a consultas join complejas.

Este tutorial muestra cómo implementar la herencia de TPH. TPH es el patrón de herencia predeterminado en Entity Framework, por tanto, lo único que debe hacer es crear una clase Person, cambiar las clases Instructor y Student para derivar de Person, agregar la nueva clase a DbContext y crear una migración. (Para obtener información sobre cómo implementar los otros patrones de herencia, consulte Asignación de la herencia de tabla por tipo (TPT) y Asignación de la herencia de clase de tabla por tipo concreto (TPC) en la documentación de MSDN Entity Framework).

Creación de la clase Person

En la carpeta Models, cree Person.cs y reemplace el código de plantilla por el código siguiente:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public abstract class Person
    {
        public int ID { get; set; }

        [Required]
        [StringLength(50)]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
        [Required]
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        public string FirstMidName { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }
    }
}

Actualiza Instructor y Student

Ahora actualice el Instructor.cs y el Student.cs para heredar los valores de Person.sc.

En Instructor.cs, derive la clase Instructor de la clase Person y quite los campos de clave y nombre. El código tendrá un aspecto similar al ejemplo siguiente:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Instructor : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        public virtual ICollection<Course> Courses { get; set; }
        public virtual OfficeAssignment OfficeAssignment { get; set; }
    }
}

Realice cambios similares a Student.cs. La clase Student tendrá un aspecto similar al ejemplo siguiente:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student : Person
    {
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Agregar Person al modelo

En SchoolContext.cs, agregue una propiedad DbSet para el tipo de entidad Person:

public DbSet<Person> People { get; set; }

Esto es todo lo que Entity Framework necesita para configurar la herencia de tabla por jerarquía. Como verá, cuando se actualice la base de datos, tendrá una tabla Person en lugar de las tablas Student y Instructor.

Crear y actualizar migraciones

En la Consola del administrador de paquetes (PMC), escriba el comando siguiente:

Add-Migration Inheritance

Ejecute el comando Update-Database de PMC. El comando producirá un error en este punto porque tenemos datos existentes que las migraciones no saben cómo controlar. Recibirá un mensaje de error similar al siguiente:

No se puede quitar el objeto "dbo.Instructor". Hay una referencia a él en una restricción FOREIGN KEY.

Abra Migrations<timestamp>_Inheritance.cs y reemplace el método Up por el código siguiente:

public override void Up()
{
    // Drop foreign keys and indexes that point to tables we're going to drop.
    DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
    DropIndex("dbo.Enrollment", new[] { "StudentID" });

    RenameTable(name: "dbo.Instructor", newName: "Person");
    AddColumn("dbo.Person", "EnrollmentDate", c => c.DateTime());
    AddColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128, defaultValue: "Instructor"));
    AlterColumn("dbo.Person", "HireDate", c => c.DateTime());
    AddColumn("dbo.Person", "OldId", c => c.Int(nullable: true));

    // Copy existing Student data into new Person table.
    Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId FROM dbo.Student");

    // Fix up existing relationships to match new PK's.
    Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");

    // Remove temporary key
    DropColumn("dbo.Person", "OldId");

    DropTable("dbo.Student");

    // Re-create foreign keys and indexes pointing to new table.
    AddForeignKey("dbo.Enrollment", "StudentID", "dbo.Person", "ID", cascadeDelete: true);
    CreateIndex("dbo.Enrollment", "StudentID");
}

Este código se encarga de las siguientes tareas de actualización de la base de datos:

  • Quita las restricciones de la clave externa y los índices que apuntan a la tabla Student.

  • Cambia el nombre de la tabla Instructor por Person y realiza los cambios necesarios para que pueda almacenar datos de los alumnos:

    • Agrega EnrollmentDate que acepta valores NULL para alumnos.
    • Agrega la columna discriminadora para indicar si una fila es para un alumno o para un instructor.
    • Hace que HireDate admita un valor NULL, puesto que las filas de alumnos no dispondrán de fechas de contratación.
    • Agrega un campo temporal que se usará para actualizar las claves externas que apuntan a los alumnos. Cuando copie alumnos en la tabla Person, obtendrán nuevos valores de clave principal.
  • Copia datos de la tabla Student a la tabla Person. Esto hace que los alumnos se asignen a nuevos valores de clave principal.

  • Corrige los valores de clave externa correspondientes a los alumnos.

  • Vuelve a crear las restricciones y los índices de la clave externa, pero ahora los dirige a la tabla Person.

(Si hubiera usado el GUID en lugar de un número entero como tipo de clave principal, los valores de la clave principal de alumno no tendrían que cambiar y algunos de estos pasos se podrían haber omitido).

Vuelva a ejecutar el comando update-database.

(En un sistema de producción, haría los cambios correspondientes en el método Down por si alguna vez tuviera que usarlo para volver a la versión anterior de la base de datos. Para este tutorial, no se usará el método Down).

Nota:

Al migrar datos y hacer cambios en el esquema, es posible que se generen otros errores. Si recibe errores de migración que no puede resolver, puede continuar con el tutorial cambiando la cadena de conexión en el archivo Web.config o eliminando la base de datos. El enfoque más sencillo consiste en cambiar el nombre de la base de datos en el archivo Web.config. Por ejemplo, cambie el nombre de la base de datos a ContosoUniversity2 como se muestra en el ejemplo siguiente:

<add name="SchoolContext" 
    connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;" 
    providerName="System.Data.SqlClient" />

Con una base de datos nueva, no hay ningún dato para migrar y es mucho más probable que el comando update-database se complete sin errores. Para obtener instrucciones sobre cómo eliminar la base de datos, vea Procedimiento para quitar una base de datos de Visual Studio 2012. Si adopta este enfoque para continuar con el tutorial, omita el paso de implementación al final de este tutorial o implemente en un nuevo sitio y una base de datos. Si implementa una actualización en el mismo sitio en el que ya se ha implementado, EF obtendrá el mismo error cuando ejecute las migraciones automáticamente. Si desea solucionar un error de migración, el mejor recurso es uno de los foros de Entity Framework o StackOverflow.com.

Prueba la implementación

Ejecute el sitio y pruebe varias páginas. Todo funciona igual que antes.

En Explorador de servidores, expanda Data Connections\SchoolContext y después Tables, y verá que las tablas Student e Instructor se han reemplazado por una tabla Person. Abra el diseñador de la tabla Person y verá que contiene todas las columnas que solía haber en las tablas Student e Instructor.

Haga clic con el botón derecho en la tabla Person y después haga clic en Mostrar datos de tabla para ver la columna discriminadora.

En el diagrama siguiente, se muestra la estructura de la nueva base de datos School:

School_database_diagram

Implementación en Azure

Esta sección requiere que haya completado la sección opcional Implementación de la aplicación en Azure en la Parte 3: Ordenación, filtrado y paginación de esta serie de tutoriales. Si ha tenido errores de migración que ha resuelto mediante la eliminación de la base de datos en el proyecto local, omita este paso; o cree un sitio y una base de datos nuevos e impleméntelos en el nuevo entorno.

  1. En Visual Studio, haga clic con el botón derecho en el proyecto, en el Explorador de soluciones y seleccione Publicar en el menú contextual.

  2. Haga clic en Publicar.

    La aplicación web se abre en el explorador predeterminado.

  3. Pruebe la aplicación para comprobar que funciona.

    La primera vez que ejecuta una página que accede a la base de datos, Entity Framework ejecuta todos los métodos Up de migración necesarios para actualizar la base de datos con el modelo de datos actual.

Obtención del código

Descargar el proyecto completado

Recursos adicionales

Puede encontrar enlaces a otros recursos de Entity Framework en el Acceso a datos de ASP.NET: recursos recomendados.

Para más información sobre esta y otras estructuras de herencia, consulte Patrón de herencia TPT y Patrón de herencia TPH en MSDN. En el siguiente tutorial, aprenderá a controlar una serie de escenarios de Entity Framework relativamente avanzados.

Pasos siguientes

En este tutorial ha:

  • Aprendido a asignar la herencia a la base de datos
  • Creado la clase Person
  • Actualizado Instructor y Student
  • Agregado Person al modelo
  • Creado y actualizado migraciones
  • Probado la implementación
  • Implementado en Azure

Vaya al siguiente artículo para aprender temas importantes por tener en cuenta cuando profundice en los conceptos básicos del desarrollo de aplicaciones web ASP.NET que usan Entity Framework Core First.