Razor Pages con Entity Framework Core en ASP.NET Core: Tutorial 1 de 8

De Tom Dykstra, Jeremy Likness y Jon P Smith

Este es el primero de una serie de tutoriales en los que se muestra cómo usar Entity Framework (EF) Core en una aplicación Razor Pages en ASP.NET Core. En el tutorial se crea un sitio web de una universidad ficticia, Contoso University. El sitio incluye funciones como la admisión de alumnos, la creación de cursos y las asignaciones de instructores. En el tutorial se usa el enfoque de Code First. Para obtener información sobre cómo seguir este tutorial mediante el enfoque de Database First, consulte este problema de GitHub.

Descargue o vea la aplicación completa.Instrucciones de descarga.

Requisitos previos

Motores de bases de datos

En las instrucciones de Visual Studio se usa SQL Server LocalDB, una versión de SQL Server Express que solo se ejecuta en Windows.

Solución de problemas

Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Una buena forma de obtener ayuda consiste en publicar una pregunta en StackOverflow.com con la etiqueta ASP.NET Core o la etiqueta EF Core.

La aplicación de ejemplo

La aplicación compilada en estos tutoriales es un sitio web básico de una universidad. Los usuarios pueden ver y actualizar la información de estudiantes, cursos e instructores. Estas son algunas de las pantallas que se crean en el tutorial.

Students Index page

Students Edit page

El estilo de la interfaz de usuario de este sitio se basa en las plantillas de proyecto integradas. El enfoque del tutorial es cómo usar EF Core con ASP.NET Core, no cómo personalizar la interfaz de usuario.

Opcional: compilación de la descarga de ejemplo

Este paso es opcional. Se recomienda compilar la aplicación completa cuando tenga problemas que no pueda resolver. Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Instrucciones de descarga.

Seleccione ContosoUniversity.csproj para abrir el proyecto.

  • Compile el proyecto.

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

    Update-Database
    

Ejecute el proyecto para inicializar la base de datos.

Creación del proyecto de aplicación web

  1. Inicie Visual Studio 2022 y seleccione Crear un proyecto.

    Create a new project from the start window

  2. En el cuadro de diálogo Crear un proyecto nuevo, seleccione Aplicación web ASP.NET Core y, a continuación, seleccione Siguiente.

    Create an ASP.NET Core Web App

  3. En el cuadro de diálogo Configurar su nuevo proyecto, escriba ContosoUniversity en Nombre del proyecto. Es importante asignarle el nombre ContosoUniversity al proyecto (respetando mayúsculas y minúsculas) para que los espacios de nombres coincidan al copiar y pegar el código de ejemplo.

  4. Seleccione Next (Siguiente).

  5. En el cuadro de diálogo Información adicional, seleccione .NET 6.0 (Compatibilidad a largo plazo) y, después, Crear.

    Additional information

Configurar el estilo del sitio

Copie y pegue el siguiente código en el archivo Pages/Shared/_Layout.cshtml:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/ContosoUniversity.styles.css" asp-append-version="true" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
                    <ul class="navbar-nav flex-grow-1">                        
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

El archivo de diseño establece el encabezado, el pie de página y el menú del sitio. En el código anterior se realizan los cambios siguientes:

  • Todas las repeticiones de "ContosoUniversity" por "Contoso University". Hay tres repeticiones.
  • Se eliminan las entradas del menú Home y Privacy.
  • Se han agregado entradas para Acerca de, Estudiantes, Cursos, Instructores y Departamentos.

En Pages/Index.cshtml, reemplace el contenido del archivo por el código siguiente:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
@*                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
*@                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
@*                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
*@                </p>
            </div>
        </div>
    </div>
</div>

El código anterior reemplaza el texto sobre ASP.NET Core con texto sobre esta aplicación.

Ejecute la aplicación para comprobar que aparece la página principal.

El modelo de datos

En las secciones siguientes se crea un modelo de datos:

Course-Enrollment-Student data model diagram

Un alumno se puede inscribir en cualquier número de cursos y un curso puede tener cualquier número de alumnos inscritos.

La entidad Student

Student entity diagram

  • Cree una carpeta Models en la carpeta del proyecto.
  • Cree el archivo Models/Student.cs con el siguiente código:
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La propiedad ID se convierte en la columna de clave principal de la tabla de base de datos que se corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine ID o classnameID. Por tanto, el nombre que se reconoce de forma automática para la clave principal de la clase Student es StudentID. Para obtener más información, consulte EF Core: claves.

La propiedad Enrollments es una propiedad de navegación. Las propiedades de navegación contienen otras entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments de una entidad Student contiene todas las entidades Enrollment que están relacionadas con esa instancia de Student. Por ejemplo, si una fila Student de la base de datos tiene dos filas Enrollment relacionadas, la propiedad de navegación Enrollments contiene esas dos entidades Enrollment.

En la base de datos, una fila Enrollment se relaciona con una fila Student si su columna StudentID contiene el valor de identificador del estudiante. Por ejemplo, imagine que una fila Student tiene el identificador 1. Las filas Enrollment relacionadas tendrán StudentID = 1. StudentID es una clave externa de la tabla Enrollment.

La propiedad Enrollments se define como ICollection<Enrollment> porque puede haber varias entidades Enrollment relacionadas. Puede usar otros tipos de colección, como List<Enrollment> o HashSet<Enrollment>. Cuando se usa ICollection<Enrollment>, EF Core crea una colección HashSet<Enrollment> de forma predeterminada.

La entidad Enrollment

Enrollment entity diagram

Cree el archivo Models/Enrollment.cs con el siguiente código:

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La propiedad EnrollmentID es la clave principal; esta entidad usa el patrón classnameID en lugar de ID por sí solo. Para un modelo de datos de producción, muchos desarrolladores eligen un patrón y lo usan de forma coherente. En este tutorial se usan los dos simplemente para ilustrar el trabajo. El uso de ID sin classname facilita la implementación de algunos tipos de cambios del modelo de datos.

La propiedad Grade es una enum. El signo de interrogación después de la declaración de tipo Grade indica que la propiedad Gradeacepta valores NULL. Una calificación que sea NULL es diferente de una calificación que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.

La propiedad StudentID es una clave externa y la propiedad de navegación correspondiente es Student. Una entidad Enrollment está asociada con una entidad Student, por lo que la propiedad contiene una única entidad Student.

La propiedad CourseID es una clave externa y la propiedad de navegación correspondiente es Course. Una entidad Enrollment está asociada con una entidad Course.

EF Core interpreta una propiedad como una clave externa si se denomina <navigation property name><primary key property name>. Por ejemplo, StudentID es la clave externa para la propiedad de navegación Student, ya que la clave principal de la entidad Student es ID. Las propiedades de clave externa también se pueden denominar <primary key property name>. Por ejemplo CourseID, dado que la clave principal de la entidad Course es CourseID.

