LINQ en ASP.NET MVC: Consultas Asíncronas para mejorar el rendimiento

En aplicaciones web, especialmente en aquellas que manejan grandes volúmenes de datos o realizan múltiples consultas a bases de datos, es crucial optimizar el rendimiento y la capacidad de respuesta. Una técnica efectiva para lograr esto es mediante el uso de consultas LINQ asíncronas. Las consultas asíncronas permiten liberar el hilo de la aplicación mientras se espera la respuesta de la base de datos, mejorando así la escalabilidad y la experiencia del usuario.

Creación del Proyecto en Visual Studio

  • Abrir Visual Studio: Inicia Visual Studio y selecciona Crear un nuevo proyecto.
  • Seleccionar Plantilla: Elige Aplicación Web ASP.NET (.NET Framework) y haz clic en Siguiente.
  • Configurar Proyecto: Introduce el nombre del proyecto, selecciona la ubicación y asegúrate de que el Framework sea .NET Framework 4.7.2 o superior. Haz clic en Crear.
  • Elegir Plantilla de Proyecto: Selecciona MVC y haz clic en Crear.

Instalación de Entity Framework

Para utilizar Entity Framework, necesitas instalar el paquete NuGet:

  1. Abrir la Consola del Administrador de Paquetes: Ve a Herramientas > Administrador de paquetes NuGet > Consola del Administrador de paquetes.
  2. Instalar Entity Framework: Ejecuta el siguiente comando:
Install-Package EntityFramework

 

 

Definición de Modelos y Configuración de DbContext

Definición de Entidades

Vamos a definir dos entidades: Product y Category con una relación de uno a muchos (Carpeta Models).

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

public class Category
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public ICollection<Product> Products { get; set; }
}

 

Configuración del Contexto de Datos (DbContext)

Crea una clase ApplicationDbContext que herede de DbContext y configura las entidades y sus relaciones.

public class ApplicationDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    public ApplicationDbContext() : base("DefaultConnection")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Category>()
            .HasMany(c => c.Products) // Una categoría puede tener muchos productos
            .WithRequired(p => p.Category) // Un producto requiere una categoría
            .HasForeignKey(p => p.CategoryId); // La clave foránea en Product es CategoryId

        base.OnModelCreating(modelBuilder);
    }
}

Explicación:

  1. DbContext y DbSet:

    • ApplicationDbContext hereda de DbContext, que representa una sesión con la base de datos y permite consultar y guardar instancias de entidades.
    • DbSet<Product> y DbSet<Category> son propiedades que representan las colecciones de entidades Product y Category en la base de datos. Estas propiedades se utilizan para consultar y guardar datos de estas entidades.
  2. Constructor:

    • El constructor de ApplicationDbContext llama al constructor base de DbContext utilizando el nombre de la cadena de conexión "DefaultConnection". Esta cadena de conexión generalmente se define en el archivo de configuración (Web.config o appsettings.json) y especifica la base de datos a la que se conectará el contexto.
  3. OnModelCreating:

    • OnModelCreating es un método protegido que se llama una vez que el modelo de datos (DbModelBuilder) se está configurando.
    • En este método, se utiliza modelBuilder.Entity<Category>() para configurar la entidad Category.
    • .HasMany(c => c.Products) especifica que una categoría puede tener múltiples productos. Esto establece una relación uno a muchos entre Category y Product.
    • .WithRequired(p => p.Category) establece que cada producto requiere una categoría asociada. Esto indica que la relación es obligatoria desde el lado del producto.
    • .HasForeignKey(p => p.CategoryId) especifica que la clave foránea en la tabla Product que referencia a Category es CategoryId.
  4. Llamada a base.OnModelCreating(modelBuilder):

    • Se llama a base.OnModelCreating(modelBuilder) para asegurarse de que cualquier configuración del modelo definida en las clases base de DbContext también se aplique.

 

 

Configuración de la Cadena de Conexión

En el archivo Web.config, configura la cadena de conexión a la base de datos:

<connectionStrings>
    <add name="DefaultConnection" 
         connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=AsyncLINQDemo;Integrated Security=True" 
         providerName="System.Data.SqlClient" />
</connectionStrings>

Explicación:

  • Data Source: Especifica la instancia del servidor de base de datos. En este caso, (LocalDb)\MSSQLLocalDB se refiere a una instancia local de SQL Server Express LocalDB.

  • Initial Catalog: Especifica el nombre de la base de datos a la cual quieres conectarte.

  • Integrated Security: Cuando se establece en True, indica que la autenticación de Windows se utiliza para conectarse a la base de datos. Esto significa que la conexión utilizará las credenciales del usuario actual para autenticarse en SQL Server.

 

 

Crear la Simulación de Base de Datos

Crea una clase ApplicationDbContextInitializer que herede de DropCreateDatabaseAlways<TContext> para poblar la base de datos con datos de ejemplo.

