Compartir a través de


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

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión de .NET 9 de este artículo.

Por Tom Dykstra, Jeremy Likness y Jon P Smith

Este es el primero de una serie de tutoriales que muestran cómo usar Entity Framework (EF) Core en una aplicación ASP.NET Core Razor Pages . 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 primer enfoque de la base de datos, consulte este problema de Github.

Descargue o vea la aplicación completada.Descargue las instrucciones.

Requisitos previos

  • Si no está familiarizado con Pages, consulte la serie de tutoriales RazorRazor Pages antes de comenzar esta.

Motores de bases de datos

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

Solución de problemas

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

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 Índice de estudiantes

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.

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 tiene un problema que no puede resolver, compare el código con el proyecto completado. Descargue las instrucciones.

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 nuevo proyecto.

    Creación de un nuevo proyecto desde la ventana de inicio

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

    Creación de una aplicación web de ASP.NET Core

  3. En el cuadro de diálogo Configurar el nuevo proyecto , escriba ContosoUniversity en Nombre del proyecto. Es importante asignar un nombre al proyecto ContosoUniversity, incluida la coincidencia de mayúsculas, por lo que los espacios de nombres coincidirán al copiar y pegar código de ejemplo.

  4. Seleccione Siguiente.

  5. En el cuadro de diálogo Información adicional , seleccione .NET 6.0 (soporte técnico a largo plazo) y, a continuación, seleccione Crear.

    Información adicional

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 ocurrencias.
  • Se eliminan las entradas del menú Home y Privacy.
  • Se agregan entradas para About, Students, Courses, Instructors y Departments.

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 deEnrollment-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 entidades estudiantiles

  • 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 en la tabla de Inscripción.

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 entidades de inscripción

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 Grade puede ser nula. 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 entidades del curso

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 este problema de GitHub, tipos de referencia que aceptan valores NULL y Tutorial: Expresar la intención de diseño con más claridad con tipos de referencia que aceptan valores NULL 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 pueden.

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 EF Core de DbContext. 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 scaffolding.
  • En el cuadro de diálogo Agregar nuevo elemento de andamiaje:
    • En la pestaña izquierda, seleccione Páginas comunes >> instaladasRazor.
    • Seleccione Razor Páginas con Entity Framework (CRUD)>ADD.
  • En el cuadro de diálogo Agregar Razor páginas mediante Entity Framework (CRUD):
    • En la lista desplegable Clase de modelo , seleccione Student (ContosoUniversity.Models) .
    • En la fila de la 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 páginas .

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 páginas 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 .mdf archivos en el C:/Users/<user> directorio .

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 asegurado 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 se basa en la inyecció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 del appsettings.json archivo 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 en las migraciones 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.
  • Seleccione el enlace Students y luego Create New.
  • 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 aplicación si se está ejecutando y ejecute el siguiente comando 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) en el menú Ver de Visual Studio.
  • En SSOX, seleccione (localdb)\MSSQLLocalDB > Databases > 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 creadas y las filas insertadas 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 se asigna el Student modelo al esquema de la 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 , Task el valor devuelto, await la palabra clave y ToListAsync el método 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, consulte Async Overview 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, consulte Consideraciones de rendimiento (EF).

Pasos siguientes

Uso de SQLite para el desarrollo, SQL Server para producción

Este es el primero de una serie de tutoriales que muestran cómo usar Entity Framework (EF) Core en una aplicación ASP.NET Core Razor Pages . 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 primer enfoque de la base de datos, consulte este problema de Github.

Descargue o vea la aplicación completada.Descargue las instrucciones.

Requisitos previos

  • Si no está familiarizado con Pages, consulte la serie de tutoriales RazorRazor Pages antes de comenzar esta.

Motores de bases de datos

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