La entidad Course

Course entity diagram

Cree el archivo Models/Course.cs con el siguiente código:

using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

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

La propiedad Enrollments es una propiedad de navegación. Una entidad Course puede estar relacionada con cualquier número de entidades Enrollment.

El atributo DatabaseGenerated permite que la aplicación especifique la clave principal en lugar de hacer que la base de datos la genere.

Compile la aplicación. El compilador genera varias advertencias sobre cómo se controlan los valores null. Consulte esta incidencia de GitHub, Tipos de referencia que aceptan valores NULL y Tutorial: expresar la intención de diseño más claramente con tipos de referencia que aceptan y que no aceptan valores NULL para obtener más información.

Para eliminar las advertencias de los tipos de referencia que aceptan valores NULL, quite la siguiente línea del archivo ContosoUniversity.csproj:

<Nullable>enable</Nullable>

Actualmente, el motor de scaffolding no admite tipos de referencia que aceptan valores NULL, por lo que los modelos usados en scaffold tampoco.

Quite la anotación de tipo de referencia que acepta valores NULL ? de public string? RequestId { get; set; } en Pages/Error.cshtml.cs para que el proyecto se compile sin advertencias del compilador.

Scaffolding de las páginas Student

En esta sección, se usa la herramienta de scaffolding de ASP.NET Core para generar lo siguiente:

  • Una clase DbContext de EF Core. El contexto es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos determinado. Se deriva de la clase Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages que controlan las operaciones de creación, lectura, actualización y eliminación (CRUD) de la entidad Student.
  • Cree una carpeta Pages/Students.
  • En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Pages/Students y seleccione Agregar>Nuevo elemento con scaffold.
  • En el cuadro de diálogo Agregar nuevo elemento scaffold:
    • En la pestaña de la izquierda, seleccione Instalado > Común >Razor Páginas
    • Seleccione Páginas de Razor que usan Entity Framework (CRUD)>AGREGAR.
  • En el cuadro de diálogo para agregar instancias de Razor Pages que usan Entity Framework (CRUD):
    • En la lista desplegable Clase de modelo, seleccione Student (ContosoUniversity.Models) .
    • En la fila Clase de contexto de datos, seleccione el signo + (más).
      • Cambie el nombre del contexto de datos para que acabe en SchoolContext en lugar de ContosoUniversityContext. Nombre del contexto actualizado: ContosoUniversity.Data.SchoolContext.
      • Seleccione Agregar para terminar de agregar la clase de contexto de datos.
      • Seleccione Agregar para finalizar el cuadro de diálogo Agregar Razor Pages.

Los paquetes siguientes se instalan de forma automática:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Si el paso anterior falla, compile el proyecto y vuelva a intentar el paso de scaffolding.

El proceso de scaffolding:

  • Crea Razor Pages en la carpeta Pages/Students:
    • Create.cshtml y Create.cshtml.cs
    • Delete.cshtml y Delete.cshtml.cs
    • Details.cshtml y Details.cshtml.cs
    • Edit.cshtml y Edit.cshtml.cs
    • Index.cshtml y Index.cshtml.cs
  • Crea Data/SchoolContext.cs.
  • Agrega el contexto a la inserción de dependencias en Program.cs.
  • Agrega una cadena de conexión de la base de datos a appsettings.json .

Cadena de conexión de base de datos

La herramienta de scaffolding genera una cadena de conexión en el archivo appsettings.json .

La cadena de conexión especifica SQL Server LocalDB:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB es una versión ligera del motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en producción. De forma predeterminada, LocalDB crea archivos .mdf en el directorio C:/Users/<user>.

Actualización de la clase de contexto de base de datos

La clase principal que coordina la funcionalidad de EF Core para un modelo de datos determinado es la clase context de la base de datos. El contexto se deriva de Microsoft.EntityFrameworkCore.DbContext. En el contexto se especifica qué entidades se incluyen en el modelo de datos. En este proyecto, la clase se denomina SchoolContext.

Actualice Data/SchoolContext.cs con el siguiente código:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

El código anterior cambia del elemento DbSet<Student> Student singular al DbSet<Student> Students plural. Para que el código de las páginas Razor coincida con el nombre nuevo de DBSet, realice un cambio global de: _context.Student. a: _context.Students.

Hay ocho repeticiones.

Dado que un conjunto de entidades contiene varias entidades, muchos desarrolladores prefieren que los nombres de la propiedad DBSet sean plurales.

El código resaltado:

  • Crea una propiedad DbSet<TEntity> para cada conjunto de entidades. En terminología de EF Core:
    • Un conjunto de entidades normalmente se corresponde a una tabla de base de datos.
    • Una entidad se corresponde con una fila de la tabla.
  • Llama a OnModelCreating. OnModelCreating:
    • se llama cuando SchoolContext se ha inicializado, pero antes de que el modelo se haya bloqueado y utilizado para inicializar el contexto.
    • Es necesario porque, más adelante en el tutorial, la entidad Student tendrá referencias a las demás entidades.

Esperamos corregir este problema en una versión futura.

Program.cs

ASP.NET Core integra la inserción de dependencias. Los servicios como SchoolContext se registran con inserción de dependencias durante el inicio de la aplicación. Estos servicios se proporcionan a los componentes que los necesitan, como páginas Razor, a través de parámetros de constructor. El código de constructor que obtiene una instancia de contexto de base de datos se muestra más adelante en el tutorial.

La herramienta de scaffolding ha registrado de forma automática la clase de contexto con el contenedor de inserción de dependencias.

El proveedor de scaffolding ha agregado las líneas resaltadas siguientes:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto DbContextOptions. Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena de conexión desde el archivo appsettings.json o appsettings.Development.json.

Incorporación del filtro de excepción de base de datos

Agregue AddDatabaseDeveloperPageExceptionFilter y UseMigrationsEndPoint, tal como se muestra en el código siguiente:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

Agregue el paquete NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

En la Consola del administrador de paquetes, escriba esto para agregar el paquete NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

El paquete NuGet de Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore proporciona middleware de ASP.NET Core para páginas de error de Entity Framework Core. Este middleware ayuda a detectar y diagnosticar errores con migraciones de Entity Framework Core.

AddDatabaseDeveloperPageExceptionFilter proporciona información de error útil en el entorno de desarrollo para errores de migración de EF.

Creación de la base de datos