public class ApplicationDbContextInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
{
    protected override void Seed(ApplicationDbContext context)
    {
        var categories = new List<Category>
        {
            new Category { Name = "Electronics" },
            new Category { Name = "Books" },
            new Category { Name = "Clothing" }
        };

        categories.ForEach(c => context.Categories.Add(c));
        context.SaveChanges();

        var products = new List<Product>
        {
            new Product { Name = "Laptop", Price = 999.99m, CategoryId = categories[0].Id },
            new Product { Name = "Smartphone", Price = 699.99m, CategoryId = categories[0].Id },
            new Product { Name = "Science Fiction Novel", Price = 19.99m, CategoryId = categories[1].Id },
            new Product { Name = "Fantasy Novel", Price = 15.99m, CategoryId = categories[1].Id },
            new Product { Name = "T-Shirt", Price = 9.99m, CategoryId = categories[2].Id },
            new Product { Name = "Jeans", Price = 49.99m, CategoryId = categories[2].Id }
        };

        products.ForEach(p => context.Products.Add(p));
        context.SaveChanges();
    }
}

En ASP.NET MVC, puedes usar el MétodoApplication_Start en el archivo global.asax para configurar tu ApplicationDbContextInitializer y asegurarte de que se inicialice la base de datos al iniciar la aplicación.

En el archivo Global.asax, añadimos el inicializador de la base de datos:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        // Configuración de las rutas, filtros y otras configuraciones de la aplicación
        AreaRegistration.RegisterAllAreas();
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        // Configurar el inicializador del contexto de datos
        Database.SetInitializer(new ApplicationDbContextInitializer());
    }
}

Explicación:

  • ApplicationDbContextInitializer: Esta clase hereda de DropCreateDatabaseAlways<TContext>, lo que significa que siempre recreará la base de datos si existe alguna diferencia con el modelo en código. En el método Seed, se agregan categorías ficticias a la base de datos.

  • Global.asax: En Application_Start, se llama a Database.SetInitializer para establecer el ApplicationDbContextInitializer como el inicializador de la base de datos. Esto asegura que cada vez que se inicie la aplicación web, la base de datos se cree (si no existe) y se poblará con los datos especificados en Seed.

 

 

Implementación de Consultas LINQ Asíncronas en el Controlador

Utilizar consultas LINQ asíncronas es fundamental para mejorar el rendimiento de una aplicación ASP.NET MVC. La idea principal es liberar el hilo de la aplicación mientras la base de datos procesa la consulta, permitiendo que el servidor maneje más solicitudes de manera simultánea.

 

Creación de Clases ViewModel

La creación de clases ViewModel en ASP.NET MVC es fundamental para estructurar y presentar datos específicos en las vistas, separando así la lógica de presentación de los modelos de dominio utilizados en el backend.

A continuación crearemos las siguientes clases ViewModel para transferir los datos a las vistas (en una nueva carpeta ViewModels).

public class CategoryViewModel
{
    public string Name { get; set; }
    public IEnumerable<ProductViewModel> Products { get; set; }
}

public class ProductViewModel
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

 

Implementación del Controlador

En el controlador HomeController.cs, implementamos las consultas asíncronas para obtener los datos.

public class HomeController : Controller
{
    private readonly ApplicationDbContext _context;

    public HomeController()
    {
        _context = new ApplicationDbContext();
    }

    public async Task<ActionResult> Index()
    {
        var categories = await _context.Categories
            .Select(c => new CategoryViewModel
            {
                Name = c.Name,
                Products = c.Products.Select(p => new ProductViewModel
                {
                    Name = p.Name,
                    Price = p.Price
                })
            }).ToListAsync();

        return View(categories);
    }

    public async Task<ActionResult> Products()
    {
        var products = await _context.Products
            .Select(p => new ProductViewModel
            {
                Name = p.Name,
                Price = p.Price,
                Category = p.Category.Name
            }).ToListAsync();

        return View(products);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _context.Dispose();
        }
        base.Dispose(disposing);
    }
}

 

Explicación Detallada del Código LINQ en el Controlador

Esta explicación detallada del código LINQ en el controlador proporciona una visión completa de cómo las consultas asíncronas pueden mejorar el rendimiento de las aplicaciones ASP.NET MVC, además de explicar cada parte del código para una mejor comprensión.

 

1. Desglose del Método Index

Declaración del Método

public async Task<ActionResult> Index()
  • async: Indica que el método es asíncrono.
  • Task<ActionResult>: El tipo de retorno es una tarea que produce un resultado de tipo ActionResult.

Consulta LINQ Asíncrona

var categories = await _context.Categories
    .Select(c => new CategoryViewModel
    {
        Name = c.Name,
        Products = c.Products.Select(p => new ProductViewModel
        {
            Name = p.Name,
            Price = p.Price
        })
    }).ToListAsync();
  • await _context.Categories: await se utiliza para esperar la ejecución de la consulta asíncrona. _context.Categories obtiene el conjunto de categorías desde el contexto de datos.
  • .Select(c => new CategoryViewModel { ... }): Proyecta cada categoría en un nuevo objeto CategoryViewModel.
    • Name = c.Name: Asigna el nombre de la categoría.
    • Products = c.Products.Select(p => new ProductViewModel { ... }): Proyecta cada producto de la categoría en un nuevo objeto ProductViewModel.
      • Name = p.Name: Asigna el nombre del producto.
      • Price = p.Price: Asigna el precio del producto.
  • .ToListAsync(): Convierte el resultado de la consulta en una lista de manera asíncrona.

