Introducción a LINQ


LINQ (leído “link”, pese a que mucha gente lo denomina “lin-quiú”) es una biblioteca de la plataforma .NET que proporciona acceso a datos de forma nativa a C# o VB.NET.

Basa su existencia en las denominadas expresiones de consulta, que recuerdan a las sentencias SQL pero extienden su funcionalidad de consulta a otros elementos como listas, arrays, clases enumerables, o documentos XML.

Operadores de consulta

LINQ hace uso de un conjunto de operadores, definidos en el espacio de nombres System.Linq, que permiten realizar consultas sobre cualquier conjunto de objetos enumerables, es decir, IEnumerable<T>. Por ejemplo, imaginemos que tenemos un array (que implementa IEnumerable) como el siguiente:

    string[] juegos = {  "Carcassonne", "Bang", "Jungle Speed", "Los colonos de Catán", "Black stories", "Munchkin", "Zombies" };

    IEnumerable<string> consulta = from j in juegos
                                    where j.StartsWith("J")
                                    orderby j ascending
                                    select j;

    foreach( string elemento in consulta)
        Console.WriteLine(elemento);

Como podemos observar, si disponemos de conocimientos básicos de SQL, la consulta LINQ se parece bastante a una consulta SQL: cláusula select, where, from, orderby… Puede que lo único que altere un poco la paz de los que nunca hayan visto esta tecnología sea el orden en el que se declaran las sentencias.

Ejemplo

Construiremos un pequeño proyecto en C# que nos sirva para ilustrar un poco mejor el funcionamiento de LINQ. El ejemplo anterior es bastante ilustrativo, pero únicamente estamos trabajando con cadenas de texto. Cuando operemos con elementos reales, estos serán, por lo menos, objetos, por lo que intentaremos emular un entorno un poco más realista.

Comenzaremos, por tanto, generando una clase que contenga un conjunto de atributos como el siguiente:

    public class Juego
    {
        public int Id { get; set; }
        public string Nombre { get; set; }
        public int MinJugadores { get; set; }
        public int MaxJugadores { get; set; }
    }

Una vez en la clase principal del programa, crearemos una lista estática de objetos de la clase Juego, como por ejemplo:

        private static IEnumerable<Juego> juegos = new List<Juego>()
        {
            new Juego { Id=1, Nombre="Carcassonne", MinJugadores=2, MaxJugadores=8 },
            new Juego { Id=2, Nombre="Los colonos de Catán", MinJugadores=2, MaxJugadores=8 },
            new Juego { Id=3, Nombre="Jungle Speed", MinJugadores=1, MaxJugadores=4 },
            new Juego { Id=4, Nombre="Black Stories", MinJugadores=2, MaxJugadores=100 },
            new Juego { Id=5, Nombre="Munchkin", MinJugadores=3, MaxJugadores=10 }
        };

A continuación, crearemos un método que devuelva todos aquellos juegos que puedan ser jugados por más de cuatro personas ordenados ascendentemente.

        private static List<Juego> consultar()
        {
            List<Juego> consulta = from j in juegos
                                   where j.MaxJugadores > 4
                                   orderby j.Nombre ascending
                                   select j;

            return consulta;
        }

Finalmente, en el método Main() realizamos la invocación del método y mostramos su contenido por pantalla.

        static void Main(string[] args)
        {
            List<Juego> resultado = consultar();
            foreach (Juego j in resultado)
                Console.WriteLine(string.Format("{0} ({1}). De {2} a {3} jugadores.", j.Nombre, j.Id, j.MinJugadores, j.MaxJugadores));
            Console.ReadLine();
        }

El resultado será el siguiente:

SQL invertido y la cláusula SELECT

Probablemente, tras ver este ejemplo, la duda de por qué LINQ se parece tanto a una sentencia SQL invertida seguramente siga vigente. Sin embargo, el motivo no es aleatorio: la primera sentencia es un from, en el que le indicamos una variable de rango (j) y una fuente de datos (juegos). De este modo, nuestro IDE ya sabe, en primer lugar, de dónde pretendemos extraer los datos, por lo que podrá facilitarnos los siguientes pasos de la consulta a través del Intellisense y será capaz de inducir los elementos relativos al filtro (where) o a la ordenación (orderby). Como podemos imaginar, esto no sería posible si la primera sentencia fuera select.