Actualice Program.cs para crear la base de datos si no existe:

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddDbContext<SchoolContext>(options =>
  options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
else
{
    app.UseDeveloperExceptionPage();
    app.UseMigrationsEndPoint();
}

using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    // DbInitializer.Initialize(context);
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

El método EnsureCreated no realiza ninguna acción si existe una base de datos para el contexto. Si no existe ninguna base de datos, se crean la base de datos y el esquema. EnsureCreated habilita el flujo de trabajo siguiente para controlar los cambios del modelo de datos:

  • Se elimina la base de datos. Se pierden los datos existentes.
  • Se cambia el modelo de datos. Por ejemplo, se agrega un campo EmailAddress.
  • Ejecutar la aplicación.
  • EnsureCreated crea una base de datos con el esquema nuevo.

Este flujo de trabajo funciona bien al principio de la fase de desarrollo, cuando el esquema evoluciona rápidamente, siempre y cuando no sea necesario conservar los datos. La situación es distinta cuando es necesario conservar los datos introducidos en la base de datos. En ese caso, use las migraciones.

Más adelante en la serie de tutoriales, se eliminará la base de datos creada por EnsureCreated y se usarán las migraciones. Una base de datos creada por EnsureCreated no se puede actualizar mediante migraciones.

Prueba de la aplicación

  • Ejecutar la aplicación.
  • Haga clic en el vínculo Students y, después, en Crear nuevo.
  • Pruebe los vínculos Edit, Details y Delete.

Inicializar la base de datos

El método EnsureCreated crea una base de datos vacía. En esta sección se agrega código que rellena la base de datos con datos de prueba.

Cree el archivo Data/DbInitializer.cs con el siguiente código:

using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

El código comprueba si hay alumnos en la base de datos. Si no hay ningún alumno, agrega datos de prueba a la base de datos. Crea los datos de prueba en matrices en lugar de colecciones List<T> para optimizar el rendimiento.

  • En Program.cs, quite // en la línea DbInitializer.Initialize:
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<SchoolContext>();
    context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
}
  • Detenga la aplciación si se está ejecutando y ejecute el comando siguiente en la Consola del Administrador de paquetes (PMC):

    Drop-Database -Confirm
    
    
  • Responda con Y para eliminar la base de datos.

  • Reinicie la aplicación.
  • Seleccione la página Students para ver los datos inicializados.

Consulta la base de datos

  • Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio.
  • En SSOX, seleccione (localdb)\MSSQLLocalDB > Bases de datos > SchoolContext-{GUID}. El nombre de la base de datos se genera a partir del nombre de contexto proporcionado anteriormente, más un guión y un GUID.
  • Expanda el nodo Tablas.
  • Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se crearon y las filas que se insertaron en la tabla.
  • Haga clic con el botón derecho en la tabla Student y haga clic en Ver código para ver cómo el modelo Student se asigna al esquema de tabla Student.

Métodos asincrónicos de EF en aplicaciones web de ASP.NET Core

La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.

Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código asincrónico permite que los recursos de servidor se usen de forma más eficaz y el servidor pueda administrar más tráfico sin retrasos.

El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones de poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico elevado, la posible mejora del rendimiento es importante.

En el código siguiente, la palabra clave async, el valor devuelto Task, la palabra clave await y el método ToListAsync hacen que el código se ejecute de forma asincrónica.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • La palabra clave async indica al compilador que:
    • Genere devoluciones de llamada para partes del cuerpo del método.
    • Cree el objeto Task que se devuelve.
  • El tipo devuelto Task representa el trabajo en curso.
  • La palabra clave await hace que el compilador divida el método en dos partes. La primera parte termina con la operación que se inició de forma asincrónica. La segunda parte se coloca en un método de devolución de llamada que se llama cuando finaliza la operación.
  • ToListAsync es la versión asincrónica del método de extensión ToList.

Algunos aspectos que tener en cuenta al escribir código asincrónico en el que se usa EF Core son los siguientes:

  • Solo se ejecutan de forma asincrónica las instrucciones que hacen que las consultas o los comandos se envíen a la base de datos. Esto incluye ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync y SaveChangesAsync. No incluye las instrucciones que solo cambian una IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un contexto de EF Core no es seguro para subprocesos: no intente realizar varias operaciones en paralelo.
  • Para aprovechar las ventajas de rendimiento del código asincrónico, compruebe que en los paquetes de biblioteca (por ejemplo para la paginación) se usa async si llaman a métodos de EF Core que envían consultas a la base de datos.

Para obtener más información sobre la programación asincrónica en .NET, vea Programación asincrónica y Programación asincrónica con async y await.

Advertencia