Solución de problemas

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

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 Índice de estudiantes

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.

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 tiene un problema que no puede resolver, compare el código con el proyecto completado. Descargue las instrucciones.

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 nuevo proyecto.
  2. En el cuadro de diálogo Crear un nuevo proyecto, seleccione ASP.NET Core Web Application>Siguiente.
  3. En el cuadro de diálogo Configurar el 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 nueva aplicación web ASP.NET Core , seleccione:
    1. .NET Core y ASP.NET Core 5.0 en las listas desplegables.
    2. ASP.NET Core Web App.
    3. Crearnuevo cuadro de diálogo de proyecto ASP.NET Core

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 ocurrencias.
  • Se eliminan las entradas del menú Home y Privacy.
  • Se agregan entradas para About, Students, Courses, Instructors y Departments.

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 deEnrollment-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 entidades estudiantiles

  • 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 en la tabla de Inscripción.

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 entidades de inscripción

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 Grade puede ser nula. 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 entidades del curso

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 EF Core de DbContext. 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 scaffolding.
  • En el cuadro de diálogo Agregar nuevo elemento de andamiaje:
    • En la pestaña izquierda, seleccione Páginas comunes >> instaladasRazor.
    • Seleccione Razor Páginas con Entity Framework (CRUD)>ADD.
  • En el cuadro de diálogo Agregar Razor páginas mediante Entity Framework (CRUD):
    • En la lista desplegable Clase de modelo , seleccione Student (ContosoUniversity.Models) .
    • En la fila de la 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 páginas .

Si se produce un error en el andamiaje 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.', vuelva a ejecutar la herramienta de andamiaje o consulte este problema 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 páginas 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 .mdf archivos en el C:/Users/<user> directorio .

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 asegurado 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 se basa en la inyecció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 del appsettings.json archivo.

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 en las migraciones 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.
  • Seleccione el enlace Students y luego Create New.
  • 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 aplicación si se está ejecutando y ejecute el siguiente comando 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) en el menú Ver de Visual Studio.
  • En SSOX, seleccione (localdb)\MSSQLLocalDB > Databases > 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 creadas y las filas insertadas 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 se asigna el Student modelo al esquema de la 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 , Task el valor devuelto, await la palabra clave y ToListAsync el método 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, consulte Async Overview 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 las opciones de configuración como MyMaxModelBindingCollectionSize.

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

Para obtener más información, consulte Consideraciones de 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 obtener más información, consulte Registro en .NET Core y ASP.NET Core y este problema de GitHub.

Pasos siguientes

Uso de SQLite para el desarrollo, SQL Server para producción

Este es el primero de una serie de tutoriales que muestran cómo usar Entity Framework (EF) Core en una aplicación ASP.NET Core Razor Pages . 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 primer enfoque de la base de datos, consulte este problema de Github.

Descargue o vea la aplicación completada.Descargue las instrucciones.

Requisitos previos

  • Si no está familiarizado con Pages, consulte la serie de tutoriales RazorRazor Pages antes de comenzar esta.

Motores de bases de datos

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

Las instrucciones de Visual Studio Code usan 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 tiene un problema que no puede resolver, compare el código con el proyecto completado. Una buena manera de obtener ayuda es publicar una pregunta en StackOverflow.com, usando la etiqueta ASP.NET Core o la EF Core etiqueta .

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 Índice de estudiantes

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 tiene el código de la versión ASP.NET Core 3.0 del tutorial. Los archivos que reflejan el estado del código de 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 ASP.NET Aplicación web principal.
  • Asigne al proyecto el nombre ContosoUniversity. 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, a continuación, seleccione 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 ocurrencias.

  • Elimine las Home entradas de menú 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 deEnrollment-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 entidades estudiantiles

  • 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 en la tabla Inscripción.

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 entidades de inscripción

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 Grade puede ser nula. 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 entidades del curso

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 EF Core de . 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 scaffolding.
  • En el cuadro de diálogo Agregar estructura, seleccione Razor Páginas con Entity Framework (CRUD)>AGREGAR.
  • En el cuadro de diálogo Agregar Razor páginas mediante Entity Framework (CRUD):
    • En la lista desplegable Clase de modelo , seleccione Student (ContosoUniversity.Models) .
    • En la fila de la Clase de contexto de datos, seleccione el signo + (más).
    • Cambie el nombre del contexto de datos de ContosoUniversity.Models.ContosoUniversityContext a 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 páginas 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 appsettings.json archivo 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 .mdf archivos en el C:/Users/<user> directorio .

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 se basa en la inyecció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 del appsettings.json archivo.

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.
  • Seleccione el enlace Students y luego Create New.
  • 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 aplicación si se está ejecutando y ejecute el siguiente comando 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) en el menú Ver de Visual Studio.
  • En SSOX, seleccione (localdb)\MSSQLLocalDB > Databases > 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 creadas y las filas insertadas 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 se asigna el Student modelo al esquema de la 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 , Task<T> el valor devuelto, await la palabra clave y ToListAsync el método 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, consulte Async Overview y Programación asincrónica con async y await.

Pasos siguientes