Empezando con linq

Configuración

LINQ requiere .NET 3.5 o superior (o .NET 2.0 usando LINQBridge).

Agregue una referencia a System.Core, si aún no se ha agregado.

En la parte superior del archivo, importe el espacio de nombres:

  • C#
  using System;
  using System.Linq;
  • VB.NET
  Imports System.Linq

Las diferentes uniones en LINQ

En los siguientes ejemplos, usaremos las siguientes muestras:

List<Product> Products = new List<Product>()
{
  new Product()
  {
    ProductId = 1,
    Name = "Book nr 1",
    Price = 25
  },
  new Product()
  {
    ProductId = 2,
    Name = "Book nr 2",
    Price = 15
  },
  new Product()
  {
    ProductId = 3,
    Name = "Book nr 3",
    Price = 20
  },
};
List<Order> Orders = new List<Order>()
{
  new Order()
  {
    OrderId = 1,
    ProductId = 1,
  },
  new Order()
  {
    OrderId = 2,
    ProductId = 1,
  },
  new Order()
  {
    OrderId = 3,
    ProductId = 2,
  },
  new Order()
  {
    OrderId = 4,
    ProductId = NULL,
  },
};

UNIR INTERNAMENTE

Sintaxis de consulta

var joined = (from p in Products
              join o in Orders on p.ProductId equals o.ProductId
              select new
              {
                o.OrderId,
                p.ProductId,
                p.Name
              }).ToList();

Sintaxis del método

var joined = Products.Join(Orders, p => p.ProductId, 
                                   o => o.OrderId, 
                                     => new 
                                    { 
                                      OrderId   = o.OrderId, 
                                      ProductId = p.ProductId, 
                                      Name      = p.Name 
                                    })
                     .ToList();

Resultado:

{ 1, 1, "Book nr 1" },
{ 2, 1, "Book nr 1" },
{ 3, 2, "Book nr 2" }

IZQUIERDA COMBINACIÓN EXTERNA

var joined = (from p in Products
              join o in Orders on p.ProductId equals o.ProductId into g
              from lj in g.DefaultIfEmpty()
              select new
              {
                //For the empty records in lj, OrderId would be NULL
                OrderId = (int?)lj.OrderId,
                p.ProductId,
                p.Name
              }).ToList();

Resultado:

{ 1, 1, "Book nr 1" },
{ 2, 1, "Book nr 1" },
{ 3, 2, "Book nr 2" },
{ NULL, 3, "Book nr 3" }

** UNIÓN CRUZADA **

var joined = (from p in Products
              from o in Orders
              select new
              {
                o.OrderId,
                p.ProductId,
                p.Name
              }).ToList();

Resultado:

{ 1, 1, "Book nr 1" },
{ 2, 1, "Book nr 1" },
{ 3, 2, "Book nr 2" },
{ NULL, 3, "Book nr 3" },
{ 4, NULL, NULL }

ÚNETE AL GRUPO

var joined = (from p in Products
              join o in Orders on p.ProductId equals o.ProductId
                into t
              select new
              {
                p.ProductId,
                p.Name,
                Orders = t
              }).ToList();

La propiedad Orders ahora contiene un IEnumerable<Order> con todas las órdenes vinculadas.

Resultado:

{ 1, "Book nr 1", Orders = { 1, 2 } },
{ 2, "Book nr 2", Orders = { 3 } },
{ 3, "Book nr 3", Orders = { } },

Cómo unirse en múltiples condiciones

Al unirse con una sola condición, puede usar:

join o in Orders 
  on p.ProductId equals o.ProductId

Al unirse en múltiples, use:

join o in Orders 
  on new { p.ProductId, p.CategoryId } equals new { o.ProductId, o.CategoryId }

Asegúrese de que ambos objetos anónimos tengan las mismas propiedades, y en VB.NET, deben estar marcados como Clave, aunque VB.NET permite múltiples cláusulas Equals separadas por Y:

Join o In Orders 
  On p.ProductId Equals o.ProductId And p.CategoryId Equals o.CategoryId

Sintaxis de consultas y sintaxis de métodos

La sintaxis de consulta y la sintaxis de método son semánticamente idénticas, pero muchas personas encuentran que la sintaxis de consulta es más simple y fácil de leer. Digamos que necesitamos recuperar todos los elementos pares ordenados en orden ascendente de una colección de números.

C#:

int[] numbers = { 0, 1, 2, 3, 4, 5, 6 };

// Query syntax:
IEnumerable<int> numQuery1 =
            from num in numbers
            where num % 2 == 0
            orderby num
            select num;

// Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

VB.NET:

Dim numbers() As Integer = { 0, 1, 2, 3, 4, 5, 6 }

' Query syntax: '
Dim numQuery1 = From num In numbers
                 Where num Mod 2 = 0
                 Select num
                 Order By num

' Method syntax: '
Dim numQuery2 = numbers.where(Function(num) num Mod 2 = 0).OrderBy(Function(num) num)