La implementación asincrónica de Microsoft. Data.SqlClient tiene algunos problemas conocidos (#593, #601 y otros). Si observa problemas inesperados de rendimiento, pruebe a usar la ejecución del comando de sincronización en su lugar, especialmente cuando trabaje con valores binarios o de texto grandes.

Consideraciones de rendimiento

En general, una página web no debe cargar un número arbitrario de filas. Una consulta debe utilizar la paginación o un enfoque de limitación. Por ejemplo, la consulta anterior podría usar Take para limitar las filas devueltas:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

Enumerar una tabla grande en una vista podría devolver una respuesta HTTP 200 parcialmente construida si se produce una excepción de base de datos en mitad de la enumeración.

La paginación se trata más adelante en el tutorial.

Para obtener más información, vea Consideraciones sobre el rendimiento (EF).

Pasos siguientes

Uso de SQLite para desarrollo y SQL Server para producción

Este es el primero de una serie de tutoriales en los que se muestra cómo usar Entity Framework (EF) Core en una aplicación Razor Pages en ASP.NET Core. En el tutorial se crea un sitio web de una universidad ficticia, Contoso University. El sitio incluye funciones como la admisión de alumnos, la creación de cursos y las asignaciones de instructores. En el tutorial se usa el enfoque de Code First. Para obtener información sobre cómo seguir este tutorial mediante el enfoque de Database First, consulte este problema de GitHub.

Descargue o vea la aplicación completa.Instrucciones de descarga.

Requisitos previos

Motores de bases de datos

En las instrucciones de Visual Studio se usa SQL Server LocalDB, una versión de SQL Server Express que solo se ejecuta en Windows.

Solución de problemas

Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Una buena forma de obtener ayuda consiste en publicar una pregunta en StackOverflow.com con la etiqueta ASP.NET Core o la etiqueta EF Core.

La aplicación de ejemplo

La aplicación compilada en estos tutoriales es un sitio web básico de una universidad. Los usuarios pueden ver y actualizar la información de estudiantes, cursos e instructores. Estas son algunas de las pantallas que se crean en el tutorial.

Students Index page

Students Edit page

El estilo de la interfaz de usuario de este sitio se basa en las plantillas de proyecto integradas. El enfoque del tutorial es cómo usar EF Core con ASP.NET Core, no cómo personalizar la interfaz de usuario.

Opcional: compilación de la descarga de ejemplo

Este paso es opcional. Se recomienda compilar la aplicación completa cuando tenga problemas que no pueda resolver. Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Instrucciones de descarga.

Seleccione ContosoUniversity.csproj para abrir el proyecto.

  • Compile el proyecto.
  • En la Consola del administrador de paquetes (PMC), ejecute el comando siguiente:
Update-Database

Ejecute el proyecto para inicializar la base de datos.

Creación del proyecto de aplicación web

  1. Inicie Visual Studio y seleccione Crear un proyecto.
  2. En el cuadro de diálogo Crear un proyecto, seleccione Aplicación web ASP.NET Core>Siguiente.
  3. En el cuadro de diálogo Configurar su nuevo proyecto, escriba ContosoUniversity en Nombre del proyecto. Es importante usar este nombre exacto, incluido el uso de mayúsculas, para que cada namespace coincida cuando se copie el código.
  4. Seleccione Crear.
  5. En el cuadro de diálogo Crear una aplicación web ASP.NET Core, seleccione:
    1. .NET Core y ASP.NET Core 5.0 en los menús desplegables.
    2. Aplicación web de ASP.NET Core.
    3. CrearNew ASP.NET Core Project dialog

Configurar el estilo del sitio

Copie y pegue el siguiente código en el archivo Pages/Shared/_Layout.cshtml:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2021 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

El archivo de diseño establece el encabezado, el pie de página y el menú del sitio. En el código anterior se realizan los cambios siguientes:

  • Todas las repeticiones de "ContosoUniversity" por "Contoso University". Hay tres repeticiones.
  • Se eliminan las entradas del menú Home y Privacy.
  • Se han agregado entradas para Acerca de, Estudiantes, Cursos, Instructores y Departamentos.

En Pages/Index.cshtml, reemplace el contenido del archivo por el código siguiente:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

El código anterior reemplaza el texto sobre ASP.NET Core con texto sobre esta aplicación.

Ejecute la aplicación para comprobar que aparece la página principal.

El modelo de datos

En las secciones siguientes se crea un modelo de datos:

Course-Enrollment-Student data model diagram

Un alumno se puede inscribir en cualquier número de cursos y un curso puede tener cualquier número de alumnos inscritos.

La entidad Student

Student entity diagram

  • Cree una carpeta Models en la carpeta del proyecto.

  • Cree el archivo Models/Student.cs con el siguiente código:

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La propiedad ID se convierte en la columna de clave principal de la tabla de base de datos que se corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine ID o classnameID. Por tanto, el nombre que se reconoce de forma automática para la clave principal de la clase Student es StudentID. Para obtener más información, consulte EF Core: claves.

La propiedad Enrollments es una propiedad de navegación. Las propiedades de navegación contienen otras entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments de una entidad Student contiene todas las entidades Enrollment que están relacionadas con esa instancia de Student. Por ejemplo, si una fila Student de la base de datos tiene dos filas Enrollment relacionadas, la propiedad de navegación Enrollments contiene esas dos entidades Enrollment.

En la base de datos, una fila Enrollment se relaciona con una fila Student si su columna StudentID contiene el valor de identificador del estudiante. Por ejemplo, imagine que una fila Student tiene el identificador 1. Las filas Enrollment relacionadas tendrán StudentID = 1. StudentID es una clave externa de la tabla Enrollment.

La propiedad Enrollments se define como ICollection<Enrollment> porque puede haber varias entidades Enrollment relacionadas. Puede usar otros tipos de colección, como List<Enrollment> o HashSet<Enrollment>. Cuando se usa ICollection<Enrollment>, EF Core crea una colección HashSet<Enrollment> de forma predeterminada.

La entidad Enrollment

Enrollment entity diagram

Cree el archivo Models/Enrollment.cs con el siguiente código:

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La propiedad EnrollmentID es la clave principal; esta entidad usa el patrón classnameID en lugar de ID por sí solo. Para un modelo de datos de producción, muchos desarrolladores eligen un patrón y lo usan de forma coherente. En este tutorial se usan los dos simplemente para ilustrar el trabajo. El uso de ID sin classname facilita la implementación de algunos tipos de cambios del modelo de datos.

La propiedad Grade es una enum. El signo de interrogación después de la declaración de tipo Grade indica que la propiedad Gradeacepta valores NULL. Una calificación que sea NULL es diferente de una calificación que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.

La propiedad StudentID es una clave externa y la propiedad de navegación correspondiente es Student. Una entidad Enrollment está asociada con una entidad Student, por lo que la propiedad contiene una única entidad Student.

La propiedad CourseID es una clave externa y la propiedad de navegación correspondiente es Course. Una entidad Enrollment está asociada con una entidad Course.

EF Core interpreta una propiedad como una clave externa si se denomina <navigation property name><primary key property name>. Por ejemplo, StudentID es la clave externa para la propiedad de navegación Student, ya que la clave principal de la entidad Student es ID. Las propiedades de clave externa también se pueden denominar <primary key property name>. Por ejemplo CourseID, dado que la clave principal de la entidad Course es CourseID.

La entidad Course

Course entity diagram

Cree el archivo Models/Course.cs con el siguiente código:

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

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

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

La propiedad Enrollments es una propiedad de navegación. Una entidad Course puede estar relacionada con cualquier número de entidades Enrollment.

El atributo DatabaseGenerated permite que la aplicación especifique la clave principal en lugar de hacer que la base de datos la genere.

Compile el proyecto para comprobar que no hay errores de compilación.

Scaffolding de las páginas Student

En esta sección, se usa la herramienta de scaffolding de ASP.NET Core para generar lo siguiente:

  • Una clase DbContext de EF Core. El contexto es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos determinado. Se deriva de la clase Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages que controlan las operaciones de creación, lectura, actualización y eliminación (CRUD) de la entidad Student.
  • Cree una carpeta Pages/Students.
  • En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Pages/Students y seleccione Agregar>Nuevo elemento con scaffold.
  • En el cuadro de diálogo Agregar nuevo elemento scaffold:
    • En la pestaña de la izquierda, seleccione Instalado > Común >Razor Páginas
    • Seleccione Páginas de Razor que usan Entity Framework (CRUD)>AGREGAR.
  • En el cuadro de diálogo para agregar instancias de Razor Pages que usan Entity Framework (CRUD):
    • En la lista desplegable Clase de modelo, seleccione Student (ContosoUniversity.Models) .
    • En la fila Clase de contexto de datos, seleccione el signo + (más).
      • Cambie el nombre del contexto de datos para que acabe en SchoolContext en lugar de ContosoUniversityContext. Nombre del contexto actualizado: ContosoUniversity.Data.SchoolContext.
      • Seleccione Agregar para terminar de agregar la clase de contexto de datos.
      • Seleccione Agregar para finalizar el cuadro de diálogo Agregar Razor Pages.

Si se produce el error 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.' en el scaffolding, vuelva a ejecutar la herramienta de scaffold o consulte esta incidencia de GitHub.

Los paquetes siguientes se instalan de forma automática:

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.VisualStudio.Web.CodeGeneration.Design

Si el paso anterior falla, compile el proyecto y vuelva a intentar el paso de scaffolding.

El proceso de scaffolding:

  • Crea Razor Pages en la carpeta Pages/Students:
    • Create.cshtml y Create.cshtml.cs
    • Delete.cshtml y Delete.cshtml.cs
    • Details.cshtml y Details.cshtml.cs
    • Edit.cshtml y Edit.cshtml.cs
    • Index.cshtml y Index.cshtml.cs
  • Crea Data/SchoolContext.cs.
  • Agrega el contexto a la inserción de dependencias en Startup.cs.
  • Agrega una cadena de conexión de la base de datos a appsettings.json .

Cadena de conexión de base de datos

La herramienta de scaffolding genera una cadena de conexión en el archivo appsettings.json .

La cadena de conexión especifica SQL Server LocalDB:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB es una versión ligera del motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en producción. De forma predeterminada, LocalDB crea archivos .mdf en el directorio C:/Users/<user>.

Actualización de la clase de contexto de base de datos

La clase principal que coordina la funcionalidad de EF Core para un modelo de datos determinado es la clase context de la base de datos. El contexto se deriva de Microsoft.EntityFrameworkCore.DbContext. En el contexto se especifica qué entidades se incluyen en el modelo de datos. En este proyecto, la clase se denomina SchoolContext.

Actualice Data/SchoolContext.cs con el siguiente código:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

El código anterior cambia del elemento DbSet<Student> Student singular al DbSet<Student> Students plural. Para que el código de las páginas Razor coincida con el nombre nuevo de DBSet, realice un cambio global de: _context.Student. a: _context.Students.

Hay ocho repeticiones.

Dado que un conjunto de entidades contiene varias entidades, muchos desarrolladores prefieren que los nombres de la propiedad DBSet sean plurales.

El código resaltado:

  • Crea una propiedad DbSet<TEntity> para cada conjunto de entidades. En terminología de EF Core:
    • Un conjunto de entidades normalmente se corresponde a una tabla de base de datos.
    • Una entidad se corresponde con una fila de la tabla.
  • Llama a OnModelCreating. OnModelCreating:
    • se llama cuando SchoolContext se ha inicializado, pero antes de que el modelo se haya bloqueado y utilizado para inicializar el contexto.
    • Es necesario porque, más adelante en el tutorial, la entidad Student tendrá referencias a las demás entidades.

Compile el proyecto para comprobar que no haya errores del compilador.

Startup.cs

ASP.NET Core integra la inserción de dependencias. Los servicios como SchoolContext se registran con inserción de dependencias durante el inicio de la aplicación. Estos servicios se proporcionan a los componentes que los necesitan, como páginas Razor, a través de parámetros de constructor. El código de constructor que obtiene una instancia de contexto de base de datos se muestra más adelante en el tutorial.

La herramienta de scaffolding ha registrado de forma automática la clase de contexto con el contenedor de inserción de dependencias.

El proveedor de scaffolding ha agregado las líneas resaltadas siguientes:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}

