Tutorial: Información sobre escenarios avanzados: ASP.NET MVC con EF Core
En el tutorial anterior, se implementó la herencia de tabla por jerarquía. En este tutorial se presentan varios temas que son útiles para tener en cuenta al ir más allá de los conceptos básicos del desarrollo de aplicaciones web de ASP.NET Core que usan Entity Framework Core.
En este tutorial, harás lo siguiente:
- Realiza consultas SQL sin formato
- Llama a una consulta para devolver entidades
- Llama a una consulta para devolver otros tipos
- Llamar a una consulta update
- Examen de consultas SQL
- Creación de una capa de abstracción
- Más información sobre la detección automática de cambios
- Más información sobre EF Core código fuente y planes de desarrollo
- Aprenda a usar LINQ dinámico para simplificar el código
Prerrequisitos
Realiza consultas SQL sin formato
Una de las ventajas de usar Entity Framework es que evita la vinculación del código demasiado estrechamente con un método determinado de almacenamiento de datos. Para ello, genera consultas y comandos SQL, lo que también le libera de tener que escribirlas usted mismo. Pero hay escenarios excepcionales cuando es necesario ejecutar consultas SQL específicas que ha creado manualmente. En estos escenarios, Entity Framework Code First API incluye métodos que permiten pasar comandos SQL directamente a la base de datos. Tiene las siguientes opciones en EF Core 1.0:
Use el método
DbSet.FromSql
para las consultas que devuelven tipos de entidad. Los objetos devueltos deben ser del tipo esperado por el objetoDbSet
, y son rastreados automáticamente por el contexto de la base de datos, a menos que desactive el seguimiento.Use el
Database.ExecuteSqlCommand
para comandos que no son de consulta.
Si necesita ejecutar una consulta que devuelva tipos que no son entidades, puede usar ADO.NET con la conexión de base de datos proporcionada por EF. El contexto de la base de datos no realiza el seguimiento de los datos devueltos, aunque use este método para recuperar tipos de entidad.
Como siempre es cierto cuando se ejecutan comandos SQL en una aplicación web, debe tomar precauciones para proteger el sitio frente a ataques por inyección de CÓDIGO SQL. Una manera de hacerlo es usar consultas con parámetros para asegurarse de que las cadenas enviadas por una página web no se pueden interpretar como comandos SQL. En este tutorial usará consultas con parámetros al integrar la entrada del usuario en una consulta.
Llama a una consulta para devolver entidades
La clase DbSet<TEntity>
proporciona un método que puede usar para ejecutar una consulta que devuelve una entidad de tipo TEntity
. Para ver cómo funciona, cambia el código en el método Details
del controlador del Departamento.
En DepartmentsController.cs
, en el método Details
, reemplace el código que recupera un departamento por una llamada al método FromSql
, como se muestra en el código resaltado siguiente:
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
string query = "SELECT * FROM Department WHERE DepartmentID = {0}";
var department = await _context.Departments
.FromSql(query, id)
.Include(d => d.Administrator)
.AsNoTracking()
.FirstOrDefaultAsync();
if (department == null)
{
return NotFound();
}
return View(department);
}
Para comprobar que el nuevo código funciona correctamente, seleccione la pestaña Departments y, después, Details para uno de los departamentos.
Llama a una consulta para devolver otros tipos
Anteriormente creó una cuadrícula de estadísticas de alumno de la página About que mostraba el número de alumnos para cada fecha de inscripción. Obtuvo los datos del conjunto de entidades "Students" (_context.Students
) y usó LINQ para proyectar los resultados en una lista de objetos del modelo de vista EnrollmentDateGroup
. Supongamos que quiere escribir el propio SQL en lugar de usar LINQ. Para ello, debe ejecutar una consulta SQL que devuelva algo distinto de los objetos de entidad. En EF Core 1.0, una manera de hacerlo es escribir código ADO.NET y obtener la conexión de base de datos de EF.
En HomeController.cs
, reemplace el método About
por el código siguiente:
public async Task<ActionResult> About()
{
List<EnrollmentDateGroup> groups = new List<EnrollmentDateGroup>();
var conn = _context.Database.GetDbConnection();
try
{
await conn.OpenAsync();
using (var command = conn.CreateCommand())
{
string query = "SELECT EnrollmentDate, COUNT(*) AS StudentCount "
+ "FROM Person "
+ "WHERE Discriminator = 'Student' "
+ "GROUP BY EnrollmentDate";
command.CommandText = query;
DbDataReader reader = await command.ExecuteReaderAsync();
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var row = new EnrollmentDateGroup { EnrollmentDate = reader.GetDateTime(0), StudentCount = reader.GetInt32(1) };
groups.Add(row);
}
}
reader.Dispose();
}
}
finally
{
conn.Close();
}
return View(groups);
}
Agregue una instrucción using:
using System.Data.Common;
Ejecute la aplicación y diríjase a la página de Información. Muestra los mismos datos que hizo antes.
Llamar a una consulta update
Supongamos que los administradores de Contoso University quieren realizar cambios globales en la base de datos, como cambiar el número de créditos de cada curso. Si la universidad tiene un gran número de cursos, sería ineficaz recuperarlos todos como entidades y cambiarlos individualmente. En esta sección, implementará una página web que permita al usuario especificar un factor por el que cambiar el número de créditos de todos los cursos y realizará el cambio ejecutando una instrucción SQL UPDATE. La página web tendrá un aspecto similar a la siguiente ilustración:
En CoursesController.cs
, agregue métodos UpdateCourseCredits para HttpGet y HttpPost:
public IActionResult UpdateCourseCredits()
{
return View();
}
[HttpPost]
public async Task<IActionResult> UpdateCourseCredits(int? multiplier)
{
if (multiplier != null)
{
ViewData["RowsAffected"] =
await _context.Database.ExecuteSqlCommandAsync(
"UPDATE Course SET Credits = Credits * {0}",
parameters: multiplier);
}
return View();
}
Cuando el controlador procesa una solicitud HttpGet, no se devuelve nada en ViewData["RowsAffected"]
y la vista muestra un cuadro de texto vacío y un botón enviar, como se muestra en la ilustración anterior.
Cuando se hace clic en el botón Actualizar, se llama al método HttpPost y el multiplicador tiene el valor especificado en el cuadro de texto. A continuación, el código ejecuta el SQL que actualiza los cursos y devuelve a la vista en ViewData
el número de filas afectadas. Cuando la vista obtiene un valor de RowsAffected
, muestra el número de filas actualizadas.
En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Views/Courses y luego haga clic en Agregar > Nuevo elemento.
En el cuadro de diálogo Agregar nuevo elemento, haga clic en ASP.NET Core, en la opción Instalado del panel izquierdo, haga clic en Razor Vista y ponga el nombre a la nueva vista UpdateCourseCredits.cshtml
.
En Views/Courses/UpdateCourseCredits.cshtml
, reemplace el código de plantilla por el código siguiente:
@{
ViewBag.Title = "UpdateCourseCredits";
}
<h2>Update Course Credits</h2>
@if (ViewData["RowsAffected"] == null)
{
<form asp-action="UpdateCourseCredits">
<div class="form-actions no-color">
<p>
Enter a number to multiply every course's credits by: @Html.TextBox("multiplier")
</p>
<p>
<input type="submit" value="Update" class="btn btn-default" />
</p>
</div>
</form>
}
@if (ViewData["RowsAffected"] != null)
{
<p>
Number of rows updated: @ViewData["RowsAffected"]
</p>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
Ejecute el método UpdateCourseCredits
seleccionando la pestaña Cursos y, a continuación, agregando "/UpdateCourseCredits" al final de la dirección URL en la barra de direcciones del explorador (por ejemplo: http://localhost:5813/Courses/UpdateCourseCredits
). Escriba un número en el cuadro de texto:
Haga clic en Update(Actualizar). Verá el número de filas afectadas:
Haga clic en Volver a la lista para ver la lista de cursos con el número revisado de créditos.
Tenga en cuenta que el código de producción garantizará que las actualizaciones siempre produzcan datos válidos. El código simplificado que se muestra aquí podría multiplicar el número de créditos suficientes para dar lugar a números mayores que 5. (La propiedad Credits
tiene un atributo [Range(0, 5)]
). La consulta de actualización funcionaría, pero los datos no válidos podrían provocar resultados inesperados en otras partes del sistema que suponen que el número de créditos es 5 o menos.
Para obtener más información sobre las consultas SQL sin procesar, vea consultas SQL sin procesar.
Examen de consultas SQL
A veces resulta útil poder ver las consultas SQL reales que se envían a la base de datos. La funcionalidad de registro de eventos integrada en ASP.NET Core es utilizada automáticamente por EF Core para escribir registros que contienen el SQL de las consultas y actualizaciones. En esta sección verá algunos ejemplos de registro de SQL.
Abra StudentsController.cs
y, en el método Details
, establezca un punto de interrupción en la instrucción if (student == null)
.
Ejecute la aplicación en modo de depuración y vaya a la página Details de un alumno.
Vaya a la ventana Salida que muestra la salida de depuración y verá la consulta:
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (56ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT TOP(2) [s].[ID], [s].[Discriminator], [s].[FirstName], [s].[LastName], [s].[EnrollmentDate]
FROM [Person] AS [s]
WHERE ([s].[Discriminator] = N'Student') AND ([s].[ID] = @__id_0)
ORDER BY [s].[ID]
Microsoft.EntityFrameworkCore.Database.Command:Information: Executed DbCommand (122ms) [Parameters=[@__id_0='?'], CommandType='Text', CommandTimeout='30']
SELECT [s.Enrollments].[EnrollmentID], [s.Enrollments].[CourseID], [s.Enrollments].[Grade], [s.Enrollments].[StudentID], [e.Course].[CourseID], [e.Course].[Credits], [e.Course].[DepartmentID], [e.Course].[Title]
FROM [Enrollment] AS [s.Enrollments]
INNER JOIN [Course] AS [e.Course] ON [s.Enrollments].[CourseID] = [e.Course].[CourseID]
INNER JOIN (
SELECT TOP(1) [s0].[ID]
FROM [Person] AS [s0]
WHERE ([s0].[Discriminator] = N'Student') AND ([s0].[ID] = @__id_0)
ORDER BY [s0].[ID]
) AS [t] ON [s.Enrollments].[StudentID] = [t].[ID]
ORDER BY [t].[ID]
Observará algo que podría sorprenderle: SQL selecciona hasta 2 filas (TOP(2)
) de la tabla Person. El método SingleOrDefaultAsync
no se resuelve en 1 fila en el servidor. Este es el motivo:
- Si la consulta devolvería varias filas, el método devuelve null.
- Para determinar si la consulta devolvería varias filas, EF debe comprobar si devuelve al menos 2.
Tenga en cuenta que no tiene que usar el modo de depuración y parar en un punto de interrupción para obtener los resultados del registro en la ventana Salida. Es simplemente una manera cómoda de detener el registro en el punto en el que quiere revisar el resultado. Si no lo hace, el registro continúa y tiene que desplazarse hacia atrás para encontrar los elementos que le interesan.
Creación de una capa de abstracción
Muchos desarrolladores escriben código para implementar el repositorio y la unidad de patrones de trabajo como un contenedor en torno al código que funciona con Entity Framework. Estos patrones están diseñados para crear una capa de abstracción entre la capa de acceso a datos y la capa lógica de negocios de una aplicación. La implementación de estos patrones puede ayudar a aislar la aplicación de los cambios en el almacén de datos y puede facilitar las pruebas unitarias automatizadas o el desarrollo controlado por pruebas (TDD). Sin embargo, escribir código adicional para implementar estos patrones no siempre es la mejor opción para las aplicaciones que usan EF, por varias razones:
La propia clase de contexto de EF protege tu código del código específico del sistema de almacenamiento de datos.
La clase de contexto de EF puede actuar como una clase de unidad de trabajo para las actualizaciones de base de datos que hace con EF.
EF incluye características para implementar TDD sin escribir código de repositorio.
Para obtener información sobre cómo implementar el repositorio y las unidades de trabajo, consulte la versión de Entity Framework 5 de esta serie de tutoriales.
Entity Framework Core implementa un proveedor de base de datos en memoria que se puede usar para las pruebas. Para obtener más información, vea Test with InMemory.
Detección automática de cambios
Entity Framework determina cómo ha cambiado una entidad (y, por tanto, qué actualizaciones deben enviarse a la base de datos) comparando los valores actuales de una entidad con los valores originales. Los valores originales se almacenan cuando se consulta o adjunta la entidad. Algunos de los métodos que provocan la detección automática de cambios son los siguientes:
DbContext.SaveChanges
DbContext.Entry
ChangeTracker.Entries
Si realiza un seguimiento de un gran número de entidades y llama a uno de estos métodos muchas veces en un bucle, es posible que obtenga mejoras de rendimiento significativas desactivando temporalmente la detección automática de cambios mediante la propiedad ChangeTracker.AutoDetectChangesEnabled
. Por ejemplo:
_context.ChangeTracker.AutoDetectChangesEnabled = false;
EF Core código fuente y planes de desarrollo
El código fuente de Entity Framework Core está en https://github.com/dotnet/efcore. El repositorio de EF Core contiene las compilaciones nocturnas, el seguimiento de problemas, las especificaciones de características, las notas de las reuniones de diseño y el plan de desarrollo futuro. Puede archivar o encontrar errores y contribuir.
Aunque el código fuente está abierto, Entity Framework Core es totalmente compatible como producto de Microsoft. El equipo de Microsoft Entity Framework mantiene el control sobre qué contribuciones se aceptan y prueba todos los cambios de código para garantizar la calidad de cada versión.
Realizar la ingeniería inversa de la base de datos existente
Para realizar la ingeniería inversa de un modelo de datos, incluidas las clases de entidad de una base de datos existente, use el comando scaffold-dbcontext. Consulte el tutorial de introducción.
Uso de LINQ dinámico para simplificar el código
El tercer tutorial de esta serie muestra cómo escribir código LINQ mediante el codificado de forma rígida de los nombres de columna en una instrucción switch
. Con dos columnas para elegir, esto funciona bien, pero si tiene muchas columnas, el código podría volverse extenso. Para solucionar ese problema, puede usar el método EF.Property
para especificar el nombre de la propiedad como una cadena. Para probar este enfoque, reemplace el método Index
en el StudentsController
por el código siguiente.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
{
ViewData["CurrentSort"] = sortOrder;
ViewData["NameSortParm"] =
String.IsNullOrEmpty(sortOrder) ? "LastName_desc" : "";
ViewData["DateSortParm"] =
sortOrder == "EnrollmentDate" ? "EnrollmentDate_desc" : "EnrollmentDate";
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
ViewData["CurrentFilter"] = searchString;
var students = from s in _context.Students
select s;
if (!String.IsNullOrEmpty(searchString))
{
students = students.Where(s => s.LastName.Contains(searchString)
|| s.FirstMidName.Contains(searchString));
}
if (string.IsNullOrEmpty(sortOrder))
{
sortOrder = "LastName";
}
bool descending = false;
if (sortOrder.EndsWith("_desc"))
{
sortOrder = sortOrder.Substring(0, sortOrder.Length - 5);
descending = true;
}
if (descending)
{
students = students.OrderByDescending(e => EF.Property<object>(e, sortOrder));
}
else
{
students = students.OrderBy(e => EF.Property<object>(e, sortOrder));
}
int pageSize = 3;
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(),
pageNumber ?? 1, pageSize));
}
Reconocimientos
Tom Dykstra y Rick Anderson (twitter @RickAndMSFT) escribió este tutorial. Rowan Miller, Diego Vega y otros miembros del equipo de Entity Framework ayudaron con revisiones de código y ayudaron a depurar problemas que se produjeron mientras se estaba escribiendo código para los tutoriales. John Parente y Paul Goldman trabajaron en la actualización del tutorial para ASP.NET Core 2.2.
Solución de errores comunes
ContosoUniversity.dll usado por otro proceso
Mensaje de error:
No se puede abrir '... bin\Debug\netcoreapp1.0\ContosoUniversity.dll' for writing -- 'The process cannot access the file '...\bin\Debug\netcoreapp1.0\ContosoUniversity.dll' porque está siendo utilizado por otro proceso.
Solución:
Detenga el sitio en IIS Express. Vaya a la bandeja del sistema de Windows, busque IIS Express y haga clic con el botón derecho en su icono; seleccione el sitio de Contoso University y, después, haga clic en Detener sitio.
Migración estructurada sin necesidad de escribir código en los métodos Up y Down
Causa posible:
Los comandos de la CLI de EF no cierran y guardan archivos de código automáticamente. Si tiene cambios no guardados al ejecutar el comando migrations add
, EF no encontrará los cambios.
Solución:
Ejecute el comando migrations remove
, guarde los cambios del código y vuelva a ejecutar el comando migrations add
.
Errores al ejecutar la actualización de la base de datos
Es posible obtener otros errores al realizar cambios de esquema en una base de datos que tenga datos existentes. Si recibe errores de migración que no puede resolver, puede cambiar el nombre de la base de datos en la cadena de conexión o eliminar la base de datos. Con una nueva base de datos, no hay datos que migrar y el comando update-database es mucho más probable que se complete sin errores.
El enfoque más sencillo es cambiar el nombre de la base de datos en appsettings.json
. La próxima vez que ejecute database update
, se creará una nueva base de datos.
Para eliminar una base de datos en SSOX, haga clic con el botón derecho en la base de datos, haga clic en Eliminary, a continuación, en el cuadro de diálogo Eliminar base de datos seleccione Cerrar conexiones existentes y haga clic en Aceptar.
Para eliminar una base de datos mediante la CLI, ejecute el comando database drop
CLI:
dotnet ef database drop
Error al localizar la instancia de SQL Server
Mensaje de error:
Se produjo un error relacionado con la red o específico de la instancia al establecer una conexión a SQL Server. No se encontró el servidor o no se pudo acceder a él. Compruebe que el nombre de instancia es correcto y que SQL Server está configurado para permitir conexiones remotas. (proveedor: Interfaces de red de SQL, error: 26 - Error al localizar el servidor o la instancia especificados)
Solución:
Compruebe la cadena de conexión. Si ha eliminado manualmente el archivo de base de datos, cambie el nombre de la base de datos en la cadena de construcción para empezar con una nueva base de datos.
Obtención del código
Descargar o ver la aplicación completada.
Recursos adicionales
Para obtener más información sobre EF Core, consulte la documentación de Entity Framework Core. También hay un libro disponible: Entity Framework Core en Acción.
Para obtener información sobre cómo implementar una aplicación web, consulte Host e implementar ASP.NET Core.
Para obtener información sobre otros temas relacionados con ASP.NET Core MVC, como la autenticación y la autorización, consulte Información general de ASP.NET Core.
Para obtener instrucciones sobre cómo crear una aplicación ASP.NET Core confiable, segura, eficaz, comprobable y escalable, consulte los patrones de aplicaciones web empresariales. Está disponible una aplicación web de ejemplo, totalmente funcional y de calidad de producción, que implementa los patrones.
Pasos siguientes
En este tutorial, tú:
- Ejecución de consultas SQL en bruto
- Llamado a una consulta para devolver entidades
- Llamado a una consulta para devolver otros tipos
- Llamado a una consulta update
- Consultas SQL examinadas
- Creación de una capa de abstracción
- Aprendí sobre la detección automática de cambios
- Aprendí sobre el código fuente EF Core y los planes de desarrollo.
- Ha aprendido a usar LINQ dinámico para simplificar el código
Esto completa esta serie de tutoriales sobre el uso de Entity Framework Core en una aplicación ASP.NET Core MVC. En esta serie se ha trabajado con una base de datos nueva, pero también se pueden utilizar técnicas de ingeniería inversa en un modelo de una base de datos existente.
Tutorial: EF Core con MVC, base de datos existente