LINQ en ASP.NET MVC: Agrupaciones y Subconsultas complejas

En el desarrollo de aplicaciones web utilizando ASP.NET MVC y Entity Framework, LINQ (Language Integrated Query) se convierte en una herramienta esencial para la manipulación y consulta de datos. En este artículo, exploraremos cómo utilizar agrupaciones y subconsultas con LINQ para realizar operaciones complejas en datos relacionales en aplicaciones ASP.NET MVC. A través de ejemplos prácticos y detallados, aprenderemos cómo estructurar consultas avanzadas para generar reportes y obtener información específica de nuestra base de datos.

Creación del Proyecto en Visual Studio

Comencemos creando un nuevo proyecto en Visual Studio para ASP.NET MVC:

  1. Crear Nuevo Proyecto:

    • Abre Visual Studio y selecciona File > New > Project.
    • Escoge ASP.NET Web Application (.NET Framework) como el tipo de proyecto.
    • Asigna un nombre al proyecto, como LinqGroupingAndSubqueries.
    • Selecciona la plantilla MVC y haz clic en OK para crear el proyecto.
  2. Instalación de Entity Framework:

    • Abre Package Manager Console desde Tools > NuGet Package Manager > Package Manager Console.
    • Ejecuta el siguiente comando para instalar Entity Framework
Install-Package EntityFramework

 

 

Definición de Modelos y Configuración de DbContext

En la carpeta Models de tu proyecto, define los modelos de datos que utilizarás y configura el contexto de datos (DbContext) para interactuar con la base de datos.

Customer.cs:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Order> Orders { get; set; }
}

Order.cs:

public class Order
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public int CustomerId { get; set; }
    public Customer Customer { get; set; }
    public ICollection<OrderDetail> OrderDetails { get; set; }
}

OrderDetail.cs:

public class OrderDetail
{
    public int Id { get; set; }
    public int OrderId { get; set; }
    public int ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public Order Order { get; set; }
    public Product Product { get; set; }
}

Product.cs:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

 

Configuración del Contexto de Datos

Crea una clase ApplicationDbContext que extienda DbContext para configurar la conexión y las relaciones entre las tablas de la base de datos utilizando Entity Framework.

public class ApplicationDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Order> Orders { get; set; }
    public DbSet<OrderDetail> OrderDetails { get; set; }
    public DbSet<Product> Products { get; set; }

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

 

Configuración de la Cadena de Conexión

Abre el archivo Web.config y añade la cadena de conexión dentro de <configuration><connectionStrings>:

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

 

Creación de Clases ViewModel

Antes de implementar las consultas LINQ en el controlador, definiremos las clases ViewModel que se utilizarán para proyectar los resultados de las consultas.

CustomerOrdersViewModel.cs:

public class CustomerOrdersViewModel
{
    public string CustomerName { get; set; }
    public int TotalOrders { get; set; }
    public int TotalProducts { get; set; }
}

OrderDetailViewModel.cs:

public class OrderDetailViewModel
{
    public int OrderId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal TotalPrice { get; set; }
}

Estas clases ViewModel se utilizan para proyectar los resultados de las consultas LINQ en formas específicas que se puedan mostrar en las vistas.

 

 

Poblar la Simulación de Base de Datos

Para simular datos de prueba en la base de datos, utilizaremos el método Seed dentro de la clase de configuración de migraciones de Entity Framework. A continuación, se muestra cómo agregar más datos de ejemplo para clientes, órdenes y detalles de órdenes.

Configuration.cs (en la carpeta Migrations):

internal sealed class Configuration : DbMigrationsConfiguration<ApplicationDbContext>
{
    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    }

    protected override void Seed(ApplicationDbContext context)
    {
        var customer1 = new Customer { Name = "Cliente A" };
        var customer2 = new Customer { Name = "Cliente B" };

        var product1 = new Product { Name = "Producto 1", Price = 100 };
        var product2 = new Product { Name = "Producto 2", Price = 150 };

        var order1 = new Order
        {
            Customer = customer1,
            OrderDate = DateTime.Now,
            OrderDetails = new List<OrderDetail>
            {
                new OrderDetail { Product = product1, Quantity = 2, UnitPrice = product1.Price },
                new OrderDetail { Product = product2, Quantity = 1, UnitPrice = product2.Price }
            }
        };

        var order2 = new Order
        {
            Customer = customer2,
            OrderDate = DateTime.Now.AddDays(-7),
            OrderDetails = new List<OrderDetail>
            {
                new OrderDetail { Product = product1, Quantity = 3, UnitPrice = product1.Price }
            }
        };

        var order3 = new Order
        {
            Customer = customer1,
            OrderDate = DateTime.Now.AddDays(-14),
            OrderDetails = new List<OrderDetail>
            {
                new OrderDetail { Product = product2, Quantity = 1, UnitPrice = product2.Price }
            }
        };

        context.Customers.AddOrUpdate(customer1, customer2);
        context.Products.AddOrUpdate(product1, product2);
        context.Orders.AddOrUpdate(order1, order2, order3);

        context.SaveChanges();
    }
}