El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto DbContextOptions. Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena de conexión desde el archivo appsettings.json.

Incorporación del filtro de excepción de base de datos

Agregue AddDatabaseDeveloperPageExceptionFilter y UseMigrationsEndPoint, tal como se muestra en el código siguiente:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
       options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseMigrationsEndPoint();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Agregue el paquete NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

En la Consola del administrador de paquetes, escriba esto para agregar el paquete NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

El paquete NuGet de Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore proporciona middleware de ASP.NET Core para páginas de error de Entity Framework Core. Este middleware ayuda a detectar y diagnosticar errores con migraciones de Entity Framework Core.

AddDatabaseDeveloperPageExceptionFilter proporciona información de error útil en el entorno de desarrollo para errores de migración de EF.

Creación de la base de datos

Actualice Program.cs para crear la base de datos si no existe:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

El método EnsureCreated no realiza ninguna acción si existe una base de datos para el contexto. Si no existe ninguna base de datos, se crean la base de datos y el esquema. EnsureCreated habilita el flujo de trabajo siguiente para controlar los cambios del modelo de datos:

  • Se elimina la base de datos. Se pierden los datos existentes.
  • Se cambia el modelo de datos. Por ejemplo, se agrega un campo EmailAddress.
  • Ejecutar la aplicación.
  • EnsureCreated crea una base de datos con el esquema nuevo.

Este flujo de trabajo funciona bien al principio de la fase de desarrollo, cuando el esquema evoluciona rápidamente, siempre y cuando no sea necesario conservar los datos. La situación es distinta cuando es necesario conservar los datos introducidos en la base de datos. En ese caso, use las migraciones.

Más adelante en la serie de tutoriales, se eliminará la base de datos creada por EnsureCreated y se usarán las migraciones. Una base de datos creada por EnsureCreated no se puede actualizar mediante migraciones.

Prueba de la aplicación

  • Ejecutar la aplicación.
  • Haga clic en el vínculo Students y, después, en Crear nuevo.
  • Pruebe los vínculos Edit, Details y Delete.

Inicializar la base de datos

El método EnsureCreated crea una base de datos vacía. En esta sección se agrega código que rellena la base de datos con datos de prueba.

Cree el archivo Data/DbInitializer.cs con el siguiente código:

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

El código comprueba si hay alumnos en la base de datos. Si no hay ningún alumno, agrega datos de prueba a la base de datos. Crea los datos de prueba en matrices en lugar de colecciones List<T> para optimizar el rendimiento.

  • En Program.cs, quite // en la línea DbInitializer.Initialize:

      context.Database.EnsureCreated();
      DbInitializer.Initialize(context);
    
  • Detenga la aplciación si se está ejecutando y ejecute el comando siguiente en la Consola del Administrador de paquetes (PMC):

    Drop-Database -Confirm
    
    
  • Responda con Y para eliminar la base de datos.

  • Reinicie la aplicación.
  • Seleccione la página Students para ver los datos inicializados.

Consulta la base de datos

  • Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio.
  • En SSOX, seleccione (localdb)\MSSQLLocalDB > Bases de datos > SchoolContext-{GUID}. El nombre de la base de datos se genera a partir del nombre de contexto proporcionado anteriormente, más un guión y un GUID.
  • Expanda el nodo Tablas.
  • Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se crearon y las filas que se insertaron en la tabla.
  • Haga clic con el botón derecho en la tabla Student y haga clic en Ver código para ver cómo el modelo Student se asigna al esquema de tabla Student.

