LINQ en ASP.NET MVC: Paginación y Búsqueda para grandes volúmenes de datos

En aplicaciones que manejan grandes volúmenes de datos, la paginación y búsqueda eficiente son esenciales para mejorar la experiencia del usuario. La paginación permite dividir los datos en páginas más pequeñas, lo que reduce el tiempo de carga y mejora la usabilidad. La búsqueda eficiente asegura que los usuarios puedan encontrar rápidamente la información que necesitan sin sobrecargar el sistema. En este artículo, exploraremos cómo implementar estas características utilizando LINQ en ASP.NET MVC.

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 los Modelos

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

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
    public virtual Category Category { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual 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=DataBaseName;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.

Para simular una situación de grandes volúmenes de datos en la base de datos de prueba, puedes expandir la inicialización de datos en la clase ApplicationDbContextInitializer con un conjunto más amplio de datos de ejemplo:

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

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

        var products = new List<Product>();

        // Generar una gran cantidad de productos para simular grandes volúmenes de datos
        for (int i = 1; i <= 1000; i++)
        {
            products.Add(new Product
            {
                Name = $"Product {i}",
                Description = $"Description of Product {i}",
                Price = 10.99m + (i * 0.5m),
                CategoryId = categories[i % categories.Count].Id
            });
        }

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

Explicación:

  1. Inicialización de Categorías:

    • Se crean cinco categorías de ejemplo (Electronics, Clothing, Books, Furniture, Sports).
    • Cada categoría se agrega al contexto y se guarda en la base de datos.
  2. Generación de Productos:

    • Se crea una lista products que contendrá una gran cantidad de productos simulados (en este caso, 1000 productos).
    • Se itera sobre un bucle for para generar productos con nombres, descripciones y precios únicos.
    • Cada producto se asigna a una categoría aleatoria utilizando la operación de módulo (%) para asegurar una distribución uniforme.
  3. Persistencia en la Base de Datos:

    • Se agregan todos los productos al contexto de datos (context.Products.Add(p)) y se guardan en la base de datos con 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 con Paginación y Búsqueda en el Controlador

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 ProductViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public string CategoryName { get; set; }
}

public class PagedProductViewModel
{
    public IEnumerable<ProductViewModel> Products { get; set; }
    public int CurrentPage { get; set; }
    public int TotalPages { get; set; }
}

 

Implementación del Controlador

A continuación crearemos el controlador ProductsController.cs que manejará las acciones relacionadas con la paginación y búsqueda de productos en una aplicación ASP.NET MVC. El objetivo es permitir a los usuarios buscar productos por nombre o descripción, y visualizarlos de manera paginada.

public class ProductsController : Controller
{
    private ApplicationDbContext _context;

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

    public ActionResult Index(string searchTerm, int page = 1)
    {
        int pageSize = 10;

        // Construcción de la consulta inicial
        var query = _context.Products.Include(p => p.Category).AsQueryable();

        // Filtrado por término de búsqueda
        if (!string.IsNullOrEmpty(searchTerm))
        {
            query = query.Where(p => p.Name.Contains(searchTerm) || p.Description.Contains(searchTerm));
        }

        // Conteo total de ítems para la paginación
        var totalItems = query.Count();

        // Aplicación de la paginación y proyección a ProductViewModel
        var products = query
            .OrderBy(p => p.Name)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(p => new ProductViewModel
            {
                Id = p.Id,
                Name = p.Name,
                Description = p.Description,
                Price = p.Price,
                CategoryName = p.Category.Name
            })
            .ToList();

        // Creación del modelo de vista para la paginación
        var model = new PagedProductViewModel
        {
            Products = products,
            CurrentPage = page,
            TotalPages = (int)Math.Ceiling((double)totalItems / pageSize)
        };

        return View(model);
    }

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

 

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

En este apartado, vamos a desglosar y explicar en detalle el código LINQ utilizado en el controlador para implementar la paginación y búsqueda eficiente en nuestra aplicación ASP.NET MVC.

  • Inicialización del Contexto de Datos

    private ApplicationDbContext _context; public ProductsController() { _context = new ApplicationDbContext(); }

    Aquí se inicializa una instancia de ApplicationDbContext, que se utilizará para interactuar con la base de datos. Esto se hace en el constructor del controlador.

 

  • Método Index

    public ActionResult Index(string searchTerm, int page = 1)

    El método Index recibe dos parámetros: searchTerm para la búsqueda de productos y page para la paginación. El valor por defecto de page es 1.

 

  • Tamaño de Página

    int pageSize = 10;

    Se define el tamaño de la página, es decir, el número de productos que se mostrarán por página. En este caso, es 10.

 

  • Construcción de la Consulta Inicial

    var query = _context.Products.Include(p => p.Category).AsQueryable();

    Se construye la consulta inicial utilizando _context.Products y se incluye la entidad relacionada Category mediante Include. El método AsQueryable se utiliza para permitir la construcción dinámica de la consulta.

 

  • Aplicación del Filtro de Búsqueda

    if (!string.IsNullOrEmpty(searchTerm)) { query = query.Where(p => p.Name.Contains(searchTerm) || p.Description.Contains(searchTerm)); }

    Si se proporciona un término de búsqueda (searchTerm), se aplica un filtro a la consulta para buscar productos cuyo nombre o descripción contengan el término de búsqueda.

 

  • Obtención del Número Total de Elementos

    var totalItems = query.Count();

    Se obtiene el número total de elementos que coinciden con la consulta, lo que es necesario para calcular el número total de páginas.

 

