Tutorial: Uso de procedimientos asincrónicos y almacenados con EF en una aplicación de MVC de ASP.NET

En tutoriales anteriores has aprendido a leer y a actualizar datos mediante el modelo de programación sincrónica. En este tutorial verás cómo implementar el modelo de programación asincrónica. El código asincrónico puede ayudar a una aplicación a funcionar mejor porque hace un mejor uso de los recursos del servidor.

En este tutorial también verás cómo usar procedimientos almacenados para operaciones de inserción, actualización y eliminación en una entidad.

Por último, volverás a implementar la aplicación en Azure, junto con todos los cambios de la base de datos que has implementado desde la primera vez que implementaste.

En las ilustraciones siguientes se muestran algunas de las páginas con las que va a trabajar.

Departments page

Create Department

En este tutorial ha:

  • Aprenderás sobre el código asincrónico
  • Crearás un controlador de departamento
  • Uso de procedimientos almacenados
  • Implementación en Azure

Requisitos previos

Por qué usar código asincrónico

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

En versiones anteriores de .NET, escribir y probar código asincrónico era complejo, propenso a errores y difícil de depurar. En .NET 4.5; escribir, probar y depurar código asincrónico es tan fácil que por lo general deberías hacerlo, a menos que tengas una razón para no hacerlo. El código asincrónico introduce una pequeña cantidad de sobrecarga, pero para 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.

Para obtener más información sobre la programación asincrónica, consulta Uso de la compatibilidad asincrónica de .NET 4.5 para evitar el bloqueo de llamadas.

Creación de un controlador de departamento

Crea un controlador de departamento de la misma manera que hiciste los controladores anteriores, pero, esta vez, activa la casilla Usar acciones del controlador asincrónico.

Los siguientes aspectos destacados muestran lo que se agregó al código sincrónico para que el método Index lo convierta en asincrónico:

public async Task<ActionResult> Index()
{
    var departments = db.Departments.Include(d => d.Administrator);
    return View(await departments.ToListAsync());
}

Se aplicaron cuatro cambios para permitir que la consulta de base de datos de Entity Framework se ejecute de forma asincrónica:

  • El método se marca con la palabra clave async, que indica al compilador que genere devoluciones de llamada para partes del cuerpo del método y que crea automáticamente una instancia del objeto Task<ActionResult>que se devuelve.
  • El tipo de valor devuelto se cambió de ActionResult a Task<ActionResult>. El tipo de valor Task<T> representa el trabajo en curso con un resultado de tipo T.
  • La palabra clave await se aplicó a la llamada de servicio web. Cuando el compilador ve esta palabra clave, en segundo plano divide 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 al que se llama cuando finaliza la operación.
  • Se ha llamado a la versión asincrónica del método de extensión ToList.

¿Por qué se modifica la instrucción departments.ToList, pero no la instrucción departments = db.Departments? El motivo es que 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. La instrucción departments = db.Departments configura una consulta, pero la consulta no se ejecuta hasta que se llama al método ToList. Por lo tanto, solo el método ToList se ejecuta de forma asincrónica.

En el método Details y los métodos HttpGet, Edit y Delete, el método Find es el que hace que una consulta se envíe a la base de datos, por lo que es el método que se ejecuta de forma asincrónica:

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Department department = await db.Departments.FindAsync(id);
    if (department == null)
    {
        return HttpNotFound();
    }
    return View(department);
}

En los métodos Create, HttpPost Edit y DeleteConfirmed, es la llamada al método SaveChanges la que hace que se ejecute un comando, no instrucciones como db.Departments.Add(department) que solo hacen que se modifiquen las entidades en la memoria.

public async Task<ActionResult> Create(Department department)
{
    if (ModelState.IsValid)
    {
        db.Departments.Add(department);
    await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }

Abr2 Views\Department\Index.cshtml y reemplaza el código de plantilla con el código siguiente:

@model IEnumerable<ContosoUniversity.Models.Department>
@{
    ViewBag.Title = "Departments";
}
<h2>Departments</h2>
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Budget)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.StartDate)
        </th>
    <th>
            Administrator
        </th>
        <th></th>
    </tr>
