Conceptos básicos de las expresiones de consultas (Guía de programación de C#)

¿Qué es una consulta y qué hace?

Una consulta es un conjunto de instrucciones que describe qué datos hay que recuperar de uno o varios orígenes de datos determinados y qué forma y organización deben tener los datos devueltos. Una cosa es una consulta y otra distinta los resultados que genera.

Normalmente, los datos del origen se organizan de forma lógica como una secuencia de elementos del mismo tipo. Una tabla de base de datos SQL contiene una secuencia de filas. De igual forma, un objeto ADO.NET DataTable contiene una secuencia de objetos DataRow. En un archivo XML, hay una "secuencia" de elementos XML (aunque éstos se organizan jerárquicamente en una estructura de árbol). Una colección en memoria contiene una secuencia de objetos.

Desde el punto de vista de una aplicación, el tipo específico y la estructura de los datos originales no son aspectos importantes. La aplicación siempre considera los datos de origen como una colección IEnumerable o IQueryable. En LINQ to XML, los datos de origen aparecen visibles como un <XElement> IEnumerable. En LINQ to DataSet, es un <DataRow> IEnumerable. En LINQ to SQL, es un IEnumerable o IQueryable de cualquier objeto personalizado que se haya definido para representar los datos de la tabla SQL.

Dada esta secuencia de origen, una consulta puede hacer una de estas tres cosas:

  • Recuperar un subconjunto de los elementos para generar una nueva secuencia sin modificar los elementos individuales. A continuación, la consulta puede ordenar o agrupar de varias maneras la secuencia devuelta, como se muestra en el ejemplo siguiente (se supone que scores es un int[]):

    IEnumerable<int> highScoresQuery =
        from score in scores
        where score > 80
        orderby score descending 
        select score;
    
  • Recuperar una secuencia de elementos como en el ejemplo anterior, pero transformándolos en un nuevo tipo de objeto. Por ejemplo, una consulta puede recuperar sólo los apellidos de ciertos registros de clientes de un origen de datos. O bien, puede recuperar el registro completo y, a continuación, utilizarlo para construir otro tipo de objeto en memoria, o incluso datos XML, antes de generar la secuencia del resultado final. El ejemplo siguiente muestra una transformación de un tipo int en un string. Fíjese en el nuevo tipo de highScoresQuery.

    IEnumerable<string> highScoresQuery2 =
        from score in scores
        where score > 80
        orderby score descending 
        select String.Format("The score is {0}", score);
    
  • Recuperar un valor singleton relacionado con los datos de origen, como:

    • El número de elementos que cumplen cierta condición.

    • El elemento que presenta el valor mayor o menor.

    • El primer elemento que cumple una condición, o la suma de determinados valores en un conjunto especificado de elementos. Por ejemplo, la consulta siguiente devuelve el número de puntuaciones superiores a 80 de la matriz de enteros scores:

    int highScoreCount =
        (from score in scores
         where score > 80
         select score)
         .Count();
    

    En el ejemplo anterior, tenga en cuenta el uso de paréntesis alrededor de la expresión de consulta antes de la llamada al método Count. También puede expresar esto utilizando una nueva variable para almacenar el resultado concreto. Esta técnica es más legible porque mantiene la variable que almacena la consulta independiente de la consulta que almacena un resultado.

    IEnumerable<int> highScoresQuery3 =
        from score in scores
        where score > 80
        select score;
    
    int scoreCount = highScoresQuery3.Count();
    

En el ejemplo anterior, la consulta se ejecuta en la llamada a Count, ya que Count debe iterar sobre los resultados en orden para determinar el número de elementos devuelto por highScoresQuery.

¿Qué es una expresión de consulta?

Una expresión de consulta es una consulta expresada en sintaxis de consultas. Una expresión de consulta es una construcción de lenguaje de alto nivel. Es una expresión como cualquier otra y se utiliza en cualquier contexto en el que una expresión de C# sea válida. Una expresión de consulta está compuesta de un conjunto de cláusulas escritas en una sintaxis declarativa similar a SQL o XQuery. Cada cláusula contiene a su vez una o más expresiones de C#, y estas expresiones pueden ser una expresión de consulta o contener una expresión de consulta.

Una expresión de consulta debe comenzar con una cláusula from y debe finalizar con una cláusula select o group. Entre la primera cláusula from y la última cláusula select o group, pueden existir una o varias de estas cláusulas opcionales: where, orderby, join, let, e incluso cláusulas from adicionales. También se puede utilizar la palabra clave into para hacer que el resultado de una cláusula join o group actúe como origen de datos para las cláusulas de consulta adicionales incluidas en la misma expresión de consulta.

Variable de consulta

En LINQ, una variable de consulta es toda variable que almacena una consulta en lugar de los resultados de la misma. Para ser más exactos, una variable de consulta es siempre un tipo enumerable que generará una secuencia de elementos cuando se recorre en iteración en una instrucción foreach o una llamada directa a su método IEnumerator.MoveNext.

El ejemplo de código siguiente muestra una expresión de consulta simple con un origen de datos, una cláusula de filtrado, una cláusula de clasificación y ninguna transformación de los elementos de origen. La cláusula select finaliza la consulta.

static void Main()
{
    // Data source. 
    int[] scores = { 90, 71, 82, 93, 75, 82 };

    // Query Expression.
    IEnumerable<int> scoreQuery = //query variable 
        from score in scores //required 
        where score > 80 // optional 
        orderby score descending // optional 
        select score; //must end with select or group 

    // Execute the query to produce the results 
    foreach (int testScore in scoreQuery)
    {
        Console.WriteLine(testScore);
    }                  
}
// Outputs: 93 90 82 82      

En el ejemplo anterior, scoreQuery es una variable de consulta, a la que a veces simplemente se le llama consulta. La variable de consulta no almacena ninguno de los datos de resultado reales que se generan en el bucle foreach. Y cuando la instrucción foreach se ejecuta, los resultados de la consulta no se devuelven a través de la variable de consulta scoreQuery. En su lugar, se devuelven a través de la variable de iteración testScore. La variable scoreQuery se puede iterar en un segundo bucle foreach. Generará los mismos resultados siempre que ni ella ni el origen de datos se hayan modificado.

Una variable de consulta puede almacenar una consulta expresada en sintaxis de consulta, sintaxis de método o una combinación de ambas. En los ejemplos siguientes, queryMajorCities y queryMajorCities2 son variables de consulta:

//Query syntax
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;


// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

Por otro lado, los dos ejemplos siguientes muestran variables que no son variables de consulta, aunque cada una de ellas se inicialice con una consulta. No son variables de consulta porque almacenan resultados:

int highestScore =
    (from score in scores
     select score)
    .Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();

List<City> largeCitiesList =
    (from country in countries
     from city in country.Cities
     where city.Population > 10000
     select city)
       .ToList();

// or split the expression
IEnumerable<City> largeCitiesQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();
NotaNota

En la documentación de LINQ, las variables que almacenan una consulta incluyen la palabra "query" como parte de sus nombres.Las variables que almacenan un resultado real no incluyen "query" en sus nombres.

Para obtener más información sobre los distintos modos de expresar consultas, vea Sintaxis de consultas y sintaxis de métodos en LINQ (C#).

Asignación explícita e implícita de tipos de las variables de consulta

Esta documentación normalmente proporciona el tipo explícito de la variable de consulta para mostrar la relación de tipos entre la variable de consulta y la cláusula select. Sin embargo, también se puede utilizar la palabra clave var para indicar al compilador que debe deducir el tipo de una variable de consulta (o cualquier otra variable local) en tiempo de compilación. Por ejemplo, la consulta que se mostró anteriormente en este tema también se puede expresar utilizando la asignación implícita de tipos:

// Use of var is optional here and in all queries. 
// queryCities is an IEnumerable<City> just as  
// when it is explicitly typed. 
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

Para obtener más información, vea Variables locales con asignación implícita de tipos (Guía de programación de C#) y Relaciones entre tipos en operaciones de consulta LINQ (C#).

Iniciar una expresión de consulta

Una expresión de consulta debe comenzar con una cláusula from. Ésta especifica un origen de datos junto con una variable de rango. La variable de rango representa cada elemento sucesivo de la secuencia del origen mientras ésta se recorre. La variable de rango está fuertemente tipada basada en el tipo de los elementos del origen de datos. En el ejemplo siguiente, puesto que countries es una matriz de objetos Country, la variable de rango también presentará el mismo tipo que Country. Dado que la variable de rango está fuertemente tipada, es posible utilizar el operador de punto para obtener acceso a cualquier miembro disponible del tipo.

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km 
    select country;

La variable de rango permanece dentro de su ámbito hasta que se sale de la consulta mediante un punto y coma o con una cláusula continuation.

Una expresión de consulta puede contener varias cláusulas from. Utilice cláusulas from adicionales cuando cada elemento de la secuencia de origen sea en sí mismo una colección o contenga una colección. Por ejemplo, suponga que tiene una colección de objetos Country, cada uno de los cuales contiene una colección de objetos City denominada Cities. Para consultar los objetos City de cada Country, utilice dos cláusulas from como se muestra aquí:

IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

Para obtener más información, vea from (Cláusula, Referencia de C#).

Finalizar una expresión de consulta

Una expresión de consulta debe finalizar con una cláusula select o una cláusula group.

Cláusula group

Utilice la cláusula group para generar una secuencia de grupos organizada por una clave especificada. La clave puede ser de cualquier tipo de datos. Por ejemplo, la consulta siguiente crea una secuencia de grupos que contiene uno o más objetos Country y cuya clave es un valor char de cadena.

var queryCountryGroups =
    from country in countries
    group country by country.Name[0];

Para obtener más información sobre agrupación de datos, vea group (Cláusula, Referencia de C#).

Cláusula select

Utilice la cláusula select para generar todos los otros tipos de secuencias. Una cláusula select simplemente genera una secuencia del mismo tipo de objetos que los objetos contenidos en el origen de datos. En este ejemplo, el origen de datos contiene objetos Country. La cláusula orderby simplemente ordena los elementos en un nuevo orden, mientras que la cláusula select genera una secuencia de los objetos Country reordenados.

IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;

La cláusula select se puede utilizar para transformar datos de origen en secuencias de nuevos tipos. Esta transformación también se denomina una proyección. En el ejemplo siguiente, la cláusula select proyecta una secuencia de tipos anónimos que contiene sólo un subconjunto de los campos del elemento original. Observe que los nuevos objetos se inicializan mediante un inicializador de objeto.

// Here var is required because the query 
// produces an anonymous type. 
var queryNameAndPop =
    from country in countries
    select new { Name = country.Name, Pop = country.Population };

Para obtener más información sobre todas las maneras en que una cláusula select se puede utilizar para transformar datos de origen, vea select (Cláusula, Referencia de C#).

Continuaciones con "into"

Puede utilizar la palabra clave into en una cláusula select o group para crear un identificador temporal que almacena una consulta. Haga esto cuando deba realizar operaciones de consulta adicionales sobre una consulta después de una operación de agrupación o selección. En el ejemplo siguiente, countries están agrupados según su población en intervalos de 10 millones. Una vez creados estos grupos, las cláusulas adicionales filtran algunos grupos y, a continuación, ordenan los grupos en orden ascendente. Para realizar esas operaciones adicionales, se requiere la continuación representada por countryGroup.

// percentileQuery is an IEnumerable<IGrouping<int, Country>> 
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10000000
    group country by percentile into countryGroup
    where countryGroup.Key >= 20
    orderby countryGroup.Key
    select countryGroup;

// grouping is an IGrouping<int, Country> 
foreach (var grouping in percentileQuery)
{
    Console.WriteLine(grouping.Key);
    foreach (var country in grouping)
        Console.WriteLine(country.Name + ":" + country.Population);
}

Para obtener más información, vea into (Referencia de C#).

Filtrado, ordenación y combinación

Entre la cláusula de inicio from, y de finalización select o group, todas las demás cláusulas (where, join, orderby, from, let) son opcionales. Cualquiera de las cláusulas opcionales se pueden utilizar varias veces, o ninguna, en el cuerpo de una consulta.

Cláusula where

Utilice la cláusula where para filtrar los elementos de los datos de origen según una o varias expresiones de predicado. La cláusula where del ejemplo siguiente tiene dos predicados.

IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population < 200000 && city.Population > 100000
    select city;

Para obtener más información, consulte where (Cláusula, Referencia de C#).

Cláusula orderby

Utilice la cláusula orderby para ordenar los resultados en orden ascendente o descendente. También puede especificar criterios de ordenación secundarios. El ejemplo siguiente realiza una ordenación principal sobre los objetos country mediante la propiedad Area. A continuación, realiza una ordenación secundaria mediante la propiedad Population.

IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area, country.Population descending 
    select country;

La palabra clave ascending es opcional; constituye el criterio de ordenación predeterminado si no se especifica ningún orden. Para obtener más información, consulte orderby (Cláusula, Referencia de C#).

Cláusula join

Utilice la cláusula join para asociar y/o combinar elementos de un origen de datos con elementos de otro origen de datos según una comparación de igualdad entre claves especificadas en cada elemento. En LINQ, las operaciones de combinación se realizan sobre secuencias de objetos cuyos elementos son de tipos diferentes. Después de haber unido dos secuencias, deberá utilizar una instrucción select o group para especificar qué elemento va a almacenar en la secuencia de salida. También puede utilizar un tipo anónimo para combinar propiedades de cada conjunto de elementos asociados en un nuevo tipo para la secuencia de salida. El ejemplo siguiente asocia objetos prod cuya propiedad Category coincide con una de las categorías de la matriz de cadenas categories. Los productos cuya propiedad Category no coincide con alguna cadena de categories no pasan el filtro. La instrucción select proyecta un nuevo tipo cuyas propiedades se toman de cat y prod.

var categoryQuery =
    from cat in categories
    join prod in products on cat equals prod.Category
    select new { Category = cat, Name = prod.Name };

También puede realizar una unión de grupos almacenando los resultados de la operación join en una variable temporal mediante la palabra clave into. Para obtener más información, consulte join (Cláusula, Referencia de C#).

Cláusula let

Utilice la cláusula let para almacenar el resultado de una expresión, tal como una llamada a un método, en una nueva variable de intervalo. En el ejemplo siguiente, la variable de intervalo firstName almacena el primer elemento de la matriz de cadenas devuelta por Split.

string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
    from name in names
    let firstName = name.Split(new char[] { ' ' })[0]
    select firstName;

foreach (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar

Para obtener más información, consulte let (Cláusula, Referencia de C#).

Subconsultas en una expresión de consulta

Una cláusula de consulta puede contener en sí misma una expresión de consulta, la cual se denomina a veces subconsulta. Cada subconsulta se inicia con su propia cláusula from, que no necesariamente hace referencia al mismo origen de datos de la primera cláusula from. Por ejemplo, la consulta siguiente muestra una expresión de consulta que se utiliza en la instrucción select para recuperar los resultados de una operación de agrupación.

var queryGroupMax =
    from student in students
    group student by student.GradeLevel into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore =
            (from student2 in studentGroup
             select student2.Scores.Average())
             .Max()
    };

Para obtener más información, vea Cómo: Realizar una subconsulta en una operación de agrupación (Guía de programación de C#).

Vea también

Conceptos

Guía de programación de C#

Expresiones de consultas LINQ (Guía de programación de C#)

Información general sobre operadores de consulta estándar

Otros recursos

LINQ (Language-Integrated Query)

Palabras clave de consultas (Referencia de C#)