  • Aplicación de Ordenación, Paginación y Proyección

    var products = query .OrderBy(p => p.Name) .Skip((page - 1) * pageSize) .Take(pageSize) .Select(p => new ProductViewModel { Id = p.Id, Name = p.Name, Description = p.Description, Price = p.Price, CategoryName = p.Category.Name }) .ToList();
    • OrderBy: Ordena los productos por nombre.
    • Skip: Omite un número de productos basado en la página actual y el tamaño de la página ((page - 1) * pageSize).
    • Take: Toma el número de productos especificado por pageSize.
    • Select: Proyecta cada producto en un ProductViewModel.

 

  • Creación del Modelo Paginado

    var model = new PagedProductViewModel { Products = products, CurrentPage = page, TotalPages = (int)Math.Ceiling((double)totalItems / pageSize) };

    Se crea una instancia de PagedProductViewModel que contiene la lista de productos, la página actual y el número total de páginas.

 

  • Retorno de la Vista

    return View(model);

    Finalmente, se devuelve la vista con el modelo paginado.

 

  • Liberación de Recursos

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

    El método Dispose se sobrescribe para liberar los recursos del contexto de datos cuando el controlador se destruye.

 

 

Creación de las Vistas

Vista Index.cshtml

La Vista Index.cshtml permite a los usuarios buscar productos por nombre y navegar a través de las diferentes páginas de resultados de manera eficiente, utilizando los datos proporcionados por el controlador a través del modelo de vista PagedProductViewModel.

@model YourNamespace.ViewModels.PagedProductViewModel

@{
    ViewBag.Title = "Products";
}

<h2>Products</h2>

<form method="get" action="@Url.Action("Index")">
    <input type="text" name="searchTerm" value="@Request.QueryString["searchTerm"]" placeholder="Search..." />
    <button type="submit">Search</button>
</form>

<table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Description</th>
            <th>Price</th>
            <th>Category</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var product in Model.Products)
        {
            <tr>
                <td>@product.Name</td>
                <td>@product.Description</td>
                <td>@product.Price</td>
                <td>@product.CategoryName</td>
            </tr>
        }
    </tbody>
</table>

<div>
    Page @(Model.CurrentPage) of @(Model.TotalPages)
</div>
<div>
    @if (Model.CurrentPage > 1)
    {
        @Html.ActionLink("Previous", "Index", new { page = Model.CurrentPage - 1, searchTerm = Request.QueryString["searchTerm"] })
    }

    @if (Model.CurrentPage < Model.TotalPages)
    {
        @Html.ActionLink("Next", "Index", new { page = Model.CurrentPage + 1, searchTerm = Request.QueryString["searchTerm"] })
    }
</div>

Explicación:

  1. Modelo Utilizado:

    • @model YourNamespace.ViewModels.PagedProductViewModel: Esta línea especifica que la vista está fuertemente tipada con el modelo PagedProductViewModel. Este ViewModel contiene la lista de productos paginados (Products), así como la información de paginación (CurrentPage y TotalPages).
  2. Formulario de Búsqueda:

    • <form method="get" action="@Url.Action("Index")">: Este formulario permite al usuario ingresar un término de búsqueda para buscar productos por nombre.
    • <input type="text" name="searchTerm" value="@Request.QueryString["searchTerm"]" placeholder="Search..." />: El campo de texto donde se ingresa el término de búsqueda. Request.QueryString["searchTerm"] recupera el valor del parámetro de consulta searchTerm de la URL actual.
    • <button type="submit">Search</button>: Botón para enviar el formulario y realizar la búsqueda.
  3. Tabla de Productos:

    • Se utiliza un bucle foreach para iterar a través de la lista de productos (Model.Products) y mostrar cada producto en una fila de la tabla.
    • <td>@product.Name</td>, <td>@product.Description</td>, <td>@product.Price</td>, <td>@product.CategoryName</td>: Celdas que muestran el nombre, descripción, precio y nombre de la categoría de cada producto.
  4. Información de Paginación:

    • Page @(Model.CurrentPage) of @(Model.TotalPages): Muestra la página actual y el total de páginas disponibles para la paginación.
  5. Botones de Paginación:

    • @if (Model.CurrentPage > 1) { ... }: Muestra un enlace "Previous" (Anterior) solo si no estamos en la primera página.
    • @if (Model.CurrentPage < Model.TotalPages) { ... }: Muestra un enlace "Next" (Siguiente) solo si no estamos en la última página.
    • @Html.ActionLink("Previous", "Index", new { page = Model.CurrentPage - 1, searchTerm = Request.QueryString["searchTerm"] }): Genera un enlace para la página anterior que lleva al usuario a la página anterior con el mismo término de búsqueda.
    • @Html.ActionLink("Next", "Index", new { page = Model.CurrentPage + 1, searchTerm = Request.QueryString["searchTerm"] }): Genera un enlace para la página siguiente que lleva al usuario a la siguiente página con el mismo término de búsqueda.

Index.cshtml

 

 

Conclusiones

La paginación y búsqueda eficiente son cruciales en aplicaciones con grandes volúmenes de datos. Implementar estas características en ASP.NET MVC usando LINQ mejora la experiencia del usuario al hacer que las aplicaciones sean más rápidas y fáciles de usar. Utilizar clases ViewModel y métodos eficientes de consultas LINQ asegura que los datos se manejen de manera óptima y se presenten de forma clara y accesible.

 

   EtiquetasASP.NET MVC LINQ Entity Framework

  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