Código asincrónico

La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.

Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código asincrónico permite que los recursos de servidor se usen de forma más eficaz y el servidor pueda administrar más tráfico sin retrasos.

El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones de poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico elevado, la posible mejora del rendimiento es importante.

En el código siguiente, la palabra clave async, el valor devuelto Task, la palabra clave await y el método ToListAsync hacen que el código se ejecute de forma asincrónica.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • La palabra clave async indica al compilador que:
    • Genere devoluciones de llamada para partes del cuerpo del método.
    • Cree el objeto Task que se devuelve.
  • El tipo devuelto Task representa el trabajo en curso.
  • La palabra clave await hace que el compilador divida el método en dos partes. La primera parte termina con la operación que se inició de forma asincrónica. La segunda parte se coloca en un método de devolución de llamada que se llama cuando finaliza la operación.
  • ToListAsync es la versión asincrónica del método de extensión ToList.

Algunos aspectos que tener en cuenta al escribir código asincrónico en el que se usa EF Core son los siguientes:

  • Solo se ejecutan de forma asincrónica las instrucciones que hacen que las consultas o los comandos se envíen a la base de datos. Esto incluye ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync y SaveChangesAsync. No incluye las instrucciones que solo cambian una IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un contexto de EF Core no es seguro para subprocesos: no intente realizar varias operaciones en paralelo.
  • Para aprovechar las ventajas de rendimiento del código asincrónico, compruebe que en los paquetes de biblioteca (por ejemplo para la paginación) se usa async si llaman a métodos de EF Core que envían consultas a la base de datos.

Para obtener más información sobre la programación asincrónica en .NET, vea Programación asincrónica y Programación asincrónica con async y await.

Consideraciones de rendimiento

En general, una página web no debe cargar un número arbitrario de filas. Una consulta debe utilizar la paginación o un enfoque de limitación. Por ejemplo, la consulta anterior podría usar Take para limitar las filas devueltas:

public async Task OnGetAsync()
{
    Student = await _context.Students.Take(10).ToListAsync();
}

Enumerar una tabla grande en una vista podría devolver una respuesta HTTP 200 parcialmente construida si se produce una excepción de base de datos en mitad de la enumeración.

MaxModelBindingCollectionSize tiene como valor predeterminado 1024. El código siguiente establece MaxModelBindingCollectionSize:

public void ConfigureServices(IServiceCollection services)
{
    var myMaxModelBindingCollectionSize = Convert.ToInt32(
                Configuration["MyMaxModelBindingCollectionSize"] ?? "100");

    services.Configure<MvcOptions>(options =>
           options.MaxModelBindingCollectionSize = myMaxModelBindingCollectionSize);

    services.AddRazorPages();

    services.AddDbContext<SchoolContext>(options =>
          options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));

    services.AddDatabaseDeveloperPageExceptionFilter();
}

Consulte Configuración para obtener información sobre valores de configuración como MyMaxModelBindingCollectionSize.

La paginación se trata más adelante en el tutorial.

Para obtener más información, vea Consideraciones sobre el rendimiento (EF).

Registros SQL de Entity Framework Core

La configuración de registros suele proporcionarla la sección Logging de los archivos appsettings.{Environment}.json. Para registrar instrucciones SQL, agregue "Microsoft.EntityFrameworkCore.Database.Command": "Information" al archivo appsettings.Development.json:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
     ,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
    }
  },
  "AllowedHosts": "*"
}

Con el archivo JSON anterior, las instrucciones SQL se muestran en la línea de comandos y en ventana de salida de Visual Studio.

Para más información, vea Registrarse en .NET Core y ASP.NET Core y esta incidencia de GitHub.

Pasos siguientes

Uso de SQLite para desarrollo y SQL Server para producción

Este es el primero de una serie de tutoriales en los que se muestra cómo usar Entity Framework (EF) Core en una aplicación Razor Pages en ASP.NET Core. En el tutorial se crea un sitio web de una universidad ficticia, Contoso University. El sitio incluye funciones como la admisión de alumnos, la creación de cursos y las asignaciones de instructores. En el tutorial se usa el enfoque de Code First. Para obtener información sobre cómo seguir este tutorial mediante el enfoque de Database First, consulte este problema de GitHub.

Descargue o vea la aplicación completa.Instrucciones de descarga.

Requisitos previos

Motores de bases de datos

En las instrucciones de Visual Studio se usa SQL Server LocalDB, una versión de SQL Server Express que solo se ejecuta en Windows.

En las instrucciones de Visual Studio Code se usa SQLite, un motor de base de datos multiplataforma.

Si decide usar SQLite, descargue e instale una herramienta de terceros para administrar y ver una base de datos de SQLite, como DB Browser for SQLite.

Solución de problemas

Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Una buena forma de obtener ayuda consiste en publicar una pregunta en StackOverflow.com con la etiqueta ASP.NET Core o la etiqueta EF Core.

La aplicación de ejemplo

La aplicación compilada en estos tutoriales es un sitio web básico de una universidad. Los usuarios pueden ver y actualizar la información de estudiantes, cursos e instructores. Estas son algunas de las pantallas que se crean en el tutorial.

Students Index page

Students Edit page

El estilo de la interfaz de usuario de este sitio se basa en las plantillas de proyecto integradas. El enfoque del tutorial es cómo usar EF Core, no cómo personalizar la interfaz de usuario.

Siga el vínculo de la parte superior de la página para obtener el código fuente para el proyecto completado. La carpeta cu30 contiene el código para la versión ASP.NET Core 3.0 del tutorial. Los archivos que reflejan el estado del código para los tutoriales 1-7 se pueden encontrar en la carpeta cu30snapshots.

Para ejecutar la aplicación después de descargar el proyecto completado:

  • Compile el proyecto.

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

    Update-Database
    
  • Ejecute el proyecto para inicializar la base de datos.

Creación del proyecto de aplicación web

  • En el menú Archivo de Visual Studio, seleccione Nuevo>Proyecto.
  • Seleccione Aplicación web de ASP.NET Core.
  • Asigne el nombre ContosoUniversity al proyecto. Es importante usar este nombre exacto incluido el uso de mayúsculas, para que los espacios de nombres coincidan cuando se copie y pegue el código.
  • Seleccione .NET Core y ASP.NET Core 3.0 en las listas desplegables y, luego, Aplicación web.

Configurar el estilo del sitio

Configure el encabezado, el pie de página y el menú del sitio mediante la actualización de Pages/Shared/_Layout.cshtml:

  • Cambie todas las repeticiones de "ContosoUniversity" por "Contoso University". Hay tres repeticiones.

  • Elimine las entradas de menú Home y Privacy, y agregue entradas para About, Students, Courses, Instructors y Departments.

