LINQ en ASP.NET MVC: Proyecciones complejas con Select y SelectMany

En el desarrollo de aplicaciones web con ASP.NET MVC y Entity Framework, las consultas LINQ se utilizan ampliamente para manipular y consultar datos. Las proyecciones en LINQ, realizadas mediante Select y SelectMany, son esenciales para transformar datos en formas específicas. En este artículo, exploraremos cómo utilizar estas proyecciones complejas para obtener y presentar datos de manera eficiente y personalizada en nuestras aplicaciones ASP.NET MVC.

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 LinqProjectionsExample.
    • 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 definir las relaciones entre los Modelos de datos y las tablas de la base de datos utilizando Entity Framework.

ApplicationDbContext.cs:

using System.Data.Entity;

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; }

    // Constructor que configura la cadena de conexión.
    public ApplicationDbContext() : base("name=DefaultConnection")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configuraciones adicionales del modelo
        base.OnModelCreating(modelBuilder);
    }
}

 

Configuración de la Cadena de Conexión

Para que Entity Framework pueda conectarse a la base de datos, es necesario definir la cadena de conexión en el archivo Web.config:

<configuration>
  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=LinqProjectionsExample;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <!-- Otras configuraciones -->
</configuration>

Esta cadena de conexión se configura para usar una base de datos local con el nombre LinqProjectionsExample. Puedes ajustar esta configuración según tus necesidades específicas de entorno.

 

 

Crear 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):

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using LinqProjectionsExample.Models;

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 product3 = new Product { Name = "Producto 3", Price = 200 };

        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 },
                new OrderDetail { Product = product3, Quantity = 2, UnitPrice = product3.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 },
                new OrderDetail { Product = product3, Quantity = 1, UnitPrice = product3.Price }
            }
        };

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

        context.SaveChanges();
    }
}

 

 

Consultas LINQ con proyecciones complejas en el Controlador

Para utilizar consultas LINQ con proyecciones complejas, crearemos un controlador ReportsController que maneje las acciones necesarias para mostrar los resultados en las vistas correspondientes.

 

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 DateTime OrderDate { get; set; }
    public List<OrderDetailViewModel> OrderDetails { get; set; }
}

OrderDetailViewModel.cs:

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

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

 

Crear el Controlador

  • En la carpeta Controllers, crea un nuevo controlador llamado ReportsController.
  • Añade las acciones CustomerOrders que realizará las consultas LINQ y devolverá los datos necesarios para las vistas correspondientes.

ReportsController.cs:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web.Mvc;
using LinqProjectionsExample.Models;

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

    // GET: Reports/CustomerOrders
    public ActionResult CustomerOrders()
    {
        // Consulta LINQ con proyecciones usando Select y SelectMany
        var query = db.Customers
                      .Select(c => new CustomerOrdersViewModel
                      {
                          CustomerName = c.Name,
                          OrderDate = c.Orders.Max(o => o.OrderDate),
                          OrderDetails = c.Orders.SelectMany(o => o.OrderDetails)
                                                 .Select(od => new OrderDetailViewModel
                                                 {
                                                     ProductName = od.Product.Name,
                                                     Quantity = od.Quantity,
                                                     UnitPrice = od.UnitPrice,
                                                     TotalPrice = od.Quantity * od.UnitPrice
                                                 }).ToList()
                      }).ToList();

        return View(query);
    }
}

 

Explicación Detallada del Código LINQ

Vamos a profundizar en la consulta LINQ utilizada en la acción CustomerOrders del controlador ReportsController.

1. Selección Inicial de Clientes:

var query = db.Customers

Esta línea selecciona todos los clientes de la base de datos utilizando el contexto de datos db.

2. Proyección de Clientes con Select:

