Es muy probable que alguna vez en tu vida de desarrollador hayas escuchado hablar de una consulta SQL, pero si no, te cuento que una consulta es una instrucción que se le da a la computadora para que busque registros dentro de una tabla y nos entregue los resultados de dicha búsqueda. Una consulta en SQL se ve así:

SELECT Name, Surname, Age
FROM Students
WHERE Age > 10
ORDER BY Name

Durante mucho tiempo, el poder de las consultas se mantuvo únicamente para las bases de datos, sin embargo, en el 2007 Microsoft liberó junto con la versión 3.5 de su framework, una herramienta llamada LINQ que proviene de Language INtegrated Query (personalmente prefiero escribir Linq con minúsculas... cuestión de gustos).

Linq nos otorga la posibilidad de usar un lenguaje bastante similar al SQL dentro de nuestro código C# para consultar un origen de datos. Para poder usarlo, se requiere de un proveedor que no es más que una especie de traductor que toma nuestra consulta y la convierte en algo que pueda ejecutar el origen de datos. Existen proveedores dentro del framework que nos permiten usar Linq a objetos, Linq a XML y Linq a SQL.

Linq to Objects

Para esta serie de posts, me centraré en Linq a objetos (los otros tienen un funcionamiento similar) que opera sobre colecciones de objetos en memoria que implementan la interfaz IEnumerable como arreglos, List, Vector...

Haciéndo una analogía a la consulta con la que inicié el post, supongamos que tenemos una lista de alumnos llamada students:

List<Student> students = new List<Student>
{
    new Student { Age = 11, Name = "Laura", Surname = "Rodríguez" },
    new Student { Age = 9, Name = "Francisco", Surname="Jímenez" },
};

Formar una consulta simple es bastante sencillo, y muy parecido a lo que haríamos en SQL, lo que necesitamos hacer es:

  1. Nombrar una variable que contendrá temporalmente cada uno de los elementos contenidos en la colección sobre la que vamos a ejecutar la consulta. Para nuestro caso, la llamarémos s.
  2. Indicar el origen de datos a consultar. En nuestro caso, este origen de datos es nuestra lista students.
  3. Indicar las condiciones que queremos que nuestra consulta compruebe.
  4. Realizar una acción con los elementos que cumplan las condiciones previamente indicadas.

Para ejecutar la consulta del inicio del post sobre nuestra lista de alumnos, debemos usar la sintaxis siguiente:

var olderStudents = from s in students
                    where s.Age > 10
                    select s;

Es fácil identificar las partes que la componen:

  • from s hace referencia a la variable temporal de la que les contaba un poco antes.
  • in students hace referencia a la colección sobre la que vamos a ejecutar la consulta, esto hace que nuestra variable s sea de tipo Student
  • where s.Age > 10 hace referencia a las condiciones que queremos que nuestra consulta compruebe aquí podemos usar condiciones más complejas usando los operadores lógicos || o &&.
  • select s hace referencia a la acción que queremos, en este caso únicamente estamos seleccionando los elementos que conicidan con s.Age > 10

Ejecución retrasada

Una cosa que es importante saber es que el declarar una consulta Linq no implica que se ejecute en el momento de la declaración. Lo que quiere decir esto es que debemos ver el resultado de una consulta como una referencia a cómo se ejecuta la consulta, no el resultado de la consulta ejecutada, es por eso que muchas veces veran que se usa la palabra var junto con Linq. En realidad la consulta será ejecutada cuando se necesite.

Para explicar con un ejemplo, volviendo a nuestra consulta olderStudents, sabemos que solamente uno de los objetos cumple con la condición establecida de s.Age > 10, es por eso que cuando queremos contar los elementos con Count(), el resultado es 1:

Console.WriteLine(olderStudents.Count()); // 1

Ahora, si agregamos un nuevo elemento a nuestra lista original students:

students.Add(new Student { Age = 23, Name = "Antonio", Surname = "Feregrino" });

Y volvemos a contar los objetos dentro de de nuestra consulta olderStudents, el resultado será 2:

Console.WriteLine(olderStudents.Count()); // 2

A esta característica se le conoce como ejecución retrasada o deferred execution. Podemos evitar esto al convertir nuestra consulta a una colección estática, como una lista o un arreglo utilizando los métodos que nos entrega Linq, como ToList o ToArray. Al hacer esto se ejecutará la consulta y se perderá la referencia a esta, con lo que cualquier modificación que sufran los orígenes de datos, no afectará nuestro resultado. Esto se logra así desde la declaración de la consulta, o más adelante llamando ToList o ToArray sobre la consulta.

// Al momento de la declaración
List<Student> olderStudentList = (from s in students
                                  where s.Age > 10
                                  select s).ToList();

// En cualquier otro momento		  
var olderStudentsArray = olderStudents.ToArray();

Lo que sigue

Las consultas simples, como la que se usa en este post, son solo la punta del iceberg de todo lo que se puede hacer con Linq, en futuros post veremos las posibilidades que tenemos para hacer consultas más complejas para obtener promedios, sumas, mínimos, hacer uniones, agrupamientos, intersecciones, distinciones... en fin, una enorme cantidad de cosas. Además de que veremos qué son los tipos anónimos en C#.