Los cambios aparecen resaltados.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Contoso University - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

En Pages/Index.cshtml, reemplace el contenido del archivo con el código siguiente para reemplazar el texto sobre ASP.NET Core con texto sobre esta aplicación:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

Ejecute la aplicación para comprobar que aparece la página principal.

El modelo de datos

En las secciones siguientes se crea un modelo de datos:

Course-Enrollment-Student data model diagram

Un alumno se puede inscribir en cualquier número de cursos y un curso puede tener cualquier número de alumnos inscritos.

La entidad Student

Student entity diagram

  • Cree una carpeta Models en la carpeta del proyecto.

  • Cree el archivo Models/Student.cs con el siguiente código:

    using System;
    using System.Collections.Generic;
    
    namespace ContosoUniversity.Models
    {
        public class Student
        {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
    
            public ICollection<Enrollment> Enrollments { get; set; }
        }
    }
    

La propiedad ID se convierte en la columna de clave principal de la tabla de base de datos que se corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine ID o classnameID. Por tanto, el nombre que se reconoce de forma automática para la clave principal de la clase Student es StudentID. Para obtener más información, consulte EF Core: claves.

La propiedad Enrollments es una propiedad de navegación. Las propiedades de navegación contienen otras entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments de una entidad Student contiene todas las entidades Enrollment que están relacionadas con esa instancia de Student. Por ejemplo, si una fila Student de la base de datos tiene dos filas Enrollment relacionadas, la propiedad de navegación Enrollments contiene esas dos entidades Enrollment.

En la base de datos, una fila Enrollment se relaciona con una fila Student si su columna StudentID contiene el valor de identificador del alumno. Por ejemplo, imagine que una fila Student tiene el identificador 1. Las filas Enrollment relacionadas tendrán StudentID = 1. StudentID es una clave externa de la tabla Enrollment.

La propiedad Enrollments se define como ICollection<Enrollment> porque puede haber varias entidades Enrollment relacionadas. Puede usar otros tipos de colección, como List<Enrollment> o HashSet<Enrollment>. Cuando se usa ICollection<Enrollment>, EF Core crea una colección HashSet<Enrollment> de forma predeterminada.

La entidad Enrollment

Enrollment entity diagram

Cree el archivo Models/Enrollment.cs con el siguiente código:

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

La propiedad EnrollmentID es la clave principal; esta entidad usa el patrón classnameID en lugar de ID por sí solo. Para un modelo de datos de producción, elija un patrón y úselo de forma coherente. En este tutorial se usan los dos simplemente para ilustrar el trabajo. El uso de ID sin classname facilita la implementación de algunos tipos de cambios del modelo de datos.

La propiedad Grade es una enum. El signo de interrogación después de la declaración de tipo Grade indica que la propiedad Gradeacepta valores NULL. Una calificación que sea NULL es diferente de una calificación que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.

La propiedad StudentID es una clave externa y la propiedad de navegación correspondiente es Student. Una entidad Enrollment está asociada con una entidad Student, por lo que la propiedad contiene una única entidad Student.

La propiedad CourseID es una clave externa y la propiedad de navegación correspondiente es Course. Una entidad Enrollment está asociada con una entidad Course.

EF Core interpreta una propiedad como una clave externa si se denomina <navigation property name><primary key property name>. Por ejemplo, StudentID es la clave externa para la propiedad de navegación Student, ya que la clave principal de la entidad Student es ID. Las propiedades de clave externa también se pueden denominar <primary key property name>. Por ejemplo CourseID, dado que la clave principal de la entidad Course es CourseID.

La entidad Course

Course entity diagram

Cree el archivo Models/Course.cs con el siguiente código:

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

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

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

La propiedad Enrollments es una propiedad de navegación. Una entidad Course puede estar relacionada con cualquier número de entidades Enrollment.

El atributo DatabaseGenerated permite que la aplicación especifique la clave principal en lugar de hacer que la base de datos la genere.

Compile el proyecto para comprobar que no hay errores de compilación.

Scaffolding de las páginas Student

En esta sección, se usa la herramienta de scaffolding de ASP.NET Core para generar lo siguiente:

  • Una clase context de EF Core. El contexto es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos determinado. Se deriva de la clase Microsoft.EntityFrameworkCore.DbContext.
  • Razor Pages que controlan las operaciones de creación, lectura, actualización y eliminación (CRUD) de la entidad Student.
  • Cree una carpeta Students en la carpeta Pages.
  • En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Pages/Students y seleccione Agregar>Nuevo elemento con scaffold.
  • En el cuadro de diálogo Agregar scaffold, seleccione Páginas de Razor que usan Entity Framework (CRUD)>AGREGAR.
  • En el cuadro de diálogo para agregar instancias de Razor Pages que usan Entity Framework (CRUD):
    • En la lista desplegable Clase de modelo, seleccione Student (ContosoUniversity.Models) .
    • En la fila Clase de contexto de datos, seleccione el signo + (más).
    • Cambie el nombre del contexto de datos ContosoUniversity.Models.ContosoUniversityContext por ContosoUniversity.Data.SchoolContext.
    • Seleccione Agregar.

Los paquetes siguientes se instalan de forma automática:

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.EntityFrameworkCore.Tools

Si tiene un problema con el paso anterior, compile el proyecto y vuelva a intentar el paso de scaffolding.

El proceso de scaffolding:

  • Crea Razor Pages en la carpeta Pages/Students:
    • Create.cshtml y Create.cshtml.cs
    • Delete.cshtml y Delete.cshtml.cs
    • Details.cshtml y Details.cshtml.cs
    • Edit.cshtml y Edit.cshtml.cs
    • Index.cshtml y Index.cshtml.cs
  • Crea Data/SchoolContext.cs.
  • Agrega el contexto a la inserción de dependencias en Startup.cs.
  • Agrega una cadena de conexión de la base de datos a appsettings.json .

Cadena de conexión de base de datos

El archivo appsettings.json especifica la cadena de conexión SQL Server LocalDB.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext6;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB es una versión ligera del motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en producción. De forma predeterminada, LocalDB crea archivos .mdf en el directorio C:/Users/<user>.

Actualización de la clase de contexto de base de datos

La clase principal que coordina la funcionalidad de EF Core para un modelo de datos determinado es la clase context de la base de datos. El contexto se deriva de Microsoft.EntityFrameworkCore.DbContext. En el contexto se especifica qué entidades se incluyen en el modelo de datos. En este proyecto, la clase se denomina SchoolContext.