.Select(c => new CustomerOrdersViewModel
{
    CustomerName = c.Name,
    OrderDate = c.Orders.Max(o => o.OrderDate),
    OrderDetails = c.Orders.SelectMany(o => o.OrderDetails)
                           .Select(od => new OrderDetailViewModel
                           {
                               ProductName = od.Product.Name,
                               Quantity = od.Quantity,
                               UnitPrice = od.UnitPrice,
                               TotalPrice = od.Quantity * od.UnitPrice
                           }).ToList()
})

En esta parte de la consulta, utilizamos el operador Select para proyectar cada cliente (c) en un nuevo objeto CustomerOrdersViewModel.

  • CustomerName: Se asigna directamente desde c.Name, que es el nombre del cliente.
  • OrderDate: Utilizamos el método Max para obtener la fecha más reciente de las órdenes del cliente (c.Orders.Max(o => o.OrderDate)).

3. Uso de SelectMany para Detalles de Órdenes:

.OrderDetails = c.Orders.SelectMany(o => o.OrderDetails)

El operador SelectMany se utiliza para aplanar la colección de órdenes (Orders) de cada cliente en una colección única de detalles de órdenes (OrderDetails). Esto permite acceder a todos los detalles de las órdenes de un cliente en una sola colección.

4. Proyección de Detalles de Órdenes con Select:

.Select(od => new OrderDetailViewModel
{
    ProductName = od.Product.Name,
    Quantity = od.Quantity,
    UnitPrice = od.UnitPrice,
    TotalPrice = od.Quantity * od.UnitPrice
}).ToList()

Dentro de SelectMany, utilizamos Select para proyectar cada detalle de orden (od) en un nuevo objeto OrderDetailViewModel.

  • ProductName: Se asigna desde od.Product.Name, que es el nombre del producto.
  • Quantity: Se asigna desde od.Quantity, que es la cantidad de producto ordenado.
  • UnitPrice: Se asigna desde od.UnitPrice, que es el precio unitario del producto.
  • TotalPrice: Se calcula multiplicando la cantidad (Quantity) por el precio unitario (UnitPrice).

5. Conversión a Lista:

}).ToList()

Finalmente, convertimos la colección proyectada de detalles de órdenes en una lista utilizando ToList(). Esto asegura que los resultados sean devueltos como una lista de OrderDetailViewModel dentro de cada CustomerOrdersViewModel.

 

 

Creación de las Vistas

Las vistas se utilizan para mostrar los resultados de las consultas en el navegador. A continuación, se muestra cómo crear la vista para mostrar los pedidos de los clientes.

 

Crear Vista para CustomerOrders:

  • Haz clic derecho en la acción CustomerOrders del ReportsController y selecciona Add View.
  • Configura la vista como una vista fuertemente tipada con el modelo IEnumerable<CustomerOrdersViewModel>.

CustomerOrders.cshtml:

@model IEnumerable<LinqProjectionsExample.Models.CustomerOrdersViewModel>

<h2>Customer Orders</h2>

<table class="table">
    <thead>
        <tr>
            <th>Customer Name</th>
            <th>Order Date</th>
            <th>Product Name</th>
            <th>Quantity</th>
            <th>Unit Price</th>
            <th>Total Price</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            foreach (var detail in item.OrderDetails)
            {
                <tr>
                    <td>@item.CustomerName</td>
                    <td>@item.OrderDate.ToShortDateString()</td>
                    <td>@detail.ProductName</td>
                    <td>@detail.Quantity</td>
                    <td>@detail.UnitPrice.ToString("C")</td>
                    <td>@detail.TotalPrice.ToString("C")</td>
                </tr>
            }
        }
    </tbody>
</table>

 

 

Conclusión

En este artículo, hemos explorado cómo utilizar Select y SelectMany en consultas LINQ para crear proyecciones complejas en ASP.NET MVC. Estas técnicas permiten transformar y aplanar datos de manera eficiente, facilitando la presentación de información detallada y personalizada en aplicaciones web. Al dominar estas herramientas, puedes mejorar significativamente la gestión y visualización de datos en tus proyectos ASP.NET MVC.

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