Compartir a través de


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 desarrolladores deben aprender un nuevo lenguaje de consulta 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.

Tres partes de una operación de consulta

Todas las operaciones de consulta LINQ constan de tres acciones distintas:

  1. Obtenga el origen de datos.
  2. Cree la consulta.
  3. Ejecutar la consulta.

En el ejemplo siguiente se muestra cómo se expresan las tres partes de una operación de consulta en el código fuente. En el ejemplo se usa una matriz de enteros como origen de datos para mayor comodidad; sin embargo, los mismos conceptos también se aplican a otros orígenes de datos. En el resto de este artículo se hace referencia a este ejemplo.

// 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 ilustración siguiente se muestra la operación de consulta completa. 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.

Diagrama de la operación de consulta LINQ completa.

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. Una consulta se ejecuta en una foreach instrucción y foreach requiere IEnumerable o IEnumerable<T>. Los tipos que admiten IEnumerable<T> o una interfaz derivada, como el genérico IQueryable<T> , se denominan tipos consultables.

Un tipo consultable no requiere ninguna modificación ni tratamiento especial para servir como origen de datos LINQ. Si los datos de origen aún no están en memoria como un tipo consultable, el proveedor 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. Escribes tus consultas sobre los objetos y, en tiempo de ejecución, Entity Framework controla la comunicación con la base de datos. En el ejemplo siguiente, Customers representa una tabla específica de la base de datos y el tipo del resultado de la consulta, IQueryable<T>, 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. Sin embargo, la regla básica es sencilla: un origen de datos LINQ es cualquier objeto que admita la interfaz genérica IEnumerable<T> o una interfaz que herede de él, 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 LINQ. Para obtener más información, vea Cómo consultar un objeto ArrayList con LINQ (C#).

La consulta

La consulta especifica qué información obtener del origen de datos o de sus fuentes. Opcionalmente, una consulta también especifica cómo se debe ordenar, agrupar y dar forma a esa información antes de devolverse. Una consulta se almacena en una variable de consulta e se inicializa con una expresión de consulta. Use la 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, wherey select. (Si está familiarizado con SQL, ha observado que la ordenación de las cláusulas se invierte del orden en SQL). La from cláusula especifica el origen de datos, la where cláusula aplica el filtro y la select cláusula especifica el tipo de los elementos devueltos. Todas las cláusulas de consulta se describen en detalle en esta sección. Por ahora, el punto importante es que en LINQ, la propia variable de consulta no realiza ninguna acción y no devuelve ningún dato. Simplemente almacena la información necesaria para generar los resultados cuando la consulta se ejecuta en algún momento posterior. Para obtener más información sobre cómo se construyen las consultas, vea Información general sobre operadores de consulta estándar (C#).

Nota:

Las consultas también se pueden expresar mediante la sintaxis del método. Para obtener más información, vea Sintaxis de consulta y sintaxis del método en LINQ.

Clasificación de operadores de consulta estándar por medio de ejecución

Las implementaciones LINQ to Objects de los métodos de operador de consulta estándar se ejecutan de una de estas dos maneras principales: inmediatas o diferidas. Los operadores de consulta que usan la ejecución diferida se pueden dividir además en dos categorías: streaming y no streaming.

Inmediata

La ejecución inmediata significa que el origen de datos se lee y la operación se realiza una vez. Todos los operadores de consulta estándar que devuelven un resultado escalar se ejecutan inmediatamente. Algunos ejemplos de estas consultas son Count, Max, Averagey First. Estos métodos se ejecutan sin una instrucción explícita foreach porque la propia consulta debe usar foreach para devolver un resultado. Estas consultas devuelven un valor único, no una IEnumerable colección. Puede forzar que cualquier consulta se ejecute inmediatamente mediante los métodos Enumerable.ToList o Enumerable.ToArray. La ejecución inmediata proporciona reutilización de los resultados de la consulta, no de la declaración de consulta. Los resultados se recuperan una vez y, a continuación, se almacenan para su uso futuro. La consulta siguiente devuelve un recuento de los números par 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é sus resultados, 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 foreach bucle inmediatamente después de la expresión de consulta. Sin embargo, llamando a ToList o ToArray también almacena en caché todos los datos de un único objeto de colección.

Aplazado

La ejecución diferida significa que la operación no se realiza en el punto del código donde se declara la consulta. La operación solo se realiza cuando se enumera la variable de consulta, por ejemplo mediante una foreach instrucción . 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, los resultados pueden diferir cada vez. Casi todos los operadores de consulta estándar cuyo tipo de valor devuelto es IEnumerable<T> o IOrderedEnumerable<TElement> se ejecutan de forma diferida. La ejecución diferida proporciona la facilidad de reutilización de 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 diferida:

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 num de iteración contiene cada valor (uno a 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 diferida se pueden clasificar además como streaming o no streaming.

Transmisión en línea

Los operadores de streaming no tienen que leer todos los datos de origen antes de que produzcan 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 elementos de origen hasta que se pueda generar un elemento de resultado. Esto significa que se puede leer más de un elemento de origen para generar un elemento de resultado.

De no streaming

Los operadores no secuenciales deben leer todos los datos fuente antes de poder producir un elemento de resultado. Las operaciones como la ordenación o la agrupación se dividen en esta categoría. En el momento de la ejecución, los operadores de consulta que no son de streaming leen todos los datos de origen, lo colocan en una estructura de datos, realizan la operación y producen 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 está marcado en dos columnas, dos secuencias de entrada intervienen en la operación y cada secuencia se evalúa de forma diferente. En estos casos, siempre es la primera secuencia de la lista de parámetros que se evalúa de forma diferida y de streaming.

Operador de consulta estándar Tipo de retorno Ejecución inmediata Ejecución aplazada de streaming Ejecución aplazada de no streaming
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average Valor numérico único
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max Valor numérico único, TSource, o TResult?
Min Valor numérico único, TSource, o TResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum Valor numérico único
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] arreglo
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

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>, Arrayo Dictionary<TKey,TValue>. La colección puede definirse por el usuario o un tipo devuelto por una API de .NET. En el enfoque LINQ, escribirá código declarativo que describe lo que desea recuperar. LINQ to Objects proporciona una excelente introducción a la programación con LINQ.

Las consultas LINQ ofrecen tres ventajas principales sobre los bucles tradicionales foreach :

  • Son más concisos y legibles, especialmente cuando se filtran varias condiciones.
  • Proporcionan funcionalidades eficaces de filtrado, ordenación y agrupación con un mínimo de código de aplicación.
  • Se pueden migrar a otros orígenes de datos con poca o ninguna modificación.

Cuanto más compleja sea la operación que desea realizar en los datos, más ventaja obtendrá mediante LINQ en lugar de técnicas de iteración tradicionales.

Almacenar los resultados de una consulta en memoria

Una consulta es básicamente un conjunto de instrucciones para 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, se devuelven los elementos a los que se accede. Para evaluar una consulta y almacenar sus resultados sin ejecutar un foreach bucle, 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 la 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]);

Consulte también