Retorno de la Vista

return View(categories);

Pasa la lista de CategoryViewModel a la vista Index.cshtml.

 

2. Desglose del Método Products

Declaración del Método

public async Task<ActionResult> Products()

Similar al método Index, es un método asíncrono que retorna una tarea de tipo ActionResult.

Consulta LINQ Asíncrona

var products = await _context.Products
    .Select(p => new ProductViewModel
    {
        Name = p.Name,
        Price = p.Price,
        Category = p.Category.Name
    }).ToListAsync();
  • await _context.Products: await se utiliza para esperar la ejecución de la consulta asíncrona. _context.Products obtiene el conjunto de productos desde el contexto de datos.
  • .Select(p => new ProductViewModel { ... }): Proyecta cada producto en un nuevo objeto ProductViewModel.
    • Name = p.Name: Asigna el nombre del producto.
    • Price = p.Price: Asigna el precio del producto.
    • Category = p.Category.Name: Asigna el nombre de la categoría a la que pertenece el producto.
  • .ToListAsync(): Convierte el resultado de la consulta en una lista de manera asíncrona.

Retorno de la Vista

return View(products);

Pasa la lista de ProductViewModel a la vista Products.cshtml.

 

3. El Método Dispose

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        _context.Dispose();
    }
    base.Dispose(disposing);
}
  • Dispose(bool disposing): Método que libera los recursos utilizados por el contexto de datos.
  • _context.Dispose(): Libera explícitamente los recursos del contexto de datos.
  • base.Dispose(disposing): Llama al método base Dispose para asegurar que todos los recursos se liberen correctamente.

 

 

Creación de Vistas

Por último creamos las vistas para mostrar los datos en el navegador.

Vista Index

Creamos la vista Index.cshtml para mostrar las categorías y sus productos.

@model IEnumerable<YourNamespace.ViewModels.CategoryViewModel>

<h2>Categories</h2>
@foreach (var category in Model)
{
    <h3>@category.Name</h3>
    <ul>
    @foreach (var product in category.Products)
    {
        <li>@product.Name - @product.Price.ToString("C")</li>
    }
    </ul>
}

 

Vista Products

Creamos la vista Products.cshtml para mostrar todos los productos.

@model IEnumerable<YourNamespace.ViewModels.ProductViewModel>

<h2>Products</h2>
<table>
    <tr>
        <th>Name</th>
        <th>Price</th>
    </tr>
@foreach (var product in Model)
{
    <tr>
        <td>@product.Name</td>
        <td>@product.Price.ToString("C")</td>
    </tr>
}
</table>

 

 

Beneficios y Buenas Prácticas para el Uso de Consultas Asíncronas en LINQ

El uso de consultas LINQ asíncronas en aplicaciones ASP.NET MVC ofrece varios beneficios:

  1. Mejora del Rendimiento: Las consultas asíncronas permiten liberar el hilo actual mientras se espera la respuesta de la base de datos, mejorando la capacidad de respuesta y el rendimiento general de la aplicación.

  2. Escalabilidad: Las aplicaciones que manejan múltiples solicitudes simultáneamente se benefician enormemente de las operaciones asíncronas, ya que pueden manejar más solicitudes con los mismos recursos.

  3. Experiencia del Usuario: Las operaciones asíncronas mejoran la experiencia del usuario al reducir los tiempos de espera y hacer que la aplicación sea más receptiva.

  4. Facilidad de Implementación: Entity Framework facilita la implementación de consultas asíncronas mediante métodos como ToListAsync(), FirstOrDefaultAsync(), entre otros.

Para aprovechar al máximo las consultas asíncronas, sigue estas mejores prácticas:

  • Usa siempre métodos asíncronos (ToListAsync, FirstOrDefaultAsync, SingleOrDefaultAsync, etc.) para las operaciones de base de datos.
  • Evita el uso de async void a menos que sea absolutamente necesario. Usa async Task en su lugar.
  • Gestiona adecuadamente el ciclo de vida del contexto de datos para evitar problemas de concurrencia y liberar recursos correctamente.

Nota: Por último, te recomiendo utilizar consultas asíncronas siempre que se realicen operaciones de E/S (entrada/salida), especialmente cuando se interactúa con bases de datos, servicios web u otros recursos externos.

 

  Compartir


  Nuevo comentario

El campo Comentario es obligatorio.
El campo Nombre es obligatorio.

  Comentarios

No hay comentarios para este Post.



Utilizamos cookies propias y de terceros para mejorar nuestros servicios y ofrecerle una mejor experiencia de navegación. Si continúa navegando consideramos que acepta su uso. Más información   Acepto