Recuerde que algunas consultas deben expresarse como llamadas a métodos. Por ejemplo, debe usar una llamada de método para expresar una consulta que recupera la cantidad de elementos que coinciden con una condición específica. También debe usar una llamada de método para una consulta que recupera el elemento que tiene el valor máximo en una secuencia de origen. Así que eso podría ser una ventaja de usar la sintaxis del método para hacer que el código sea más consistente. Sin embargo, por supuesto, siempre puede aplicar el método después de una llamada de sintaxis de consulta:

C#:

int maxNum =
    (from num in numbers
     where num % 2 == 0
     select num).Max();

VB.NET:

Dim maxNum =
    (From num In numbers
     Where num Mod 2 = 0
     Select num).Max();

Métodos LINQ e IEnumerable frente a IQueryable

Los métodos de extensión LINQ en IEnumerable<T> toman métodos reales1, ya sean métodos anónimos:

//C#
Func<int,bool> fn = x => x > 3;
var list = new List<int>() {1,2,3,4,5,6};
var query = list.Where(fn);

'VB.NET
Dim fn = Function(x As Integer) x > 3
Dim list = New List From {1,2,3,4,5,6};
Dim query = list.Where(fn);

o métodos con nombre (métodos definidos explícitamente como parte de una clase):

//C#
class Program {
    bool LessThan4(int x) {
        return x < 4;
    }

    void Main() {
        var list = new List<int>() {1,2,3,4,5,6};
        var query = list.Where(LessThan4);
    }
}

'VB.NET
Class Program
    Function LessThan4(x As Integer) As Boolean
        Return x < 4
    End Function
    Sub Main
        Dim list = New List From {1,2,3,4,5,6};
        Dim query = list.Where(AddressOf LessThan4)
    End Sub
End Class

En teoría, es posible analizar el IL del método, descubrir qué intenta hacer el método y aplicar la lógica de ese método a cualquier fuente de datos subyacente, no solo objetos en la memoria. Pero analizar IL no es para los débiles de corazón.


Afortunadamente, .NET proporciona la interfaz IQueryable<T> y los métodos de extensión en System.Linq.Queryable para este escenario. Estos métodos de extensión toman un árbol de expresión — una estructura de datos que representa código — en lugar de un método real, que el proveedor de LINQ puede analizar2 y convertir a una forma más adecuada para consultar la fuente de datos subyacente. Por ejemplo:

//C#
IQueryable<Person> qry = PersonsSet();

// Since we're using a variable of type Expression<Func<Person,bool>>, the compiler 
// generates an expression tree representing this code
Expression<Func<Person,bool>> expr = x => x.LastName.StartsWith("A");
// The same thing happens when we write the lambda expression directly in the call to 
// Queryable.Where

qry = qry.Where(expr);


'VB.NET
Dim qry As IQueryable(Of Person) = PersonSet()

' Since we're using a variable of type Expression(Of Func(Of Person,Boolean)), the compiler 
' generates an expression tree representing this code
Dim expr As Expression(Of Func(Of Person, Boolean)) = Function(x) x.LastName.StartsWith("A")
' The same thing happens when we write the lambda expression directly in the call to 
' Queryable.Where

qry = qry.Where(expr)

Si (por ejemplo) esta consulta es contra una base de datos SQL, el proveedor podría convertir esta expresión en la siguiente declaración SQL:

SELECT *
FROM Persons
WHERE LastName LIKE N'A%'

y ejecutarlo contra la fuente de datos.

Por otro lado, si la consulta es contra una API REST, el proveedor podría convertir la misma expresión en una llamada a la API:

http://www.example.com/person?filtervalue=A&filtertype=startswith&fieldname=lastname

Hay dos ventajas principales al adaptar una solicitud de datos en función de una expresión (en lugar de cargar toda la colección en la memoria y consultar localmente):

  • La fuente de datos subyacente a menudo puede consultar de manera más eficiente. Por ejemplo, puede muy bien haber un índice en LastName. Cargar los objetos en la memoria local y consultar en la memoria pierde esa eficiencia.
  • Los datos se pueden moldear y reducir antes de transferirlos. En este caso, la base de datos o el servicio web solo necesita devolver los datos coincidentes, a diferencia del conjunto completo de Personas disponibles en la fuente de datos.

Notas 1. Técnicamente, en realidad no toman métodos, sino [instancias delegadas que apuntan a métodos] (http://programmers.stackexchange.com/a/314086/100120). Sin embargo, esta distinción es irrelevante aquí. 2. Este es el motivo de errores [como](http ://stackoverflow.com/q/10110266) “LINQ to Entities no reconoce el método ‘System.String ToString()’, y este método no se puede traducir a una expresión de tienda.”. El proveedor de LINQ (en este caso, el proveedor de Entity Framework) no sabe cómo analizar y traducir una llamada a ToString a SQL equivalente.