El operador select, sin embargo, no se limita a realizar una consulta sobre la fuente de datos. La cláusula select generará, uno a uno, cada uno de los elementos que conformarán la consulta definida antes del operador from. Por lo tanto, debemos romper con la primera impresión (que todos en su día tuvimos al comenzar a hacer uso de LINQ) de que select únicamente recupera objetos de una fuente de datos. La sentencia select, además de referenciar, tiene también la capacidad de crear los objetos que se ciñen a la consulta.

Dicho así quizás no resulte muy claro. Dejemos que un ejemplo hable por nosotros. Crearemos una nueva clase llamada JuegoMultitudinario que modelará un juego de más de 4 jugadores.

    public class JuegoMultitudinario
    {
        public int Id { get; set; }
        public string Nombre { get; set; }
        public DateTime FechaCreacion { get; set; }
    }

 

Ahora crearemos un método que genere, de forma dinámica, un IEnumerable<JuegoMultitudinario> (lista de objetos JuegoMultitudinario) a partir de la consulta anterior:

        private static IEnumerable<JuegoMultitudinario> consultarJuegosMultitudinarios()
        {
            IEnumerable<JuegoMultitudinario> consulta = from j in juegos
                                                        where j.MaxJugadores > 4
                                                        select new JuegoMultitudinario { Id = j.Id, Nombre = j.Nombre, FechaCreacion = DateTime.Now };

            return consulta;
        }

Como podemos observar, la consulta iterará sobre el listado de objetos de la clase Juego generando como resultado un listado de objectos de la clase JuegoMultitudinario creados dinámicamente. Por tanto, no es necesario que select devuelva datos enlazados directamente al objeto sobre el que está iterando, sino que puede hacer uso de esos datos para crear nuevos objetos que pueden o no estar relacionados.

Ejecución diferida

Podemos pensar, observando el código anterior, que en el momento en el que se define la sentencia LINQ se realizará el conjunto de proyecciones y filtros asociados a la consulta. Nada más lejos de la realidad.

La declaración de una consulta LINQ únicamente define la consulta, pero no la ejecuta. La ejecución de la consulta se postergará hasta el momento en el que sea necesaria, generalmente cuando se hace uso de la variable de rango definida en la consulta. En el caso que mostramos arriba, la ejecución se producirá en el bucle foreach que recorre los juegos para mostrarlos por pantalla.

No obstante, existen excepciones para este comportamiento, como por ejemplo el operador count. Los operadores que realizan una ejecución diferida (la mayor parte) se denominan Lazy, y comprenden algunos de los operadores más utilizados, como select, where u orderby. Aquellos que ejecutan automáticamente la sentencia, como count, reciben el nombre de Greedy.

Por supuesto, los operadores Lazy siempre serán preferibles a los Greedy, ya que no hacen uso de los datos hasta que éstos son realmente necesarios. Un buen ejemplo sería el siguiente, en el que modificaríamos el código del método Main() añadiendo un nuevo elemento a la fuente de datos (que declararemos de tipo List para poder añadir nuevos elementos mediante el método Add) justo después de haber ejecutado la sentencia LINQ.

        static void Main(string[] args)
        {
            // Definimos el resultado
            IEnumerable<Juego> resultado = consultar();

            // Modificamos la fuente de datos después de definir la sentencia LINQ
            juegos.Add(new Juego { Id = 6, Nombre = "Arkham Horror", MinJugadores = 4, MaxJugadores = 8 });

            // Iteramos sobre la variable de rango, forzando la consulta.
            foreach (Juego j in resultado)
                Console.WriteLine(string.Format("{0} ({1}). De {2} a {3} jugadores.", j.Nombre, j.Id, j.MinJugadores, j.MaxJugadores));
            Console.ReadLine();
        }

Sin conocer nada acerca de la ejecución demorada, el resultado nos podría parecer antiintuitivo, pero ahora que conocemos la razón, comprenderemos con facilidad por qué el elemento que añadimos a posteriori se muestra en el conjunto: la consulta se lanza al alcanzar el bucle foreach, momento en el cual el nuevo elemento ya ha sido insertado en la fuente de datos.