Explicación del Código:

  • Se agregaron más datos de ejemplo (order3) para simular órdenes adicionales para el mismo cliente (customer1).
  • Esto asegura que tengamos suficientes datos para probar consultas más complejas y reportes en nuestra aplicación.

 

 

El Controlador: Consultas LINQ con Agrupaciones y Subconsultas

Para utilizar consultas LINQ con agrupaciones y subconsultas, crearemos un controlador ReportsController que maneje las acciones necesarias para mostrar los resultados en las vistas.

Crear Controlador:

  • En la carpeta Controllers, crea un nuevo controlador llamado ReportsController.
  • Añade las acciones OrdersPerCustomer y CustomerOrderDetails que realizarán las consultas LINQ y devolverán los datos necesarios para las vistas correspondientes.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web.Mvc;
using LinqGroupingAndSubqueries.Models;

public class ReportsController : Controller
{
    private ApplicationDbContext db = new ApplicationDbContext();

    // GET: Reports/OrdersPerCustomer
    public ActionResult OrdersPerCustomer()
    {
        var query = from customer in db.Customers
                    join order in db.Orders on customer.Id equals order.CustomerId
                    group order by customer.Name into customerGroup
                    select new CustomerOrdersViewModel
                    {
                        CustomerName = customerGroup.Key,
                        TotalOrders = customerGroup.Count(),
                        TotalProducts = customerGroup.Sum(o => o.OrderDetails.Sum(od => od.Quantity))
                    };

        return View(query.ToList());
    }

    // GET: Reports/CustomerOrderDetails/5
    public ActionResult CustomerOrderDetails(int customerId)
    {
        var query = from orderDetail in db.OrderDetails
                    where orderDetail.Order.CustomerId == customerId
                    select new OrderDetailViewModel
                    {
                        OrderId = orderDetail.OrderId,
                        ProductName = orderDetail.Product.Name,
                        Quantity = orderDetail.Quantity,
                        UnitPrice = orderDetail.UnitPrice,
                        TotalPrice = orderDetail.Quantity * orderDetail.UnitPrice
                    };

        return View(query.ToList());
    }
}

Explicación del Código del Controlador:

  • OrdersPerCustomer:

    • Utiliza una consulta LINQ para unir las tablas Customers y Orders utilizando join.
    • Agrupa los resultados por el nombre del cliente (customer.Name) usando group by.
    • Calcula el número total de órdenes (Count()) y el total de productos (Sum()) utilizando consultas anidadas (Sum(od => od.Quantity)).
  • CustomerOrderDetails:

    • Utiliza una consulta LINQ para obtener detalles de órdenes específicas para un cliente dado (customerId).
    • Filtra los detalles de órdenes basados en el customerId.
    • Proyecta los resultados en un nuevo objeto OrderDetailViewModel que contiene detalles como el ID de la orden, nombre del producto, cantidad, precio unitario y precio total.

 

 

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

Acción OrdersPerCustomer:

// GET: Reports/OrdersPerCustomer
public ActionResult OrdersPerCustomer()
{
    // Consulta LINQ para obtener el total de órdenes y productos por cliente
    var query = from customer in db.Customers
                join order in db.Orders on customer.Id equals order.CustomerId
                group order by customer.Name into customerGroup
                select new CustomerOrdersViewModel
                {
                    CustomerName = customerGroup.Key,
                    TotalOrders = customerGroup.Count(),
                    TotalProducts = customerGroup.Sum(o => o.OrderDetails.Sum(od => od.Quantity))
                };

    return View(query.ToList());
}

Explicación:

  1. from customer in db.Customers: Esto establece la fuente de datos como la tabla Customers de la base de datos.

  2. join order in db.Orders on customer.Id equals order.CustomerId: Realiza una operación de unión entre las tablas Customers y Orders utilizando la relación entre CustomerId en la tabla Orders y Id en la tabla Customers.

  3. group order by customer.Name into customerGroup: Agrupa los resultados por el nombre del cliente (customer.Name). customerGroup es una colección agrupada de objetos Order para cada cliente.

  4. select new CustomerOrdersViewModel { ... }: Proyecta los resultados de la consulta en un nuevo objeto CustomerOrdersViewModel. En este paso:

    • CustomerName = customerGroup.Key: customerGroup.Key representa el valor por el cual se agruparon los resultados, en este caso, el nombre del cliente.
    • TotalOrders = customerGroup.Count(): Calcula el número total de órdenes para cada cliente agrupado.
    • TotalProducts = customerGroup.Sum(o => o.OrderDetails.Sum(od => od.Quantity)): Calcula el total de productos sumando la cantidad de cada detalle de orden (Quantity) para todas las órdenes de cada cliente.
  5. return View(query.ToList()): Finalmente, se convierte el resultado de la consulta LINQ en una lista (ToList()) y se pasa a la vista correspondiente para su renderización.

 

