Introducción a las consultas LINQ en C#
Una consulta es una expresión que recupera datos de un origen de datos. Los distintos orígenes de datos tienen diferentes lenguajes de consulta nativos, por ejemplo SQL para bases de datos relacionales y XQuery para XML. Los programadores deben aprender un lenguaje de consultas nuevo para cada tipo de origen de datos o formato de datos que deben admitir. LINQ simplifica esta situación al ofrecer un modelo de lenguaje C# coherente para tipos de orígenes de datos y formatos. En una consulta LINQ, siempre se trabaja con objetos de C#. Use los mismos patrones de codificación básicos para consultar y transformar datos en documentos XML, bases de datos SQL, colecciones de .NET y cualquier otro formato cuando un proveedor LINQ esté disponible.
Las tres partes de una operación de consulta
Todas las operaciones de consulta LINQ constan de tres acciones distintas:
- Obtener el origen de datos.
- Crear la consulta.
- Ejecutar la consulta.
En el siguiente ejemplo se muestra cómo se expresan las tres partes de una operación de consulta en código fuente. En el ejemplo se usa una matriz de enteros como origen de datos para su comodidad, aunque se aplican los mismos conceptos a otros orígenes de datos. En el resto de este tema se hará referencia a este artículo.
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];
// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;
// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
En la siguiente ilustración se muestra toda la operación de consulta. En LINQ, la ejecución de la consulta es distinta de la propia consulta. En otras palabras, no se recupera ningún dato mediante la creación de una variable de consulta.
El origen de datos
El origen de datos del ejemplo anterior es una matriz, que admite la interfaz genérica IEnumerable<T>. Este hecho implica que se puede consultar con LINQ. Se ejecuta una consulta en una instrucción foreach
, y foreach
requiere IEnumerable o bien IEnumerable<T>. Los tipos compatibles con IEnumerable<T> o una interfaz derivada, como la interfaz genérica IQueryable<T>, se denominan tipos consultables.
Un tipo consultable no requiere ninguna modificación ni ningún tratamiento especial para actuar como origen de datos de LINQ. Si el origen de datos no está en la memoria como tipo consultable, el proveedor de LINQ debe representarlo como tal. Por ejemplo, LINQ to XML carga un documento XML en un tipo consultable XElement:
// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");
Con EntityFramework, se crea una asignación relacional de objetos entre las clases de C# y el esquema de la base de datos. Después, se escriben las consultas en los objetos y, en tiempo de ejecución, EntityFramework controla la comunicación con la base de datos. En el ejemplo siguiente, Customers
representa una tabla específica en una base de datos, y el tipo del resultado de la consulta, IQueryable<T>, se deriva de IEnumerable<T>.
Northwnd db = new Northwnd(@"c:\northwnd.mdf");
// Query for customers in London.
IQueryable<Customer> custQuery =
from cust in db.Customers
where cust.City == "London"
select cust;
Para obtener más información sobre cómo crear tipos específicos de orígenes de datos, consulte la documentación de los distintos proveedores de LINQ. Aun así, la regla básica es sencilla: un origen de datos de LINQ es cualquier objeto que admita la interfaz genérica IEnumerable<T> o una interfaz que la haya heredado, normalmente IQueryable<T>.
Nota:
Los tipos como ArrayList, que admiten la interfaz no genérica IEnumerable, también se pueden usar como origen de datos de LINQ. Para más información, consulte el procedimiento para consultar un objeto ArrayList con LINQ (C#).
Consulta
La consulta especifica la información que se debe recuperar de los orígenes de datos. Opcionalmente, una consulta también especifica cómo se debe ordenar, agrupar y dar forma a esa información antes de devolverse. Las consultas se almacenan en una variable de consulta y se inicializan con una expresión de consulta. Use sintaxis de consulta de C# para escribir consultas.
La consulta del ejemplo anterior devuelve todos los números pares de la matriz de enteros. La expresión de consulta contiene tres cláusulas: from
, where
y select
. (Si está familiarizado con SQL, ha observado que la ordenación de las cláusulas se invierte del orden en SQL). La cláusula from
especifica el origen de datos, la cláusula where
aplica el filtro y la cláusula select
especifica el tipo de los elementos devueltos. Todas las cláusulas de consulta se describen en detalle en esta sección. Por ahora, lo importante es que en LINQ la variable de consulta no efectúa ninguna acción y no devuelve ningún dato. Lo único que hace es almacenar la información necesaria para generar los resultados cuando se ejecuta la consulta en algún momento posterior. Para obtener más información sobre cómo se construyen las consultas, consulte Información general sobre operadores de consulta estándar (C#).
Nota:
Las consultas también se pueden expresar empleando una sintaxis de método. Para obtener más información, vea Query Syntax and Method Syntax in LINQ (Sintaxis de consulta y sintaxis de método en LINQ).
Clasificación de operadores de consulta estándar por modo de ejecución
Las implementaciones de LINQ to Objects de los métodos de operador de consulta estándar se ejecutan de una de dos formas principales: inmediata o aplazada. Los operadores de consulta que usan la ejecución aplazada se pueden dividir además en dos categorías: de streaming y de no streaming.
Inmediato
La ejecución inmediata significa que se lee el origen de datos y que la operación se realiza una vez. Todos los operadores de consulta estándar que devuelven un resultado escalar se ejecutan de manera inmediata. Ejemplos de estas consultas son Count
, Max
, Average
y First
. Estos métodos se ejecutan sin una instrucción foreach
explícita porque la propia consulta debe usar foreach
para devolver un resultado. Estas consultas devuelven un único valor, y no una colección IEnumerable
. Puede forzar que cualquier consulta se ejecute inmediatamente si usa los métodos Enumerable.ToList o Enumerable.ToArray. La ejecución inmediata permite reutilizar los resultados de la consulta, no su declaración. Los resultados se recuperan una vez y, después, se almacenan para usarlos en el futuro. La consulta siguiente devuelve un recuento de los números pares de la matriz de origen:
var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;
int evenNumCount = evenNumQuery.Count();
Para forzar la ejecución inmediata de cualquier consulta y almacenar en caché los resultados correspondientes, puede llamar a los métodos ToList o ToArray.
List<int> numQuery2 =
(from num in numbers
where (num % 2) == 0
select num).ToList();
// or like this:
// numQuery3 is still an int[]
var numQuery3 =
(from num in numbers
where (num % 2) == 0
select num).ToArray();
También puede forzar la ejecución colocando el bucle foreach
justo después de la expresión de consulta, aunque, si se llama a ToList
o a ToArray
, también se almacenan en caché todos los datos de un objeto de colección.
Aplazado
La ejecución aplazada significa que la operación no se realiza en el punto en el código donde se declara la consulta. La operación se realiza solo cuando se enumera la variable de consulta, por ejemplo, mediante una instrucción foreach
. Los resultados de ejecutar la consulta dependen del contenido del origen de datos cuando se ejecuta la consulta en lugar de cuando se define la consulta. Si la variable de consulta se enumera varias veces, es posible que los resultados difieran cada vez. Casi todos los operadores de consulta estándar cuyo tipo de valor devuelto es IEnumerable<T> o IOrderedEnumerable<TElement> se ejecutan de una manera diferida. La ejecución diferida aporta la facilidad de reutilizar las consultas, ya que la consulta captura los datos actualizados del origen de datos cada vez que se iteran los resultados de la consulta. En el código siguiente se muestra un ejemplo de ejecución aplazada:
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
La instrucción foreach
es también donde se recuperan los resultados de la consulta. Por ejemplo, en la consulta anterior, la variable de iteración num
contiene cada valor (de uno en uno) en la secuencia devuelta.
Dado que la propia variable de consulta nunca contiene los resultados de la consulta, puede ejecutarla repetidamente para recuperar los datos actualizados. Por ejemplo, una aplicación independiente podría actualizar una base de datos continuamente. En la aplicación, podría crear una consulta que recupere los datos más recientes y podría ejecutarla a intervalos para recuperar los resultados actualizados.
Los operadores de consulta que usan la ejecución aplazada se pueden clasificar además como de streaming o de no streaming.
Streaming
Los operadores de streaming no deben leer todos los datos de origen antes de que generen elementos. En el momento de la ejecución, un operador de streaming realiza su operación en cada elemento de origen mientras se lee y proporciona el elemento si es necesario. Un operador de streaming continúa leyendo los elementos de origen hasta que se puede generar un elemento de resultado. Esto significa que es posible leer más de un elemento de origen para generar un elemento de resultado.
De no streaming
Los operadores de no streaming deben leer todos los datos de origen antes de poder generar un elemento de resultado. Las operaciones como la ordenación o la agrupación pertenecen a esta categoría. En tiempo de ejecución, los operadores de consulta de no streaming leen todos los datos de origen, los colocan en una estructura de datos, realizan la operación y generan los elementos resultantes.
Tabla de clasificación
En la tabla siguiente se clasifica cada método de operador de consulta estándar según su método de ejecución.
Nota:
Si un operador se marca en dos columnas, dos secuencias de entrada intervienen en la operación, y cada secuencia se evalúa de manera diferente. En estos casos, siempre es la primera secuencia de la lista de parámetros la que se evalúa en un modo de transmisión diferido.
LINQ to Objects
"LINQ to Objects" hace referencia al uso de consultas LINQ con cualquier colección IEnumerable o IEnumerable<T> directamente. Puede usar LINQ para consultar cualquier colección enumerable, como List<T>, Array o Dictionary<TKey,TValue>. La colección puede haberla definido el usuario, o bien puede que la haya devuelto una API de .NET. En el enfoque de LINQ, se escribe código declarativo que describe qué se quiere recuperar. LINQ to Objects proporciona una excelente introducción a la programación con LINQ.
Las consultas LINQ ofrecen tres ventajas principales con respecto a los bucles foreach
tradicionales:
- Son más concisas y legibles, especialmente cuando se filtran varias condiciones.
- Proporcionan funcionalidades eficaces para filtrar, ordenar y agrupar con un código de aplicación mínimo.
- Se pueden migrar a otros orígenes de datos con muy poca o ninguna modificación.
Cuanto más compleja sea la operación que se quiere realizar en los datos, más ventajas se obtienen al usar LINQ en lugar de las técnicas de iteración tradicionales.
Almacenar los resultados de una consulta en memoria
Una consulta es básicamente un conjunto de instrucciones sobre cómo recuperar y organizar los datos. Las consultas se ejecutan de forma diferida, ya que se solicita cada elemento subsiguiente del resultado. Cuando se usa foreach
para iterar los resultados, los elementos se devuelven a medida que se tiene acceso a ellos. Para evaluar una consulta y almacenar los resultados sin ejecutar un bucle foreach
, simplemente llame a uno de los métodos siguientes en la variable de consulta:
Debe asignar el objeto de colección devuelto a una nueva variable al almacenar los resultados de consulta, como se muestra en el ejemplo siguiente:
List<int> numbers = [1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];
IEnumerable<int> queryFactorsOfFour =
from num in numbers
where num % 4 == 0
select num;
// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();
// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);