Actualice Data/SchoolContext.cs con el siguiente código:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext (DbContextOptions<SchoolContext> options)
            : base(options)
        {
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Course> Courses { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

El código resaltado crea una propiedad DbSet<TEntity> para cada conjunto de entidades. En terminología de EF Core:

  • Un conjunto de entidades normalmente se corresponde a una tabla de base de datos.
  • Una entidad se corresponde con una fila de la tabla.

Puesto que un conjunto de entidades contiene varias entidades, las propiedades DBSet deben ser nombres en plural. Como la herramienta de scaffolding ha creado una instancia Student de DBSet, en este paso se cambia a Students en plural.

Para que el código de Razor Pages coincida con el nuevo nombre de DBSet, realice un cambio global de _context.Student a _context.Students en todo el proyecto. Hay ocho repeticiones.

Compile el proyecto para comprobar que no haya errores del compilador.

Startup.cs

ASP.NET Core integra la inserción de dependencias. Los servicios, como el contexto de base de datos de EF Core, se registran con inserción de dependencias durante el inicio de la aplicación. Estos servicios se proporcionan a los componentes que los necesitan (como Razor Pages) a través de parámetros de constructor. El código de constructor que obtiene una instancia de contexto de base de datos se muestra más adelante en el tutorial.

La herramienta de scaffolding ha registrado de forma automática la clase de contexto con el contenedor de inserción de dependencias.

  • En ConfigureServices, el proveedor de scaffolding ha agregado la línea resaltada:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    
        services.AddDbContext<SchoolContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
    }
    

El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto DbContextOptions. Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena de conexión desde el archivo appsettings.json.

Creación de la base de datos

Actualice Program.cs para crear la base de datos si no existe:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                    // DbInitializer.Initialize(context);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

El método EnsureCreated no realiza ninguna acción si existe una base de datos para el contexto. Si no existe ninguna base de datos, se crean la base de datos y el esquema. EnsureCreated habilita el flujo de trabajo siguiente para controlar los cambios del modelo de datos:

  • Se elimina la base de datos. Se pierden los datos existentes.
  • Se cambia el modelo de datos. Por ejemplo, se agrega un campo EmailAddress.
  • Ejecutar la aplicación.
  • EnsureCreated crea una base de datos con el esquema nuevo.

Este flujo de trabajo funciona bien al principio de la fase de desarrollo cuando el esquema evoluciona rápidamente, siempre y cuando no sea necesario conservar los datos. La situación es distinta cuando es necesario conservar los datos introducidos en la base de datos. En ese caso, use las migraciones.

Más adelante en la serie de tutoriales, eliminará la base de datos creada por EnsureCreated y, en su lugar, usará las migraciones. Una base de datos creada por EnsureCreated no se puede actualizar mediante migraciones.

Prueba de la aplicación

  • Ejecutar la aplicación.
  • Haga clic en el vínculo Students y, después, en Crear nuevo.
  • Pruebe los vínculos Edit, Details y Delete.

Inicializar la base de datos

El método EnsureCreated crea una base de datos vacía. En esta sección se agrega código que rellena la base de datos con datos de prueba.

Cree el archivo Data/DbInitializer.cs con el siguiente código:

using ContosoUniversity.Data;
using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
                new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            context.SaveChanges();

            var courses = new Course[]
            {
                new Course{CourseID=1050,Title="Chemistry",Credits=3},
                new Course{CourseID=4022,Title="Microeconomics",Credits=3},
                new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
                new Course{CourseID=1045,Title="Calculus",Credits=4},
                new Course{CourseID=3141,Title="Trigonometry",Credits=4},
                new Course{CourseID=2021,Title="Composition",Credits=3},
                new Course{CourseID=2042,Title="Literature",Credits=4}
            };

            context.Courses.AddRange(courses);
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
                new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
                new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
                new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
                new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
                new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
                new Enrollment{StudentID=3,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=1050},
                new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
                new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
                new Enrollment{StudentID=6,CourseID=1045},
                new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

El código comprueba si hay alumnos en la base de datos. Si no hay ningún alumno, agrega datos de prueba a la base de datos. Crea los datos de prueba en matrices en lugar de colecciones List<T> para optimizar el rendimiento.

  • En Program.cs, reemplace la llamada a EnsureCreated con una llamada a DbInitializer.Initialize:

    // context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
    

Detenga la aplciación si se está ejecutando y ejecute el comando siguiente en la Consola del Administrador de paquetes (PMC):

Drop-Database
  • Reinicie la aplicación.

  • Seleccione la página Students para ver los datos inicializados.

Consulta la base de datos

  • Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio.
  • En SSOX, seleccione (localdb)\MSSQLLocalDB > Bases de datos > SchoolContext-{GUID}. El nombre de la base de datos se genera a partir del nombre de contexto proporcionado anteriormente, más un guión y un GUID.
  • Expanda el nodo Tablas.
  • Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se crearon y las filas que se insertaron en la tabla.
  • Haga clic con el botón derecho en la tabla Student y haga clic en Ver código para ver cómo el modelo Student se asigna al esquema de tabla Student.

Código asincrónico

La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.

Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código asincrónico permite que los recursos de servidor se usen de forma más eficaz y el servidor pueda administrar más tráfico sin retrasos.

El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones de poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico elevado, la posible mejora del rendimiento es importante.

En el código siguiente, la palabra clave async, el valor devuelto Task<T>, la palabra clave await y el método ToListAsync hacen que el código se ejecute de forma asincrónica.

public async Task OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • La palabra clave async indica al compilador que:
    • Genere devoluciones de llamada para partes del cuerpo del método.
    • Cree el objeto Task que se devuelve.
  • El tipo devuelto Task<T> representa el trabajo en curso.
  • La palabra clave await hace que el compilador divida el método en dos partes. La primera parte termina con la operación que se inició de forma asincrónica. La segunda parte se coloca en un método de devolución de llamada que se llama cuando finaliza la operación.
  • ToListAsync es la versión asincrónica del método de extensión ToList.

Algunos aspectos que tener en cuenta al escribir código asincrónico en el que se usa EF Core son los siguientes:

  • Solo se ejecutan de forma asincrónica las instrucciones que hacen que las consultas o los comandos se envíen a la base de datos. Esto incluye ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync y SaveChangesAsync. No incluye las instrucciones que solo cambian una IQueryable, como var students = context.Students.Where(s => s.LastName == "Davolio").
  • Un contexto de EF Core no es seguro para subprocesos: no intente realizar varias operaciones en paralelo.
  • Para aprovechar las ventajas de rendimiento del código asincrónico, compruebe que en los paquetes de biblioteca (por ejemplo para la paginación) se usa async si llaman a métodos de EF Core que envían consultas a la base de datos.

Para obtener más información sobre la programación asincrónica en .NET, vea Programación asincrónica y Programación asincrónica con async y await.

Pasos siguientes