@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Budget)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.StartDate)
        </td>
    <td>
            @Html.DisplayFor(modelItem => item.Administrator.FullName)
            </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.DepartmentID }) |
            @Html.ActionLink("Details", "Details", new { id=item.DepartmentID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.DepartmentID })
        </td>
    </tr>
}
</table>

Este código cambia el título de Índice a Departamentos, mueve el nombre del administrador a la derecha y proporciona el nombre completo del administrador.

En las vistas Crear, Eliminar, Detalles y Editar, cambia el título del campo InstructorID a "Administrador" de la misma manera que cambiaste el campo de nombre del departamento a "Departamento" en las vistas Curso.

En las vistas Crear y Editar, usa el código siguiente:

<label class="control-label col-md-2" for="InstructorID">Administrator</label>

En las vistas Eliminar y Detalles, usa el código siguiente:

<dt>
    Administrator
</dt>

Ejecuta la aplicación y haz clic en la pestaña Departamentos.

Todo funciona igual que en los demás controladores, pero en este controlador todas las consultas SQL se ejecutan de forma asincrónica.

Algunos aspectos que se deben tener en cuenta al usar la programación asincrónica con Entity Framework son:

  • El código asincrónico no es seguro para subprocesos. En otras palabras, no intentes realizar varias operaciones en paralelo mediante la misma instancia de contexto.
  • Si quiere aprovechar las ventajas de rendimiento del código asincrónico, asegúrese de que en los paquetes de biblioteca que use (por ejemplo para paginación), también se usa async si llaman a cualquier método de Entity Framework que haga que las consultas se envíen a la base de datos.

Uso de procedimientos almacenados

Algunos desarrolladores y DBA prefieren usar procedimientos almacenados para el acceso a bases de datos. En versiones anteriores de Entity Framework, puedes recuperar datos mediante un procedimiento almacenado ejecutando una consulta SQL sin procesar, pero no puedes indicar a EF que use procedimientos almacenados para las operaciones de actualización. En EF 6, es fácil configurar Code First para usar procedimientos almacenados.

  1. En DAL\SchoolContext.cs, agrega el código resaltado al método OnModelCreating.

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Entity<Course>()
            .HasMany(c => c.Instructors).WithMany(i => i.Courses)
            .Map(t => t.MapLeftKey("CourseID")
                .MapRightKey("InstructorID")
                .ToTable("CourseInstructor"));
        modelBuilder.Entity<Department>().MapToStoredProcedures();
    }
    

    Este código indica a Entity Framework que use procedimientos almacenados para las operaciones de inserción, actualización y eliminación en la entidad Department.

  2. En la Consola del Administrador de paquetes, escribe el siguiente comando:

    add-migration DepartmentSP

    Abra Migrations\<timestamp>_DepartmentSP.cs para ver el código en el método Up que crea los procedimientos almacenados Insertar, Actualizar y Eliminar:

    public override void Up()
    {
        CreateStoredProcedure(
            "dbo.Department_Insert",
            p => new
                {
                    Name = p.String(maxLength: 50),
                    Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
                    StartDate = p.DateTime(),
                    InstructorID = p.Int(),
                },
            body:
                @"INSERT [dbo].[Department]([Name], [Budget], [StartDate], [InstructorID])
                  VALUES (@Name, @Budget, @StartDate, @InstructorID)
                  
                  DECLARE @DepartmentID int
                  SELECT @DepartmentID = [DepartmentID]
                  FROM [dbo].[Department]
                  WHERE @@ROWCOUNT > 0 AND [DepartmentID] = scope_identity()
                  
                  SELECT t0.[DepartmentID]
                  FROM [dbo].[Department] AS t0
                  WHERE @@ROWCOUNT > 0 AND t0.[DepartmentID] = @DepartmentID"
        );
        
        CreateStoredProcedure(
            "dbo.Department_Update",
            p => new
                {
                    DepartmentID = p.Int(),
                    Name = p.String(maxLength: 50),
                    Budget = p.Decimal(precision: 19, scale: 4, storeType: "money"),
                    StartDate = p.DateTime(),
                    InstructorID = p.Int(),
                },
            body:
                @"UPDATE [dbo].[Department]
                  SET [Name] = @Name, [Budget] = @Budget, [StartDate] = @StartDate, [InstructorID] = @InstructorID
                  WHERE ([DepartmentID] = @DepartmentID)"
        );
        
        CreateStoredProcedure(
            "dbo.Department_Delete",
            p => new
                {
                    DepartmentID = p.Int(),
                },
            body:
                @"DELETE [dbo].[Department]
                  WHERE ([DepartmentID] = @DepartmentID)"
        );    
    }
    
  3. En la Consola del Administrador de paquetes, escribe el siguiente comando:

    update-database

  4. Ejecuta la aplicación en modo de depuración, haz clic en la pestaña Departamentos y, a continuación, haz clic en Crear nuevo.

  5. Escribe los datos de un nuevo departamento y, a continuación, haz clic en Crear.

  6. En Visual Studio, examina los registros de la ventana Salida para ver que se usó un procedimiento almacenado para insertar la nueva fila Departamento.

    Department Insert SP

