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.
De Tom Dykstra, Jeremy Likness y Jon P Smith
Este es el primero de una serie de tutoriales en los que se muestra cómo usar Entity Framework (EF) Core en una aplicación Razor Pages en ASP.NET Core. En el tutorial se crea un sitio web de una universidad ficticia, Contoso University. El sitio incluye funciones como la admisión de alumnos, la creación de cursos y las asignaciones de instructores. En el tutorial se usa el enfoque de Code First. Para obtener información sobre cómo seguir este tutorial mediante el enfoque de Database First, consulte este problema de GitHub.
Descargue o vea la aplicación completa. Instrucciones de descarga.
Requisitos previos
- Si no está familiarizado con Razor Pages, consulte la serie de tutoriales Introducción a Razor Pages antes de empezar este.
- Visual Studio 2022 con la carga de trabajo de ASP.NET y desarrollo web
- SDK de .NET 6.0
Motores de bases de datos
En las instrucciones de Visual Studio se usa SQL Server LocalDB, una versión de SQL Server Express que solo se ejecuta en Windows.
Solución de problemas
Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Una buena forma de obtener ayuda consiste en publicar una pregunta en StackOverflow.com con la etiqueta ASP.NET Core o la etiqueta EF Core.
La aplicación de ejemplo
La aplicación compilada en estos tutoriales es un sitio web básico de una universidad. Los usuarios pueden ver y actualizar la información de estudiantes, cursos e instructores. Estas son algunas de las pantallas que se crean en el tutorial.
El estilo de la interfaz de usuario de este sitio se basa en las plantillas de proyecto integradas. El enfoque del tutorial es cómo usar EF Core con ASP.NET Core, no cómo personalizar la interfaz de usuario.
Opcional: compilación de la descarga de ejemplo
Este paso es opcional. Se recomienda compilar la aplicación completa cuando tenga problemas que no pueda resolver. Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Instrucciones de descarga.
Seleccione ContosoUniversity.csproj
para abrir el proyecto.
Compile el proyecto.
En la Consola del administrador de paquetes (PMC), ejecute el comando siguiente:
Update-Database
Ejecute el proyecto para inicializar la base de datos.
Creación del proyecto de aplicación web
Inicie Visual Studio 2022 y seleccione Crear un proyecto.
En el cuadro de diálogo Crear un proyecto nuevo, seleccione Aplicación web ASP.NET Core y, a continuación, seleccione Siguiente.
En el cuadro de diálogo Configurar su nuevo proyecto, escriba
ContosoUniversity
en Nombre del proyecto. Es importante asignarle el nombre ContosoUniversity al proyecto (respetando mayúsculas y minúsculas) para que los espacios de nombres coincidan al copiar y pegar el código de ejemplo.Seleccione Next (Siguiente).
En el cuadro de diálogo Información adicional, seleccione .NET 6.0 (Compatibilidad a largo plazo) y, después, Crear.
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">
© 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 han agregado entradas para Acerca de, Estudiantes, Cursos, Instructores y Departamentos.
En Pages/Index.cshtml
, reemplace el contenido del archivo por el código siguiente:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="row mb-auto">
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 mb-4 ">
<p class="card-text">
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core Razor Pages web app.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column position-static">
<p class="card-text mb-auto">
You can build the application by following the steps in a series of tutorials.
</p>
<p>
@* <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
*@ </p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column">
<p class="card-text mb-auto">
You can download the completed project from GitHub.
</p>
<p>
@* <a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
*@ </p>
</div>
</div>
</div>
</div>
El código anterior reemplaza el texto sobre ASP.NET Core con texto sobre esta aplicación.
Ejecuta la aplicación para comprobar que aparece la página home.
El modelo de datos
En las secciones siguientes se crea un modelo de datos:
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
- Cree una carpeta Models en la carpeta del proyecto.
- Cree el archivo
Models/Student.cs
con el siguiente código:namespace ContosoUniversity.Models { public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public ICollection<Enrollment> Enrollments { get; set; } } }
La propiedad ID
se convierte en la columna de clave principal de la tabla de base de datos que se corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine ID
o classnameID
. Por tanto, el nombre que se reconoce de forma automática para la clave principal de la clase Student
es StudentID
. Para obtener más información, consulte EF Core: claves.
La propiedad Enrollments
es una propiedad de navegación. Las propiedades de navegación contienen otras entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments
de una entidad Student
contiene todas las entidades Enrollment
que están relacionadas con esa instancia de Student. Por ejemplo, si una fila Student de la base de datos tiene dos filas Enrollment relacionadas, la propiedad de navegación Enrollments
contiene esas dos entidades Enrollment.
En la base de datos, una fila Enrollment se relaciona con una fila Student si su columna StudentID
contiene el valor de identificador del estudiante. Por ejemplo, imagine que una fila Student tiene el identificador 1. Las filas Enrollment relacionadas tendrán StudentID
= 1. StudentID
es una clave externa de la tabla Enrollment.
La propiedad Enrollments
se define como ICollection<Enrollment>
porque puede haber varias entidades Enrollment relacionadas. Puede usar otros tipos de colección, como List<Enrollment>
o HashSet<Enrollment>
. Cuando se usa ICollection<Enrollment>
, EF Core crea una colección HashSet<Enrollment>
de forma predeterminada.
La entidad Enrollment
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
acepta valores NULL. Una calificación que sea NULL es diferente de una calificación que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.
La propiedad StudentID
es una clave externa y la propiedad de navegación correspondiente es Student
. Una entidad Enrollment
está asociada con una entidad Student
, por lo que la propiedad contiene una única entidad Student
.
La 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
Cree el archivo Models/Course.cs
con el siguiente código:
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
La propiedad Enrollments
es una propiedad de navegación. Una entidad Course
puede estar relacionada con cualquier número de entidades Enrollment
.
El atributo DatabaseGenerated
permite que la aplicación especifique la clave principal en lugar de hacer que la base de datos la genere.
Compile la aplicación. El compilador genera varias advertencias sobre cómo se controlan los valores null
. Consulte esta incidencia de GitHub, Tipos de referencia que aceptan valores NULL y Tutorial: expresar la intención de diseño más claramente con tipos de referencia que aceptan y que no aceptan valores NULL para obtener más información.
Para eliminar las advertencias de los tipos de referencia que aceptan valores NULL, quite la siguiente línea del archivo ContosoUniversity.csproj
:
<Nullable>enable</Nullable>
Actualmente, el motor de scaffolding no admite tipos de referencia que aceptan valores NULL, por lo que los modelos usados en scaffold tampoco.
Quite la anotación de tipo de referencia que acepta valores NULL ?
de public string? RequestId { get; set; }
en Pages/Error.cshtml.cs
para que el proyecto se compile sin advertencias del compilador.
Scaffolding de las páginas Student
En esta sección, se usa la herramienta de scaffolding de ASP.NET Core para generar lo siguiente:
- Una clase
DbContext
de EF Core. El contexto es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos determinado. Se deriva de la clase Microsoft.EntityFrameworkCore.DbContext. - Razor Pages que controlan las operaciones de creación, lectura, actualización y eliminación (CRUD) de la entidad
Student
.
- Cree una carpeta Pages/Students.
- En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Pages/Students y seleccione Agregar>Nuevo elemento con scaffold.
- En el cuadro de diálogo Agregar nuevo elemento scaffold:
- En la pestaña de la izquierda, seleccione Instalado > Común >Razor Páginas
- Seleccione Páginas de Razor que usan Entity Framework (CRUD)>AGREGAR.
- En el cuadro de diálogo para agregar instancias de Razor Pages que usan Entity Framework (CRUD):
- En la lista desplegable Clase de modelo, seleccione Student (ContosoUniversity.Models) .
- En la fila Clase de contexto de datos, seleccione el signo + (más).
- Cambie el nombre del contexto de datos para que acabe en
SchoolContext
en lugar deContosoUniversityContext
. Nombre del contexto actualizado:ContosoUniversity.Data.SchoolContext
. - Seleccione Agregar para terminar de agregar la clase de contexto de datos.
- Seleccione Agregar para finalizar el cuadro de diálogo Agregar Razor Pages.
- Cambie el nombre del contexto de datos para que acabe en
Los paquetes siguientes se instalan de forma automática:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design
Si el paso anterior falla, compile el proyecto y vuelva a intentar el paso de scaffolding.
El proceso de scaffolding:
- Crea Razor Pages en la carpeta Pages/Students:
Create.cshtml
yCreate.cshtml.cs
Delete.cshtml
yDelete.cshtml.cs
Details.cshtml
yDetails.cshtml.cs
Edit.cshtml
yEdit.cshtml.cs
Index.cshtml
yIndex.cshtml.cs
- Crea
Data/SchoolContext.cs
. - Agrega el contexto a la inserción de dependencias en
Program.cs
. - Agrega una cadena de conexión de la base de datos a
appsettings.json
.
Cadena de conexión de base de datos
La herramienta de scaffolding genera una cadena de conexión en el archivo appsettings.json
.
La cadena de conexión especifica SQL Server LocalDB:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
LocalDB es una versión ligera del motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en producción. De forma predeterminada, LocalDB crea archivos .mdf en el directorio C:/Users/<user>
.
Actualización de la clase de contexto de base de datos
La clase principal que coordina la funcionalidad de EF Core para un modelo de datos determinado es la clase context de la base de datos. El contexto se deriva de Microsoft.EntityFrameworkCore.DbContext. En el contexto se especifica qué entidades se incluyen en el modelo de datos. En este proyecto, la clase se denomina SchoolContext
.
Actualice Data/SchoolContext.cs
con el siguiente código:
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
El código anterior cambia del elemento DbSet<Student> Student
singular al DbSet<Student> Students
plural. Para que el código de las páginas Razor coincida con el nombre nuevo de DBSet
, realice un cambio global de: _context.Student.
a: _context.Students.
Hay ocho repeticiones.
Dado que un conjunto de entidades contiene varias entidades, muchos desarrolladores prefieren que los nombres de la propiedad DBSet
sean plurales.
El código resaltado:
- Crea una propiedad DbSet<TEntity> para cada conjunto de entidades. En terminología de EF Core:
- Un conjunto de entidades normalmente se corresponde a una tabla de base de datos.
- Una entidad se corresponde con una fila de la tabla.
- Llama a OnModelCreating.
OnModelCreating
:- se llama cuando
SchoolContext
se ha inicializado, pero antes de que el modelo se haya 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.
- se llama cuando
Esperamos corregir este problema en una versión futura.
Program.cs
ASP.NET Core integra la inserción de dependencias. Los servicios como SchoolContext
se registran con inserción de dependencias durante el inicio de la aplicación. Estos servicios se proporcionan a los componentes que los necesitan, como páginas Razor, a través de parámetros de constructor. El código de constructor que obtiene una instancia de contexto de base de datos se muestra más adelante en el tutorial.
La herramienta de scaffolding ha registrado de forma automática la clase de contexto con el contenedor de inserción de dependencias.
El proveedor de scaffolding ha agregado las líneas resaltadas siguientes:
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));
El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto DbContextOptions. Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena de conexión desde el archivo appsettings.json
o appsettings.Development.json
.
Incorporación del filtro de excepción de base de datos
Agregue AddDatabaseDeveloperPageExceptionFilter y UseMigrationsEndPoint, tal como se muestra en el código siguiente:
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
Agregue el paquete NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.
En la Consola del administrador de paquetes, escriba esto para agregar el paquete NuGet:
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
El paquete NuGet de Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
proporciona middleware de ASP.NET Core para páginas de error de Entity Framework Core. Este middleware ayuda a detectar y diagnosticar errores con migraciones de Entity Framework Core.
AddDatabaseDeveloperPageExceptionFilter
proporciona información de error útil en el entorno de desarrollo para errores de migración de EF.
Creación de la base de datos
Actualice Program.cs
para crear la base de datos si no existe:
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("SchoolContext")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
// DbInitializer.Initialize(context);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
El método EnsureCreated no realiza ninguna acción si existe una base de datos para el contexto. Si no existe ninguna base de datos, se crean la base de datos y el esquema. EnsureCreated
habilita el flujo de trabajo siguiente para controlar los cambios del modelo de datos:
- Se elimina la base de datos. Se pierden los datos existentes.
- Se cambia el modelo de datos. Por ejemplo, se agrega un campo
EmailAddress
. - Ejecutar la aplicación.
EnsureCreated
crea una base de datos con el esquema nuevo.
Este flujo de trabajo funciona bien al principio de la fase de desarrollo, cuando el esquema evoluciona rápidamente, siempre y cuando no sea necesario conservar los datos. La situación es distinta cuando es necesario conservar los datos introducidos en la base de datos. En ese caso, use las migraciones.
Más adelante en la serie de tutoriales, se eliminará la base de datos creada por EnsureCreated
y se usarán las migraciones. Una base de datos creada por EnsureCreated
no se puede actualizar mediante migraciones.
Prueba de la aplicación
- Ejecutar la aplicación.
- Haga clic en el vínculo Students y, después, en Crear nuevo.
- Pruebe los vínculos Edit, Details y Delete.
Inicializar la base de datos
El método EnsureCreated
crea una base de datos vacía. En esta sección se agrega código que rellena la base de datos con datos de prueba.
Cree el archivo Data/DbInitializer.cs
con el siguiente código:
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
};
context.Students.AddRange(students);
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
context.Courses.AddRange(courses);
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
context.Enrollments.AddRange(enrollments);
context.SaveChanges();
}
}
}
El código comprueba si hay alumnos en la base de datos. Si no hay ningún alumno, agrega datos de prueba a la base de datos. Crea los datos de prueba en matrices en lugar de colecciones List<T>
para optimizar el rendimiento.
- En
Program.cs
, quite//
en la líneaDbInitializer.Initialize
:
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
DbInitializer.Initialize(context);
}
Detenga la aplciación si se está ejecutando y ejecute el comando siguiente en la Consola del Administrador de paquetes (PMC):
Drop-Database -Confirm
Responda con
Y
para eliminar la base de datos.
- Reinicie la aplicación.
- Seleccione la página Students para ver los datos inicializados.
Consulta la base de datos
- Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio.
- En SSOX, seleccione (localdb)\MSSQLLocalDB > Bases de datos > SchoolContext-{GUID}. El nombre de la base de datos se genera a partir del nombre de contexto proporcionado anteriormente, más un guión y un GUID.
- Expanda el nodo Tablas.
- Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se crearon y las filas que se insertaron en la tabla.
- Haga clic con el botón derecho en la tabla Student y haga clic en Ver código para ver cómo el modelo
Student
se asigna al esquema de tablaStudent
.
Métodos asincrónicos de EF en aplicaciones web de ASP.NET Core
La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.
Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código asincrónico permite que los recursos de servidor se usen de forma más eficaz y el servidor pueda administrar más tráfico sin retrasos.
El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones de poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico elevado, la posible mejora del rendimiento es importante.
En el código siguiente, la palabra clave async, el valor devuelto Task
, la palabra clave await
y el método ToListAsync
hacen que el código se ejecute de forma asincrónica.
public async Task OnGetAsync()
{
Students = await _context.Students.ToListAsync();
}
- La palabra clave
async
indica al compilador que:- Genere devoluciones de llamada para partes del cuerpo del método.
- Cree el objeto Task que se devuelve.
- El tipo devuelto
Task
representa el trabajo en curso. - La palabra clave
await
hace que el compilador divida el método en dos partes. La primera parte termina con la operación que se inició de forma asincrónica. La segunda parte se coloca en un método de devolución de llamada que se llama cuando finaliza la operación. ToListAsync
es la versión asincrónica del método de extensiónToList
.
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
ySaveChangesAsync
. No incluye las instrucciones que solo cambian unaIQueryable
, comovar students = context.Students.Where(s => s.LastName == "Davolio")
. - Un contexto de EF Core no es seguro para subprocesos: no intente realizar varias operaciones en paralelo.
- Para aprovechar las ventajas de rendimiento del código asincrónico, compruebe que en los paquetes de biblioteca (por ejemplo para la paginación) se usa async si llaman a métodos de EF Core que envían consultas a la base de datos.
Para obtener más información sobre la programación asincrónica en .NET, vea Programación asincrónica y Programación asincrónica con async y await.
Advertencia
La implementación asincrónica de Microsoft. Data.SqlClient tiene algunos problemas conocidos (#593, #601 y otros). Si observa problemas inesperados de rendimiento, pruebe a usar la ejecución del comando de sincronización en su lugar, especialmente cuando trabaje con valores binarios o de texto grandes.
Consideraciones de rendimiento
En general, una página web no debe cargar un número arbitrario de filas. Una consulta debe utilizar la paginación o un enfoque de limitación. Por ejemplo, la consulta anterior podría usar Take
para limitar las filas devueltas:
public async Task OnGetAsync()
{
Student = await _context.Students.Take(10).ToListAsync();
}
Enumerar una tabla grande en una vista podría devolver una respuesta HTTP 200 parcialmente construida si se produce una excepción de base de datos en mitad de la enumeración.
La paginación se trata más adelante en el tutorial.
Para obtener más información, vea Consideraciones sobre el rendimiento (EF).
Pasos siguientes
Este es el primero de una serie de tutoriales en los que se muestra cómo usar Entity Framework (EF) Core en una aplicación Razor Pages en ASP.NET Core. En el tutorial se crea un sitio web de una universidad ficticia, Contoso University. El sitio incluye funciones como la admisión de alumnos, la creación de cursos y las asignaciones de instructores. En el tutorial se usa el enfoque de Code First. Para obtener información sobre cómo seguir este tutorial mediante el enfoque de Database First, consulte este problema de GitHub.
Descargue o vea la aplicación completa. Instrucciones de descarga.
Requisitos previos
- Si no está familiarizado con Razor Pages, consulte la serie de tutoriales Introducción a Razor Pages antes de empezar este.
- Versión 16.8 o posterior de Visual Studio 2019 con la carga de trabajo Desarrollo web y ASP.NET
- SDK de .NET 5.0
Motores de bases de datos
En las instrucciones de Visual Studio se usa SQL Server LocalDB, una versión de SQL Server Express que solo se ejecuta en Windows.
Solución de problemas
Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Una buena forma de obtener ayuda consiste en publicar una pregunta en StackOverflow.com con la etiqueta ASP.NET Core o la etiqueta EF Core.
La aplicación de ejemplo
La aplicación compilada en estos tutoriales es un sitio web básico de una universidad. Los usuarios pueden ver y actualizar la información de estudiantes, cursos e instructores. Estas son algunas de las pantallas que se crean en el tutorial.
El estilo de la interfaz de usuario de este sitio se basa en las plantillas de proyecto integradas. El enfoque del tutorial es cómo usar EF Core con ASP.NET Core, no cómo personalizar la interfaz de usuario.
Opcional: compilación de la descarga de ejemplo
Este paso es opcional. Se recomienda compilar la aplicación completa cuando tenga problemas que no pueda resolver. Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Instrucciones de descarga.
Seleccione ContosoUniversity.csproj
para abrir el proyecto.
- Compile el proyecto.
- En la Consola del administrador de paquetes (PMC), ejecute el comando siguiente:
Update-Database
Ejecute el proyecto para inicializar la base de datos.
Creación del proyecto de aplicación web
- Inicie Visual Studio y seleccione Crear un proyecto.
- En el cuadro de diálogo Crear un proyecto, selecciona Aplicación web ASP.NET Core>Siguiente.
- En el cuadro de diálogo Configurar su nuevo proyecto, escribe
ContosoUniversity
en Nombre del proyecto. Es importante usar este nombre exacto, incluido el uso de mayúsculas, para que cadanamespace
coincida cuando se copie el código. - Selecciona Crear.
- En el cuadro de diálogo Crear una aplicación web ASP.NET Core, selecciona:
- .NET Core y ASP.NET Core 5.0 en los menús desplegables.
- Aplicación web de ASP.NET Core.
- Crear
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">
© 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 han agregado entradas para Acerca de, Estudiantes, Cursos, Instructores y Departamentos.
En Pages/Index.cshtml
, reemplace el contenido del archivo por el código siguiente:
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="row mb-auto">
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 mb-4 ">
<p class="card-text">
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core Razor Pages web app.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column position-static">
<p class="card-text mb-auto">
You can build the application by following the steps in a series of tutorials.
</p>
<p>
<a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="row no-gutters border mb-4">
<div class="col p-4 d-flex flex-column">
<p class="card-text mb-auto">
You can download the completed project from GitHub.
</p>
<p>
<a href="https://github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
</p>
</div>
</div>
</div>
</div>
El código anterior reemplaza el texto sobre ASP.NET Core con texto sobre esta aplicación.
Ejecuta la aplicación para comprobar que aparece la página home.
El modelo de datos
En las secciones siguientes se crea un modelo de datos:
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
Cree una carpeta Models en la carpeta del proyecto.
Cree el archivo
Models/Student.cs
con el siguiente código:using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public ICollection<Enrollment> Enrollments { get; set; } } }
La propiedad ID
se convierte en la columna de clave principal de la tabla de base de datos que se corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine ID
o classnameID
. Por tanto, el nombre que se reconoce de forma automática para la clave principal de la clase Student
es StudentID
. Para obtener más información, consulte EF Core: claves.
La propiedad Enrollments
es una propiedad de navegación. Las propiedades de navegación contienen otras entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments
de una entidad Student
contiene todas las entidades Enrollment
que están relacionadas con esa instancia de Student. Por ejemplo, si una fila Student de la base de datos tiene dos filas Enrollment relacionadas, la propiedad de navegación Enrollments
contiene esas dos entidades Enrollment.
En la base de datos, una fila Enrollment se relaciona con una fila Student si su columna StudentID
contiene el valor de identificador del estudiante. Por ejemplo, imagine que una fila Student tiene el identificador 1. Las filas Enrollment relacionadas tendrán StudentID
= 1. StudentID
es una clave externa de la tabla Enrollment.
La propiedad Enrollments
se define como ICollection<Enrollment>
porque puede haber varias entidades Enrollment relacionadas. Puede usar otros tipos de colección, como List<Enrollment>
o HashSet<Enrollment>
. Cuando se usa ICollection<Enrollment>
, EF Core crea una colección HashSet<Enrollment>
de forma predeterminada.
La entidad Enrollment
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
acepta valores NULL. Una calificación que sea NULL es diferente de una calificación que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.
La propiedad StudentID
es una clave externa y la propiedad de navegación correspondiente es Student
. Una entidad Enrollment
está asociada con una entidad Student
, por lo que la propiedad contiene una única entidad Student
.
La 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
Cree el archivo Models/Course.cs
con el siguiente código:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
La propiedad Enrollments
es una propiedad de navegación. Una entidad Course
puede estar relacionada con cualquier número de entidades Enrollment
.
El atributo DatabaseGenerated
permite que la aplicación especifique la clave principal en lugar de hacer que la base de datos la genere.
Compile el proyecto para comprobar que no hay errores de compilación.
Scaffolding de las páginas Student
En esta sección, se usa la herramienta de scaffolding de ASP.NET Core para generar lo siguiente:
- Una clase
DbContext
de EF Core. El contexto es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos determinado. Se deriva de la clase Microsoft.EntityFrameworkCore.DbContext. - Razor Pages que controlan las operaciones de creación, lectura, actualización y eliminación (CRUD) de la entidad
Student
.
- Cree una carpeta Pages/Students.
- En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Pages/Students y seleccione Agregar>Nuevo elemento con scaffold.
- En el cuadro de diálogo Agregar nuevo elemento scaffold:
- En la pestaña de la izquierda, seleccione Instalado > Común >Razor Páginas
- Seleccione Páginas de Razor que usan Entity Framework (CRUD)>AGREGAR.
- En el cuadro de diálogo para agregar instancias de Razor Pages que usan Entity Framework (CRUD):
- En la lista desplegable Clase de modelo, seleccione Student (ContosoUniversity.Models) .
- En la fila Clase de contexto de datos, seleccione el signo + (más).
- Cambie el nombre del contexto de datos para que acabe en
SchoolContext
en lugar deContosoUniversityContext
. Nombre del contexto actualizado:ContosoUniversity.Data.SchoolContext
. - Seleccione Agregar para terminar de agregar la clase de contexto de datos.
- Seleccione Agregar para finalizar el cuadro de diálogo Agregar Razor Pages.
- Cambie el nombre del contexto de datos para que acabe en
Si se produce el error 'Install the package Microsoft.VisualStudio.Web.CodeGeneration.Design and try again.'
en el scaffolding, vuelva a ejecutar la herramienta de scaffold o consulte esta incidencia de GitHub.
Los paquetes siguientes se instalan de forma automática:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.VisualStudio.Web.CodeGeneration.Design
Si el paso anterior falla, compile el proyecto y vuelva a intentar el paso de scaffolding.
El proceso de scaffolding:
- Crea Razor Pages en la carpeta Pages/Students:
Create.cshtml
yCreate.cshtml.cs
Delete.cshtml
yDelete.cshtml.cs
Details.cshtml
yDetails.cshtml.cs
Edit.cshtml
yEdit.cshtml.cs
Index.cshtml
yIndex.cshtml.cs
- Crea
Data/SchoolContext.cs
. - Agrega el contexto a la inserción de dependencias en
Startup.cs
. - Agrega una cadena de conexión de la base de datos a
appsettings.json
.
Cadena de conexión de base de datos
La herramienta de scaffolding genera una cadena de conexión en el archivo appsettings.json
.
La cadena de conexión especifica SQL Server LocalDB:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=CU-1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
LocalDB es una versión ligera del motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en producción. De forma predeterminada, LocalDB crea archivos .mdf en el directorio C:/Users/<user>
.
Actualización de la clase de contexto de base de datos
La clase principal que coordina la funcionalidad de EF Core para un modelo de datos determinado es la clase context de la base de datos. El contexto se deriva de Microsoft.EntityFrameworkCore.DbContext. En el contexto se especifica qué entidades se incluyen en el modelo de datos. En este proyecto, la clase se denomina SchoolContext
.
Actualice Data/SchoolContext.cs
con el siguiente código:
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
El código anterior cambia del elemento DbSet<Student> Student
singular al DbSet<Student> Students
plural. Para que el código de las páginas Razor coincida con el nombre nuevo de DBSet
, realice un cambio global de: _context.Student.
a: _context.Students.
Hay ocho repeticiones.
Dado que un conjunto de entidades contiene varias entidades, muchos desarrolladores prefieren que los nombres de la propiedad DBSet
sean plurales.
El código resaltado:
- Crea una propiedad DbSet<TEntity> para cada conjunto de entidades. En terminología de EF Core:
- Un conjunto de entidades normalmente se corresponde a una tabla de base de datos.
- Una entidad se corresponde con una fila de la tabla.
- Llama a OnModelCreating.
OnModelCreating
:- se llama cuando
SchoolContext
se ha inicializado, pero antes de que el modelo se haya 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.
- se llama cuando
Compile el proyecto para comprobar que no haya errores del compilador.
Startup.cs
ASP.NET Core integra la inserción de dependencias. Los servicios como SchoolContext
se registran con inserción de dependencias durante el inicio de la aplicación. Estos servicios se proporcionan a los componentes que los necesitan, como páginas Razor, a través de parámetros de constructor. El código de constructor que obtiene una instancia de contexto de base de datos se muestra más adelante en el tutorial.
La herramienta de scaffolding ha registrado de forma automática la clase de contexto con el contenedor de inserción de dependencias.
El proveedor de scaffolding ha agregado las líneas resaltadas siguientes:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
}
El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto DbContextOptions. Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena de conexión desde el archivo appsettings.json
.
Incorporación del filtro de excepción de base de datos
Agregue AddDatabaseDeveloperPageExceptionFilter y UseMigrationsEndPoint, tal como se muestra en el código siguiente:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
services.AddDatabaseDeveloperPageExceptionFilter();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
Agregue el paquete NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.
En la Consola del administrador de paquetes, escriba esto para agregar el paquete NuGet:
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
El paquete NuGet de Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
proporciona middleware de ASP.NET Core para páginas de error de Entity Framework Core. Este middleware ayuda a detectar y diagnosticar errores con migraciones de Entity Framework Core.
AddDatabaseDeveloperPageExceptionFilter
proporciona información de error útil en el entorno de desarrollo para errores de migración de EF.
Creación de la base de datos
Actualice Program.cs
para crear la base de datos si no existe:
using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
CreateDbIfNotExists(host);
host.Run();
}
private static void CreateDbIfNotExists(IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
// DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
El método EnsureCreated no realiza ninguna acción si existe una base de datos para el contexto. Si no existe ninguna base de datos, se crean la base de datos y el esquema. EnsureCreated
habilita el flujo de trabajo siguiente para controlar los cambios del modelo de datos:
- Se elimina la base de datos. Se pierden los datos existentes.
- Se cambia el modelo de datos. Por ejemplo, se agrega un campo
EmailAddress
. - Ejecutar la aplicación.
EnsureCreated
crea una base de datos con el esquema nuevo.
Este flujo de trabajo funciona bien al principio de la fase de desarrollo, cuando el esquema evoluciona rápidamente, siempre y cuando no sea necesario conservar los datos. La situación es distinta cuando es necesario conservar los datos introducidos en la base de datos. En ese caso, use las migraciones.
Más adelante en la serie de tutoriales, se eliminará la base de datos creada por EnsureCreated
y se usarán las migraciones. Una base de datos creada por EnsureCreated
no se puede actualizar mediante migraciones.
Prueba de la aplicación
- Ejecutar la aplicación.
- Haga clic en el vínculo Students y, después, en Crear nuevo.
- Pruebe los vínculos Edit, Details y Delete.
Inicializar la base de datos
El método EnsureCreated
crea una base de datos vacía. En esta sección se agrega código que rellena la base de datos con datos de prueba.
Cree el archivo Data/DbInitializer.cs
con el siguiente código:
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
};
context.Students.AddRange(students);
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
context.Courses.AddRange(courses);
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
context.Enrollments.AddRange(enrollments);
context.SaveChanges();
}
}
}
El código comprueba si hay alumnos en la base de datos. Si no hay ningún alumno, agrega datos de prueba a la base de datos. Crea los datos de prueba en matrices en lugar de colecciones List<T>
para optimizar el rendimiento.
En
Program.cs
, quite//
en la líneaDbInitializer.Initialize
:context.Database.EnsureCreated(); DbInitializer.Initialize(context);
Detenga la aplciación si se está ejecutando y ejecute el comando siguiente en la Consola del Administrador de paquetes (PMC):
Drop-Database -Confirm
Responda con
Y
para eliminar la base de datos.
- Reinicie la aplicación.
- Seleccione la página Students para ver los datos inicializados.
Consulta la base de datos
- Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio.
- En SSOX, seleccione (localdb)\MSSQLLocalDB > Bases de datos > SchoolContext-{GUID}. El nombre de la base de datos se genera a partir del nombre de contexto proporcionado anteriormente, más un guión y un GUID.
- Expanda el nodo Tablas.
- Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se crearon y las filas que se insertaron en la tabla.
- Haga clic con el botón derecho en la tabla Student y haga clic en Ver código para ver cómo el modelo
Student
se asigna al esquema de tablaStudent
.
Código asincrónico
La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.
Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código asincrónico permite que los recursos de servidor se usen de forma más eficaz y el servidor pueda administrar más tráfico sin retrasos.
El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones de poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico elevado, la posible mejora del rendimiento es importante.
En el código siguiente, la palabra clave async, el valor devuelto Task
, la palabra clave await
y el método ToListAsync
hacen que el código se ejecute de forma asincrónica.
public async Task OnGetAsync()
{
Students = await _context.Students.ToListAsync();
}
- La palabra clave
async
indica al compilador que:- Genere devoluciones de llamada para partes del cuerpo del método.
- Cree el objeto Task que se devuelve.
- El tipo devuelto
Task
representa el trabajo en curso. - La palabra clave
await
hace que el compilador divida el método en dos partes. La primera parte termina con la operación que se inició de forma asincrónica. La segunda parte se coloca en un método de devolución de llamada que se llama cuando finaliza la operación. ToListAsync
es la versión asincrónica del método de extensiónToList
.
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
ySaveChangesAsync
. No incluye las instrucciones que solo cambian unaIQueryable
, comovar students = context.Students.Where(s => s.LastName == "Davolio")
. - Un contexto de EF Core no es seguro para subprocesos: no intente realizar varias operaciones en paralelo.
- Para aprovechar las ventajas de rendimiento del código asincrónico, compruebe que en los paquetes de biblioteca (por ejemplo para la paginación) se usa async si llaman a métodos de EF Core que envían consultas a la base de datos.
Para obtener más información sobre la programación asincrónica en .NET, vea Programación asincrónica y Programación asincrónica con async y await.
Consideraciones de rendimiento
En general, una página web no debe cargar un número arbitrario de filas. Una consulta debe utilizar la paginación o un enfoque de limitación. Por ejemplo, la consulta anterior podría usar Take
para limitar las filas devueltas:
public async Task OnGetAsync()
{
Student = await _context.Students.Take(10).ToListAsync();
}
Enumerar una tabla grande en una vista podría devolver una respuesta HTTP 200 parcialmente construida si se produce una excepción de base de datos en mitad de la enumeración.
MaxModelBindingCollectionSize tiene como valor predeterminado 1024. El código siguiente establece MaxModelBindingCollectionSize
:
public void ConfigureServices(IServiceCollection services)
{
var myMaxModelBindingCollectionSize = Convert.ToInt32(
Configuration["MyMaxModelBindingCollectionSize"] ?? "100");
services.Configure<MvcOptions>(options =>
options.MaxModelBindingCollectionSize = myMaxModelBindingCollectionSize);
services.AddRazorPages();
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
services.AddDatabaseDeveloperPageExceptionFilter();
}
Consulte Configuración para obtener información sobre valores de configuración como MyMaxModelBindingCollectionSize
.
La paginación se trata más adelante en el tutorial.
Para obtener más información, vea Consideraciones sobre el rendimiento (EF).
Registros SQL de Entity Framework Core
La configuración de registros suele proporcionarla la sección Logging
de los archivos appsettings.{Environment}.json
. Para registrar instrucciones SQL, agregue "Microsoft.EntityFrameworkCore.Database.Command": "Information"
al archivo appsettings.Development.json
:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDB-2;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
,"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"AllowedHosts": "*"
}
Con el archivo JSON anterior, las instrucciones SQL se muestran en la línea de comandos y en ventana de salida de Visual Studio.
Para más información, vea Registrarse en .NET Core y ASP.NET Core y esta incidencia de GitHub.
Pasos siguientes
Este es el primero de una serie de tutoriales en los que se muestra cómo usar Entity Framework (EF) Core en una aplicación Razor Pages en ASP.NET Core. En el tutorial se crea un sitio web de una universidad ficticia, Contoso University. El sitio incluye funciones como la admisión de alumnos, la creación de cursos y las asignaciones de instructores. En el tutorial se usa el enfoque de Code First. Para obtener información sobre cómo seguir este tutorial mediante el enfoque de Database First, consulte este problema de GitHub.
Descargue o vea la aplicación completa. Instrucciones de descarga.
Requisitos previos
- Si no está familiarizado con Razor Pages, consulte la serie de tutoriales Introducción a Razor Pages antes de empezar este.
- Visual Studio 2019 con la carga de trabajo ASP.NET y desarrollo web
- SDK de .NET Core 3.0
Motores de bases de datos
En las instrucciones de Visual Studio se usa SQL Server LocalDB, una versión de SQL Server Express que solo se ejecuta en Windows.
En las instrucciones de Visual Studio Code se usa SQLite, un motor de base de datos multiplataforma.
Si decide usar SQLite, descargue e instale una herramienta de terceros para administrar y ver una base de datos de SQLite, como DB Browser for SQLite.
Solución de problemas
Si experimenta un problema que no puede resolver, compare el código con el proyecto completado. Una buena forma de obtener ayuda consiste en publicar una pregunta en StackOverflow.com con la etiqueta ASP.NET Core o la etiqueta EF Core.
La aplicación de ejemplo
La aplicación compilada en estos tutoriales es un sitio web básico de una universidad. Los usuarios pueden ver y actualizar la información de estudiantes, cursos e instructores. Estas son algunas de las pantallas que se crean en el tutorial.
El estilo de la interfaz de usuario de este sitio se basa en las plantillas de proyecto integradas. El enfoque del tutorial es cómo usar EF Core, no cómo personalizar la interfaz de usuario.
Siga el vínculo de la parte superior de la página para obtener el código fuente para el proyecto completado. La carpeta cu30 contiene el código para la versión ASP.NET Core 3.0 del tutorial. Los archivos que reflejan el estado del código para los tutoriales 1-7 se pueden encontrar en la carpeta cu30snapshots.
Para ejecutar la aplicación después de descargar el proyecto completado:
Compile el proyecto.
En la Consola del administrador de paquetes (PMC), ejecute el comando siguiente:
Update-Database
Ejecute el proyecto para inicializar la base de datos.
Creación del proyecto de aplicación web
- En el menú Archivo de Visual Studio, seleccione Nuevo>Proyecto.
- Seleccione Aplicación web de ASP.NET Core.
- Asigne el nombre ContosoUniversity al proyecto. Es importante usar este nombre exacto incluido el uso de mayúsculas, para que los espacios de nombres coincidan cuando se copie y pegue el código.
- Seleccione .NET Core y ASP.NET Core 3.0 en las listas desplegables y, luego, Aplicación web.
Configurar el estilo del sitio
Configure el encabezado, el pie de página y el menú del sitio mediante la actualización de Pages/Shared/_Layout.cshtml
:
Cambie todas las repeticiones de "ContosoUniversity" por "Contoso University". Hay tres ocurrencias.
Elimine las entradas de menú Home y Privacy, y agregue entradas para About, Students, Courses, Instructors y Departments.
Los cambios aparecen resaltados.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">Contoso University</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 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>
Ejecuta la aplicación para comprobar que aparece la página home.
El modelo de datos
En las secciones siguientes se crea un modelo de datos:
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
Cree una carpeta Models en la carpeta del proyecto.
Cree el archivo
Models/Student.cs
con el siguiente código:using System; using System.Collections.Generic; namespace ContosoUniversity.Models { public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public ICollection<Enrollment> Enrollments { get; set; } } }
La propiedad ID
se convierte en la columna de clave principal de la tabla de base de datos que se corresponde a esta clase. De forma predeterminada, EF Core interpreta como la clave principal una propiedad que se denomine ID
o classnameID
. Por tanto, el nombre que se reconoce de forma automática para la clave principal de la clase Student
es StudentID
. Para obtener más información, consulte EF Core: claves.
La propiedad Enrollments
es una propiedad de navegación. Las propiedades de navegación contienen otras entidades relacionadas con esta entidad. En este caso, la propiedad Enrollments
de una entidad Student
contiene todas las entidades Enrollment
que están relacionadas con esa instancia de Student. Por ejemplo, si una fila Student de la base de datos tiene dos filas Enrollment relacionadas, la propiedad de navegación Enrollments
contiene esas dos entidades Enrollment.
En la base de datos, una fila Enrollment se relaciona con una fila Student si su columna StudentID contiene el valor de identificador del alumno. Por ejemplo, imagine que una fila Student tiene el identificador 1. Las filas Enrollment relacionadas tendrán StudentID = 1. StudentID es una clave externa de la tabla Enrollment.
La propiedad Enrollments
se define como ICollection<Enrollment>
porque puede haber varias entidades Enrollment relacionadas. Puede usar otros tipos de colección, como List<Enrollment>
o HashSet<Enrollment>
. Cuando se usa ICollection<Enrollment>
, EF Core crea una colección HashSet<Enrollment>
de forma predeterminada.
La entidad Enrollment
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
acepta valores NULL. Una calificación que sea NULL es diferente de una calificación que sea cero; NULL significa que no se conoce una calificación o que todavía no se ha asignado.
La propiedad StudentID
es una clave externa y la propiedad de navegación correspondiente es Student
. Una entidad Enrollment
está asociada con una entidad Student
, por lo que la propiedad contiene una única entidad Student
.
La 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
Cree el archivo Models/Course.cs
con el siguiente código:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
La propiedad Enrollments
es una propiedad de navegación. Una entidad Course
puede estar relacionada con cualquier número de entidades Enrollment
.
El atributo DatabaseGenerated
permite que la aplicación especifique la clave principal en lugar de hacer que la base de datos la genere.
Compile el proyecto para comprobar que no hay errores de compilación.
Scaffolding de las páginas Student
En esta sección, se usa la herramienta de scaffolding de ASP.NET Core para generar lo siguiente:
- Una clase context de EF Core. El contexto es la clase principal que coordina la funcionalidad de Entity Framework para un modelo de datos determinado. Se deriva de la clase
Microsoft.EntityFrameworkCore.DbContext
. - Razor Pages que controlan las operaciones de creación, lectura, actualización y eliminación (CRUD) de la entidad
Student
.
- Cree una carpeta Students en la carpeta Pages.
- En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Pages/Students y seleccione Agregar>Nuevo elemento con scaffold.
- En el cuadro de diálogo Agregar scaffold, seleccione Páginas de Razor que usan Entity Framework (CRUD)>AGREGAR.
- En el cuadro de diálogo para agregar instancias de Razor Pages que usan Entity Framework (CRUD):
- En la lista desplegable Clase de modelo, seleccione Student (ContosoUniversity.Models) .
- En la fila Clase de contexto de datos, seleccione el signo + (más).
- Cambie el nombre del contexto de datos ContosoUniversity.Models.ContosoUniversityContext por ContosoUniversity.Data.SchoolContext.
- Seleccione Agregar.
Los paquetes siguientes se instalan de forma automática:
Microsoft.VisualStudio.Web.CodeGeneration.Design
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.Extensions.Logging.Debug
Microsoft.EntityFrameworkCore.Tools
Si tiene un problema con el paso anterior, compile el proyecto y vuelva a intentar el paso de scaffolding.
El proceso de scaffolding:
- Crea Razor Pages en la carpeta Pages/Students:
Create.cshtml
yCreate.cshtml.cs
Delete.cshtml
yDelete.cshtml.cs
Details.cshtml
yDetails.cshtml.cs
Edit.cshtml
yEdit.cshtml.cs
Index.cshtml
yIndex.cshtml.cs
- Crea
Data/SchoolContext.cs
. - Agrega el contexto a la inserción de dependencias en
Startup.cs
. - Agrega una cadena de conexión de la base de datos a
appsettings.json
.
Cadena de conexión de base de datos
El archivo appsettings.json
especifica la cadena de conexión SQL Server LocalDB.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext6;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
LocalDB es una versión ligera del motor de base de datos de SQL Server Express y está dirigida al desarrollo de aplicaciones, no al uso en producción. De forma predeterminada, LocalDB crea archivos .mdf en el directorio C:/Users/<user>
.
Actualización de la clase de contexto de base de datos
La clase principal que coordina la funcionalidad de EF Core para un modelo de datos determinado es la clase context de la base de datos. El contexto se deriva de Microsoft.EntityFrameworkCore.DbContext. En el contexto se especifica qué entidades se incluyen en el modelo de datos. En este proyecto, la clase se denomina SchoolContext
.
Actualice Data/SchoolContext.cs
con el siguiente código:
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}
El código resaltado crea una propiedad DbSet<TEntity> para cada conjunto de entidades. En terminología de EF Core:
- Un conjunto de entidades normalmente se corresponde a una tabla de base de datos.
- Una entidad se corresponde con una fila de la tabla.
Puesto que un conjunto de entidades contiene varias entidades, las propiedades DBSet deben ser nombres en plural. Como la herramienta de scaffolding ha creado una instancia Student
de DBSet, en este paso se cambia a Students
en plural.
Para que el código de Razor Pages coincida con el nuevo nombre de DBSet, realice un cambio global de _context.Student
a _context.Students
en todo el proyecto. Hay ocho repeticiones.
Compile el proyecto para comprobar que no haya errores del compilador.
Startup.cs
ASP.NET Core integra la inserción de dependencias. Los servicios, como el contexto de base de datos de EF Core, se registran con inserción de dependencias durante el inicio de la aplicación. Estos servicios se proporcionan a los componentes que los necesitan (como Razor Pages) a través de parámetros de constructor. El código de constructor que obtiene una instancia de contexto de base de datos se muestra más adelante en el tutorial.
La herramienta de scaffolding ha registrado de forma automática la clase de contexto con el contenedor de inserción de dependencias.
En
ConfigureServices
, el proveedor de scaffolding ha agregado la línea resaltada:public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddDbContext<SchoolContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SchoolContext"))); }
El nombre de la cadena de conexión se pasa al contexto mediante una llamada a un método en un objeto DbContextOptions. Para el desarrollo local, el sistema de configuración de ASP.NET Core lee la cadena de conexión desde el archivo appsettings.json
.
Creación de la base de datos
Actualice Program.cs
para crear la base de datos si no existe:
using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
CreateDbIfNotExists(host);
host.Run();
}
private static void CreateDbIfNotExists(IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
// DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
El método EnsureCreated no realiza ninguna acción si existe una base de datos para el contexto. Si no existe ninguna base de datos, se crean la base de datos y el esquema. EnsureCreated
habilita el flujo de trabajo siguiente para controlar los cambios del modelo de datos:
- Se elimina la base de datos. Se pierden los datos existentes.
- Se cambia el modelo de datos. Por ejemplo, se agrega un campo
EmailAddress
. - Ejecutar la aplicación.
EnsureCreated
crea una base de datos con el esquema nuevo.
Este flujo de trabajo funciona bien al principio de la fase de desarrollo cuando el esquema evoluciona rápidamente, siempre y cuando no sea necesario conservar los datos. La situación es distinta cuando es necesario conservar los datos introducidos en la base de datos. En ese caso, use las migraciones.
Más adelante en la serie de tutoriales, eliminará la base de datos creada por EnsureCreated
y, en su lugar, usará las migraciones. Una base de datos creada por EnsureCreated
no se puede actualizar mediante migraciones.
Prueba de la aplicación
- Ejecutar la aplicación.
- Haga clic en el vínculo Students y, después, en Crear nuevo.
- Pruebe los vínculos Edit, Details y Delete.
Inicializar la base de datos
El método EnsureCreated
crea una base de datos vacía. En esta sección se agrega código que rellena la base de datos con datos de prueba.
Cree el archivo Data/DbInitializer.cs
con el siguiente código:
using ContosoUniversity.Data;
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
};
context.Students.AddRange(students);
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
context.Courses.AddRange(courses);
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
context.Enrollments.AddRange(enrollments);
context.SaveChanges();
}
}
}
El código comprueba si hay alumnos en la base de datos. Si no hay ningún alumno, agrega datos de prueba a la base de datos. Crea los datos de prueba en matrices en lugar de colecciones List<T>
para optimizar el rendimiento.
En
Program.cs
, reemplace la llamada aEnsureCreated
con una llamada aDbInitializer.Initialize
:// context.Database.EnsureCreated(); DbInitializer.Initialize(context);
Detenga la aplciación si se está ejecutando y ejecute el comando siguiente en la Consola del Administrador de paquetes (PMC):
Drop-Database
Reinicie la aplicación.
Seleccione la página Students para ver los datos inicializados.
Consulta la base de datos
- Abra el Explorador de objetos de SQL Server (SSOX) desde el menú Vista en Visual Studio.
- En SSOX, seleccione (localdb)\MSSQLLocalDB > Bases de datos > SchoolContext-{GUID}. El nombre de la base de datos se genera a partir del nombre de contexto proporcionado anteriormente, más un guión y un GUID.
- Expanda el nodo Tablas.
- Haga clic con el botón derecho en la tabla Student y haga clic en Ver datos para ver las columnas que se crearon y las filas que se insertaron en la tabla.
- Haga clic con el botón derecho en la tabla Student y haga clic en Ver código para ver cómo el modelo
Student
se asigna al esquema de tablaStudent
.
Código asincrónico
La programación asincrónica es el modo predeterminado de ASP.NET Core y EF Core.
Un servidor web tiene un número limitado de subprocesos disponibles y, en situaciones de carga alta, es posible que todos los subprocesos disponibles estén en uso. Cuando esto ocurre, el servidor no puede procesar nuevas solicitudes hasta que los subprocesos se liberen. Con el código sincrónico, se pueden acumular muchos subprocesos mientras no estén realizando ningún trabajo porque están a la espera de que finalice la E/S. Con el código asincrónico, cuando un proceso está a la espera de que finalice la E/S, se libera su subproceso para el que el servidor lo use para el procesamiento de otras solicitudes. Como resultado, el código asincrónico permite que los recursos de servidor se usen de forma más eficaz y el servidor pueda administrar más tráfico sin retrasos.
El código asincrónico introduce una pequeña cantidad de sobrecarga en tiempo de ejecución. En situaciones de poco tráfico, la disminución del rendimiento es insignificante, mientras que en situaciones de tráfico elevado, la posible mejora del rendimiento es importante.
En el código siguiente, la palabra clave async, el valor devuelto Task<T>
, la palabra clave await
y el método ToListAsync
hacen que el código se ejecute de forma asincrónica.
public async Task OnGetAsync()
{
Students = await _context.Students.ToListAsync();
}
- La palabra clave
async
indica al compilador que:- Genere devoluciones de llamada para partes del cuerpo del método.
- Cree el objeto Task que se devuelve.
- El tipo devuelto
Task<T>
representa el trabajo en curso. - La palabra clave
await
hace que el compilador divida el método en dos partes. La primera parte termina con la operación que se inició de forma asincrónica. La segunda parte se coloca en un método de devolución de llamada que se llama cuando finaliza la operación. ToListAsync
es la versión asincrónica del método de extensiónToList
.
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
ySaveChangesAsync
. No incluye las instrucciones que solo cambian unaIQueryable
, comovar students = context.Students.Where(s => s.LastName == "Davolio")
. - Un contexto de EF Core no es seguro para subprocesos: no intente realizar varias operaciones en paralelo.
- Para aprovechar las ventajas de rendimiento del código asincrónico, compruebe que en los paquetes de biblioteca (por ejemplo para la paginación) se usa async si llaman a métodos de EF Core que envían consultas a la base de datos.
Para obtener más información sobre la programación asincrónica en .NET, vea Programación asincrónica y Programación asincrónica con async y await.