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

Por Tom Dykstra y Rick Anderson

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.

Página de índice de Students

Página de edición de estudiantes

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.

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. Crear Cuadro de diálogo Nuevo proyecto de ASP.NET Core

Configurar el estilo del sitio

Copie el código siguiente y péguelo 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ú de Inicio y Privacidad.
  • 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:

Diagrama del modelo de datos Course-Enrollment-Student

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

Diagrama de la entidad Student

  • Cree una carpeta Models en la carpeta del proyecto.

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

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

Diagrama de la entidad Enrollment

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

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

Diagrama de la entidad Course

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

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 de 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 > Páginas Razor .
    • 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 e 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:

{
  "PageSize": 3,
  "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 de contexto de 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 código siguiente:

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 la 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")));
}

Tener Visual Studio Code

Compruebe que el código que agrega el proveedor de scaffolding llama a UseSqlite.

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

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

Para obtener información sobre el uso de una base de datos de producción, vea Uso de SQLite para desarrollo, SQL Server para producción.

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 a ConfigureServices, 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();
}

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

En la PMC, escriba lo siguiente para agregar el paquete NuGet:

Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore

Tener Visual Studio Code

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

    services.AddDbContext<SchoolContext>(options =>
       options.UseSqlite(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();
    });
}

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.

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 Data/DbInitializer.cs con el código siguiente:

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 -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 class IndexModel : PageModel
{
    private readonly SchoolContext _context;
    private readonly MvcOptions _mvcOptions;

    public IndexModel(SchoolContext context, IOptions<MvcOptions> mvcOptions)
    {
        _context = context;
        _mvcOptions = mvcOptions.Value;
    }

    public IList<Student> Student { get;set; }

    public async Task OnGetAsync()
    {
        Student = await _context.Students.Take(
            _mvcOptions.MaxModelBindingCollectionSize).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();
}

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

Pasos siguientes

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.

Página de índice de Students

Página de edición de estudiantes

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:

Diagrama del modelo de datos Course-Enrollment-Student

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

Diagrama de la entidad Student

  • Cree una carpeta Models en la carpeta del proyecto.

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

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

Diagrama de la entidad Enrollment

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

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

Diagrama de la entidad Course

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

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 de contexto 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 e 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 de contexto de 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 código siguiente:

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 la 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 la 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 Data/DbInitializer.cs con el código siguiente:

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

En la aplicación web de ejemplo Contoso University se muestra cómo crear una aplicación web de Razor Pages de ASP.NET Core con Entity Framework (EF) Core.

La aplicación de ejemplo es un sitio web de una universidad ficticia, Contoso University. Incluye funciones como la admisión de estudiantes, la creación de cursos y asignaciones de instructores. Esta página es la primera de una serie de tutoriales en los que se explica cómo crear la aplicación de ejemplo Contoso University.

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

Requisitos previos

Visual Studio 2019 con las cargas de trabajo siguientes:

  • ASP.NET y desarrollo web
  • Desarrollo multiplataforma de .NET Core

Familiaridad con Razor Pages. Los programadores nuevos deben completar Introducción a Razor Pages en ASP.NET Core antes de empezar esta serie.

Solución de problemas

Si experimenta un problema que no puede resolver, por lo general podrá encontrar la solución si compara el código con el proyecto completado. Una buena forma de obtener ayuda consiste en publicar una pregunta en StackOverflow.com para ASP.NET Core o EF Core.

La aplicación web Contoso University

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.

Página de índice de Students

Página de edición de estudiantes

El estilo de la interfaz de usuario de este sitio se mantiene fiel a lo que generan las plantillas integradas. El tutorial se centra en EF Core con Razor Pages, no en la interfaz de usuario.

Creación de la aplicación web de Razor Pages ContosoUniversity

  • En el menú Archivo de Visual Studio, seleccione Nuevo > Proyecto.
  • Cree una aplicación web de ASP.NET Core. Asigne el nombre ContosoUniversity al proyecto. Es importante que el nombre del proyecto sea ContosoUniversity para que coincidan los espacios de nombres al copiar y pegar el código.
  • Seleccione ASP.NET Core 2.1 en la lista desplegable y, luego, Aplicación web.

Para ver las imágenes de los pasos anteriores, consulte Creación de una aplicación web de Razor. Ejecutar la aplicación.

Configurar el estilo del sitio

Con algunos cambios se configura el menú del sitio, el diseño y la página principal. Actualice Pages/Shared/_Layout.cshtml con los cambios siguientes:

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

  • Agregue entradas de menú para Students, Courses, Instructors y Departments, y elimine la entrada de menú Contact.

Los cambios aparecen resaltados. (No se muestra todo el marcado).

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] : Contoso University</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">Contoso University</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Students/Index">Students</a></li>
                    <li><a asp-page="/Courses/Index">Courses</a></li>
                    <li><a asp-page="/Instructors/Index">Instructors</a></li>
                    <li><a asp-page="/Departments/Index">Departments</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 : Contoso University</p>
        </footer>
    </div>

    @*Remaining markup not shown for brevity.*@

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

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

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>
            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 class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in a series of tutorials.</p>
        <p>
            <a class="btn btn-default"
               href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro">
                See the tutorial &raquo;
            </a>
        </p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p>
            <a class="btn btn-default"
               href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples/">
                See project source code &raquo;
            </a>
        </p>
    </div>