Si LINQ parece potente es porque realmente lo es. Próximamente hablaremos acerca de las bondades y maravillas de esta tecnología en conjunción con XML, donde LINQ simplifica enormemente las tareas de generación (y consumo) de este tipo de documentos.

13 comments

  1. Hola, En el ejemplo de “Ejecución diferida”, donde pones “juegos.Add(new Juego { Id = 6, Nombre = “Arkham Horror”, MinJugadores = 4, Ma ….” creo que queríás poner “resultado.Add(new Juego { Id = 6, Nombre = “Arkham Horror”, MinJugadores = 4, Ma…”. Es decir, la colección sobre la que añades el nuevo objeto no es “juegos”, si no “resultado ”

    De todas las maneras, muy bien explicado 😉

  2. Hola, Jorge, muchas gracias 🙂

    En realidad la colección a la que pretendo añadir el juego es a “juegos”, no a “resultado”.

    Recuerda que la sentencia LINQ no se ejecutará hasta que no se haga uso de ella: “resultado” no será una colección en sí hasta que se ejecute el foreach que accede a ella.

    Por lo tanto, lo que está ocurriendo en ese código es:
    – Definimos “resultado” como una consulta LINQ que recuperará el contenido de “juegos”. Debido a la ejecución diferida, resultado aún no almacenará ningún dato.
    – Añadimos un nuevo elemento a “juegos”.
    – Recorremos “resultado”. Es en ese momento, y no antes, cuando LINQ rellenará “resultado” accediendo a “juegos” para recuperar sus elementos. Y eso incluye el último registro (Arkham Horror) que insertamos antes de ejecutar el foreach.

    Espero haber aclarado un poco el ejemplo 🙂

    1. Si, ya lo cogí. es que no me había dado que el objeto “juego” estaba declarado en más arriba en otro cuadro de código. Gracias. Me están gustando mucho tus artículos de LinQ. Son completos, muy bien explicados y al grano.

  3. Hola, muchas gracias por compartir el conocimiento de una manera tan didáctica.

    Soy nuevo en este lenguaje y tratando de reproducir el primer ejemplo me encuentro con el siguiente error: No se puede convertir implícitamente el tipo ‘System.Linq.IOrderedEnumerable’ en ‘System.Collections.Generic.List’. Ya existe una conversión explícita (compruebe si le falta una conversión)

    Podrías ayudarme a comprender el problema, de antemano, gracias.

    Juan Carlos

  4. Juan Carlos Díazgranados a mi me ocurrió algo parecido, utilicé Expresiones Lambda en lugar de como lo está haciendo en el ejemplo Daniel

    List listaJuegos = juegos
    .Where(j => j.MaxJugadores > 4)
    .OrderBy(j => j.Nombre);

    De esta forma me fallaba porque tenía que convertirlo a List de la siguiente manera

    List listaJuegos = juegos
    .Where(j => j.MaxJugadores > 4)
    .OrderBy(j => j.Nombre)
    .ToList();

    Si no lo hace con expresiones Lambda podrías dejarlo así

    List consulta = (from j in juegos
    where j.MaxJugadores > 4
    orderby j.Nombre ascending
    select j).ToList();

  5. juegos No tiene una definición Add(). No podemos agregar un nuevo elemento a la colección así como mencionas. Saludos Cordiales.

  6. Hola, excelente todo el material. Me daba un error el IDE y me respondieron en esta parte:

    private static List consultar()
    {
    List consulta = from j in juegos
    where j.MaxJugadores > 4
    orderby j.Nombre ascending
    select j;

    return consulta;
    }

    debería ser:

    private static List consultar()
    {
    var consulta = from j in juegos
    where j.MaxJugadores > 4
    orderby j.Nombre ascending
    select j;

    return consulta.ToList();
    }

    Saludos,

  7. Hola que tal.

    Resulta que en el primer ejemplo (como a varios) me ha salido un error.

    He visto en otros comentarios que han dejado la solución a dicho error, resulta que no me funcionan esas propuestas.

    Aquí el detalle del error:

    Error CS0266 Cannot implicitly convert type ‘System.Linq.IOrderedEnumerable’ to ‘System.Collections.Generic.List’. An explicit conversion exists (are you missing a cast?)

    Como ya muchos han mencionado, esto se soluciona realizando una conversión a tipo List, mi propuesta de solución queda de la siguiente manera:

    ———————————————————————–
    Dice:
    ———————————————————————–
    ” A continuación, crearemos un método que devuelva todos aquellos juegos que puedan ser jugados por más de cuatro personas ordenados ascendentemente.”

    private static List consultar()
    {
    List consulta = from j in juegos
    where j.MaxJugadores > 4
    orderby j.Nombre ascending
    select j;

    return consulta;
    }

    ———————————————————————–
    Tiene que decir:
    ———————————————————————–
    private static List consultar()
    {
    List consulta = (from j in juegos
    where j.MaxJugadores > 4
    orderby j.Nombre ascending
    select j;).toList();

    return consulta;
    }

    Bueno, al menos es lo que me funciono, saludos.

  8. El primer ejemplo arroja un error.

    Error CS0266 Cannot implicitly convert type ‘System.Linq.IOrderedEnumerable’ to ‘System.Collections.Generic.List’. An explicit conversion exists (are you missing a cast?

    Tiene que quedar de la siguiente forma:

    private static List consultar()
    {
    List consulta = (from j in juegos
    where j.MaxJugadores > 4
    orderby j.Nombre ascending
    select j).toList();

    return consulta;
    }

  9. private static readonly List NuevosJuegos = new List()
    {
    new Juego {Id = 1, Nombre = “Carcassonne”, MinJuagadores = 2, MaxJuagadores = 8},
    new Juego {Id = 2,Nombre = “Los colonos del catan”, MinJuagadores = 2, MaxJuagadores = 8},
    new Juego {Id=3, Nombre = “Jungle Speed”, MinJuagadores = 1, MaxJuagadores = 4},
    new Juego {Id = 4, Nombre = “Black Stories”, MinJuagadores = 2, MaxJuagadores = 100},
    new Juego {Id = 5, Nombre = “Munchkin”, MinJuagadores = 3, MaxJuagadores = 10}
    };

    private static IEnumerable Consulta()
    {
    var consulta = from j in NuevosJuegos
    where j.MaxJuagadores > 4
    orderby j.Nombre ascending
    select j;

    return consulta;
    }

    static void Main(string[] args)
    {

    IEnumerable resultado = Consulta();
    NuevosJuegos.Add(new Juego { Id = 6, Nombre = “Richard”, MinJuagadores = 4, MaxJuagadores = 8 });

    foreach (Juego juego in resultado)
    {
    Console.WriteLine(string.Format(“{0} ({1}). De {2} a {3} jugadores”, juego.Nombre, juego.Id, juego.MinJuagadores, juego.MaxJuagadores));
    }

    Console.ReadKey();
    }

  10. Para que funcione el Add.

    private static readonly List NuevosJuegos = new List()
    {
    new Juego {Id = 1, Nombre = “Carcassonne”, MinJuagadores = 2, MaxJuagadores = 8},
    new Juego {Id = 2,Nombre = “Los colonos del catan”, MinJuagadores = 2, MaxJuagadores = 8},
    new Juego {Id=3, Nombre = “Jungle Speed”, MinJuagadores = 1, MaxJuagadores = 4},
    new Juego {Id = 4, Nombre = “Black Stories”, MinJuagadores = 2, MaxJuagadores = 100},
    new Juego {Id = 5, Nombre = “Munchkin”, MinJuagadores = 3, MaxJuagadores = 10}
    };

    private static IEnumerable Consulta()
    {
    var consulta = from j in NuevosJuegos
    where j.MaxJuagadores > 4
    orderby j.Nombre ascending
    select j;

    return consulta;
    }

    static void Main(string[] args)
    {

    IEnumerable resultado = Consulta();
    NuevosJuegos.Add(new Juego { Id = 6, Nombre = “Richard”, MinJuagadores = 4, MaxJuagadores = 8 });

    foreach (Juego juego in resultado)
    {
    Console.WriteLine(string.Format(“{0} ({1}). De {2} a {3} jugadores”, juego.Nombre, juego.Id, juego.MinJuagadores, juego.MaxJuagadores));
    }

    Console.ReadKey();
    }

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s