Acción CustomerOrderDetails:

// GET: Reports/CustomerOrderDetails/5
public ActionResult CustomerOrderDetails(int customerId)
{
    // Consulta LINQ para obtener detalles de órdenes de un cliente específico
    var query = from orderDetail in db.OrderDetails
                where orderDetail.Order.CustomerId == customerId
                select new OrderDetailViewModel
                {
                    OrderId = orderDetail.OrderId,
                    ProductName = orderDetail.Product.Name,
                    Quantity = orderDetail.Quantity,
                    UnitPrice = orderDetail.UnitPrice,
                    TotalPrice = orderDetail.Quantity * orderDetail.UnitPrice
                };

    return View(query.ToList());
}

Explicación:

  1. from orderDetail in db.OrderDetails: Establece la fuente de datos como la tabla OrderDetails de la base de datos.

  2. where orderDetail.Order.CustomerId == customerId: Filtra los resultados para seleccionar solo aquellos detalles de órdenes donde el CustomerId de la orden asociada coincide con el customerId proporcionado como parámetro a la acción.

  3. select new OrderDetailViewModel { ... }: Proyecta los resultados de la consulta en un nuevo objeto OrderDetailViewModel. En este paso:

    • OrderId = orderDetail.OrderId: Obtiene el ID de la orden.
    • ProductName = orderDetail.Product.Name: Obtiene el nombre del producto asociado al detalle de la orden.
    • Quantity = orderDetail.Quantity: Obtiene la cantidad de productos en el detalle de la orden.
    • UnitPrice = orderDetail.UnitPrice: Obtiene el precio unitario del producto en el detalle de la orden.
    • TotalPrice = orderDetail.Quantity * orderDetail.UnitPrice: Calcula el precio total multiplicando la cantidad por el precio unitario.
  4. return View(query.ToList()): Convierte el resultado de la consulta LINQ en una lista (ToList()) y lo pasa a la vista correspondiente para su renderización.

 

 

Creación de Vistas

A continuación, crearemos las vistas correspondientes para mostrar los resultados de las consultas en el navegador.

Vista OrdersPerCustomer.cshtml (ubicada en Views/Reports):

@model IEnumerable<LinqGroupingAndSubqueries.Models.CustomerOrdersViewModel>

<h2>Orders Per Customer</h2>

<table class="table">
    <thead>
        <tr>
            <th>Customer Name</th>
            <th>Total Orders</th>
            <th>Total Products</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.CustomerName</td>
                <td>@item.TotalOrders</td>
                <td>@item.TotalProducts</td>
            </tr>
        }
    </tbody>
</table>

Explicación de la Vista:

  • @model IEnumerable<YourNamespace.Models.CustomerOrdersViewModel>: Especifica el modelo de vista que se utilizará en la vista. En este caso, CustomerOrdersViewModel es el modelo que contiene los datos a mostrar en la tabla.

 

Vista CustomerOrderDetails.cshtml (ubicada en Views/Reports):

@model IEnumerable<LinqGroupingAndSubqueries.Models.OrderDetailViewModel>

<h2>Customer Order Details</h2>

<table class="table">
    <thead>
        <tr>
            <th>Order ID</th>
            <th>Product Name</th>
            <th>Quantity</th>
            <th>Unit Price</th>
            <th>Total Price</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.OrderId</td>
                <td>@item.ProductName</td>
                <td>@item.Quantity</td>
                <td>@item.UnitPrice.ToString("C")</td>
                <td>@item.TotalPrice.ToString("C")</td>
            </tr>
        }
    </tbody>
</table>

Explicación de la Vista:

  • @model IEnumerable<YourNamespace.Models.OrderDetailViewModel>: Especifica el modelo de vista que se utilizará en la vista. En este caso, OrderDetailViewModel contiene los detalles específicos de cada producto en la orden.

 

 

Conclusiones

En este artículo, hemos explorado cómo utilizar agrupaciones y subconsultas con LINQ en ASP.NET MVC para manipular datos de manera eficiente y realizar consultas avanzadas en aplicaciones web. Estas técnicas son fundamentales para manejar datos relacionales complejos y generar reportes detallados para usuarios finales. Al dominar estas herramientas, podrás desarrollar aplicaciones web más robustas y eficientes, mejorando así la experiencia del usuario y optimizando la gestión de datos en tus proyectos.

Con este conocimiento, estás preparado para aplicar estas técnicas en tus propios proyectos ASP.NET MVC y aprovechar al máximo las capacidades de LINQ y Entity Framework para consultas avanzadas.

 

   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