Code First crea nombres de procedimiento almacenado predeterminados. Si usas una base de datos existente, es posible que tengas que personalizar los nombres de procedimiento almacenados para usar procedimientos almacenados ya definidos en la base de datos. Para obtener información sobre cómo hacerlo, consulta Procedimientos almacenados Insertar/Actualizar/Eliminar de Code First en Entity Framework.

Si quieres personalizar lo que hacen los procedimientos almacenados generados, puedes editar el código con scaffolding para el método Up de migraciones que crea el procedimiento almacenado. De este modo, los cambios se reflejan siempre que se ejecute esa migración y se aplicarán a la base de datos de producción cuando las migraciones se ejecuten automáticamente en producción después de la implementación.

Si quieres cambiar un procedimiento almacenado existente que se creó en una migración anterior, puedes usar el comando Add-Migration para generar una migración en blanco y, a continuación, escribir manualmente código que llame al método AlterStoredProcedure.

Implementación en Azure

Esta sección requiere que hayas completado la sección opcional Implementación de la aplicación en Azure en el tutorial Migraciones e implementación de esta serie. Si tenías errores de migración que resolviste mediante la eliminación de la base de datos en el proyecto local, omite esta sección.

  1. En Visual Studio, haga clic con el botón derecho en el proyecto, en el Explorador de soluciones y seleccione Publicar en el menú contextual.

  2. Haga clic en Publicar.

    Visual Studio implementa la aplicación en Azure y la aplicación se abre en el explorador predeterminado, que se ejecuta en Azure.

  3. Prueba la aplicación para comprobar que funciona.

    La primera vez que ejecutas una página que acceda a la base de datos, Entity Framework ejecuta todos los métodos Up de migración necesarios para actualizar la base de datos con el modelo de datos actual. Ahora puedes usar todas las páginas web que agregaste desde la última vez que implementaste, incluidas las páginas departamento que agregaste en este tutorial.

Obtención del código

Descargar el proyecto completado

Recursos adicionales

Puede encontrar enlaces a otros recursos de Entity Framework en el ASP.NET Acceso a datos: recursos recomendados.

Pasos siguientes

En este tutorial ha:

  • Aprendido sobre el código asincrónico
  • Creado un controlador de departamento
  • Usado procedimientos almacenados
  • Implementado en Azure

Pasa al siguiente artículo para aprender a controlar conflictos cuando varios usuarios actualizan la misma entidad al mismo tiempo.