</div>

Crear el modelo de datos

Cree las clases de entidad para la aplicación Contoso University. Comience con las tres entidades siguientes:

Diagrama del modelo de datos Course-Enrollment-Student

Hay una relación uno a varios entre las entidades Student y Enrollment. Hay una relación uno a varios entre las entidades Course y Enrollment. Un estudiante se puede inscribir en cualquier número de cursos. Un curso puede tener cualquier número de alumnos inscritos.

En las secciones siguientes, se crea una clase para cada una de estas entidades.

La entidad Student

Diagrama de la entidad Student

Cree una carpeta Models. En la carpeta Models, cree un archivo de clase denominado Student.cs con el código siguiente:

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 (DB) que corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine ID o classnameID. En classnameID, classname es el nombre de la clase. En el ejemplo anterior, la clave principal alternativa que se reconoce de forma automática es StudentID.

La propiedad Enrollments es una propiedad de navegación. Las propiedades de navegación se vinculan a otras entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments de una Student entity contiene todas las entidades Enrollment que están relacionadas con esa entidad 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. Una fila Enrollment relacionada es la que contiene el valor de clave principal de ese estudiante en la columna StudentID. Por ejemplo, suponga que el estudiante con ID=1 tiene dos filas en la tabla Enrollment. La tabla Enrollment tiene dos filas con StudentID = 1. StudentID es una clave externa en la tabla Enrollment que especifica el estudiante en la tabla Student.

Si una propiedad de navegación puede contener varias entidades, la propiedad de navegación debe ser un tipo de lista, como ICollection<T>. Se puede especificar ICollection<T>, o bien un tipo como List<T> o HashSet<T>. Cuando se usa ICollection<T>, EF Core crea una colección HashSet<T> de forma predeterminada. Las propiedades de navegación que contienen varias entidades proceden de relaciones de varios a varios y uno a varios.

La entidad Enrollment

Diagrama de la entidad Enrollment

En la carpeta Models, cree Enrollment.cs con el código siguiente:

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. En esta entidad se usa el patrón classnameID en lugar de ID como en la entidad Student. Normalmente, los desarrolladores eligen un patrón y lo usan en todo el modelo de datos. En un tutorial posterior, se muestra el uso de ID sin un nombre de clase para facilitar la implementación de la herencia en el 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 Grade acepta 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 entidad Student difiere de la propiedad de navegación Student.Enrollments, que contiene varias entidades Enrollment.

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 para la propiedad de navegación Student, puesto 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

Diagrama de la entidad Course

En la carpeta Models, cree Course.cs con el código siguiente:

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.

Aplicación de scaffolding al modelo de alumnos

En esta sección, se aplica scaffolding al modelo de alumnos. Es decir, la herramienta de scaffolding genera páginas para las operaciones de creación, lectura, actualización y eliminación (CRUD) del modelo de alumnos.

  • Compile el proyecto.
  • Cree la carpeta Pages/Students.
  • En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Pages/Students > Agregar > Nuevo elemento con scaffold.
  • En el cuadro de diálogo Agregar scaffold, seleccione Páginas de Razor que usan Entity Framework (CRUD) > AGREGAR.

Complete 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, haga clic en el signo + (más) y cambie el nombre generado por ContosoUniversity.Models.SchoolContext.
  • En la lista desplegable Clase de contexto de datos, seleccione ContosoUniversity.Models.SchoolContext
  • Seleccione Agregar.

Cuadro de diálogo CRUD

Si tiene algún problema con el paso anterior, consulte Aplicar scaffolding al modelo de película.

El proceso de scaffolding ha creado y cambiado los archivos siguientes:

Archivos creados

  • Pages/Students Create, Delete, Details, Edit, Index.
  • Data/SchoolContext.cs

Actualizaciones de archivos

  • Startup.cs: en la sección siguiente se detallan los cambios realizados en este archivo.
  • appsettings.json : se agrega la cadena de conexión que se usa para conectarse a una base de datos local.

Examinar el contexto registrado con la inserción de dependencias

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 creó de forma automática un contexto de base de datos y lo registró con el contenedor de inserción de dependencias.

Examine el método ConfigureServices de Startup.cs. El proveedor de scaffolding ha agregado la línea resaltada:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for 
        //non -essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    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 .

Actualización de main

En Program.cs, modifique el método Main para que haga lo siguiente:

  • Obtener una instancia del contexto de base de datos desde el contenedor de inserción de dependencias.
  • Llame a EnsureCreated.
  • Elimine el contexto cuando finalice el método EnsureCreated.

En el código siguiente se muestra el archivo Program.cs actualizado.

using ContosoUniversity.Models;                   // SchoolContext
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;   // CreateScope
using Microsoft.Extensions.Logging;
using System;

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

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

                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }

            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

EnsureCreated garantiza la existencia de la base de datos para el contexto. Si existe, no se realiza ninguna acción. Si no existe, se crean la base de datos y todo su esquema. En EnsureCreated no se usan migraciones para crear la base de datos. Una base de datos que se cree con EnsureCreated no se podrá actualizar más adelante mediante las migraciones.

EnsureCreated se llama durante el inicio de la aplicación, lo que permite el flujo de trabajo siguiente:

  • Se elimina la base de datos.
  • Se cambia el esquema de base de datos (por ejemplo, se agrega un campo EmailAddress).
  • Ejecute la aplicación.
  • EnsureCreated crea una base de datos con la columna EmailAddress.

EnsureCreated es útil al principio del desarrollo, cuando el esquema evoluciona rápidamente. Más adelante, en el tutorial se elimina la base de datos y se usan las migraciones.

Prueba de la aplicación

Ejecute la aplicación y acepte la directiva de cookies. Esta aplicación no conserva información de carácter personal. Puede obtener más información sobre la directiva de cookies en Compatibilidad con el Reglamento general de protección de datos (RGPD) de la UE.

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

Examinar el contexto de base de datos SchoolContext

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

Actualice SchoolContext.cs con el código siguiente:

using Microsoft.EntityFrameworkCore;

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

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

El código resaltado crea una propiedad DbSet<TEntity> para cada conjunto de entidades. En la 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.

DbSet<Enrollment> y DbSet<Course> se pueden omitir. EF Core las incluye implícitamente porque la entidad Student hace referencia a la entidad Enrollment y la entidad Enrollment hace referencia a la entidad Course. Para este tutorial, conserve DbSet<Enrollment> y DbSet<Course> en el SchoolContext.

SQL Server Express LocalDB

La cadena de conexión especifica SQL Server LocalDB. 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. LocalDB se inicia a petición y se ejecuta en modo de usuario, sin necesidad de una configuración compleja. De forma predeterminada, LocalDB crea archivos de base de datos .mdf en el directorio C:/Users/<user>.

Agregar código para inicializar la base de datos con datos de prueba

EF Core crea una base de datos vacía. En esta sección, se escribe un método Initialize para rellenarlo con datos de prueba.

En la carpeta Data, cree un archivo de clase denominado DbInitializer.cs y agregue el código siguiente:

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

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

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

            var students = new Student[]
            {
            new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Student.Add(s);
            }
            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}
            };
            foreach (Course c in courses)
            {
                context.Course.Add(c);
            }
            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},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollment.Add(e);
            }
            context.SaveChanges();
        }
    }
}

Nota: El código anterior usa Models para el espacio de nombres (namespace ContosoUniversity.Models) en lugar de Data. Models es coherente con el código generado por el proveedor de scaffolding. Para obtener más información, consulte este problema de scaffolding de GitHub.

El código comprueba si hay estudiantes en la base de datos. Si no hay alumnos en la base de datos, se inicializa con datos de prueba. Carga los datos de prueba en matrices en lugar de colecciones List<T> para optimizar el rendimiento.

El método EnsureCreated crea automáticamente la base de datos para el contexto de base de datos. Si la base de datos existe, EnsureCreated vuelve sin modificarla.

En Program.cs, modifique el método Main para que llame a Initialize:

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

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

            try
            {
                var context = services.GetRequiredService<SchoolContext>();
                // using ContosoUniversity.Data; 
                DbInitializer.Initialize(context);
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred creating the DB.");
            }
        }

        host.Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

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

Drop-Database

Ver la base de datos

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. Por lo tanto, el nombre de la base de datos será "SchoolContext-{GUID}". El GUID será diferente para cada usuario. Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio. En SSOX, haga clic en (localdb)\MSSQLLocalDB > Databases > SchoolContext-{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.

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 está habilitado para 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()
{
    Student = await _context.Student.ToListAsync();
}
  • La palabra clave async indica al compilador que:

    • Genere devoluciones de llamada para partes del cuerpo del método.
    • Cree automáticamente el objeto Task que se devuelve. Para más información, vea Tipo de valor devuelto Task.
  • El tipo devuelto implícito 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 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.

En el siguiente tutorial, se examinan las operaciones CRUD (crear, leer, actualizar y eliminar) básicas.

Recursos adicionales