Fundamentos de expresiones de consulta
En este artículo se presentan los conceptos básicos relacionados con las expresiones de consulta en C#.
¿Qué es una consulta y qué hace?
Una consulta es un conjunto de instrucciones que describe qué datos recuperar de un origen de datos determinado (o orígenes) y qué forma y organización deben tener los datos devueltos. Una consulta es distinta de los resultados que genera.
Por lo general, los datos de origen se organizan lógicamente como una secuencia de elementos del mismo tipo. Por ejemplo, una tabla de base de datos SQL contiene una secuencia de filas. En un archivo XML, hay una "secuencia" de elementos XML (aunque los elementos XML 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 y la estructura específicos de los datos de origen originales no son importantes. La aplicación siempre ve los datos de origen como una colección de IEnumerable<T> o IQueryable<T>. Por ejemplo, en LINQ to XML, los datos de origen se hacen visibles como un IEnumerable
<XElement>.
Dada esta secuencia de origen, una consulta puede hacer una de estas tres cosas:
Recupere un subconjunto de los elementos para generar una nueva secuencia sin modificar los elementos individuales. Después, la consulta puede ordenar o agrupar la secuencia devuelta de varias maneras, como se muestra en el ejemplo siguiente (supongamos que
scores
es unint[]
):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 transformarlos en un nuevo tipo de objeto. Por ejemplo, una consulta podría recuperar solo los nombres de familia de determinados registros de cliente de un origen de datos. O bien, podría recuperar el registro completo y, a continuación, usarlo para construir otro tipo de objeto en memoria o incluso datos XML antes de generar la secuencia de resultados final. En el ejemplo siguiente se muestra una proyección de un
int
a unstring
. Observe el nuevo tipo dehighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
Recupere un valor único de los datos de origen, como:
Número de elementos que coinciden con una condición determinada.
Elemento que tiene el mayor o menor valor.
Primer elemento que coincide con una condición o la suma de valores concretos en un conjunto especificado de elementos. Por ejemplo, la consulta siguiente devuelve el número de puntuaciones mayores que 80 de la matriz de enteros de
scores
:var highScoreCount = ( from score in scores where score > 80 select score ).Count();
En el ejemplo anterior, observe el uso de paréntesis alrededor de la expresión de consulta antes de la llamada al método Enumerable.Count. También puede usar una nueva variable para almacenar el resultado concreto.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var scoreCount = highScoresQuery3.Count();
En el ejemplo anterior, la consulta se ejecuta en la llamada a Count
, porque Count
debe iterar los resultados para determinar el número de elementos devueltos por highScoresQuery
.
¿Qué es una expresión de consulta?
Una expresión de consulta es una consulta expresada en la sintaxis de consulta. Una expresión de consulta es una construcción de lenguaje de primera clase. Es igual que cualquier otra expresión y se puede usar en cualquier contexto en el que una expresión de C# sea válida. Una expresión de consulta consta 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 varias 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 terminar con una cláusula select o group. Entre la primera cláusula from
y la última cláusula select
o group
, puede contener una o varias de estas cláusulas opcionales: where, orderby, join, let e incluso otras cláusulas from. También puede usar el como palabra clave para que el resultado de una cláusula join
o group
sirva de origen para más cláusulas de consulta en la misma expresión de consulta.
Variable de consulta
En LINQ, una variable de consulta es cualquier variable que almacena una consulta en lugar de los resultados de de una consulta. Más concretamente, una variable de consulta siempre es un tipo enumerable que genera una secuencia de elementos cuando se itera en una instrucción foreach
o una llamada directa a su método IEnumerator.MoveNext().
Nota
Los ejemplos en este artículo utilizan los siguientes origen de datos y datos de muestra.
record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];
static readonly Country[] countries = [
new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];
En el ejemplo de código siguiente se muestra una expresión de consulta simple con un origen de datos, una cláusula de filtrado, una cláusula de ordenación y ninguna transformación de los elementos de origen. La cláusula select
finaliza la consulta.
// 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 (var testScore in scoreQuery)
{
Console.WriteLine(testScore);
}
// Output: 93 90 82 82
En el ejemplo anterior, scoreQuery
es una variable de consulta , que a veces se conoce como solo una consulta . La variable de consulta no almacena datos de resultados reales, que se generan en el bucle foreach
. Y cuando se ejecuta la instrucción foreach
, 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
. Genera los mismos resultados siempre que ni él ni el origen de datos sean modificados.
Una variable de consulta puede almacenar una consulta expresada en sintaxis de consulta o sintaxis de método, o una combinación de los dos. En los ejemplos siguientes, tanto queryMajorCities
como queryMajorCities2
son variables de consulta:
City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000)
];
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 30_000_000
select city;
// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 30_000_000);
// Execute the query to produce the results
foreach (City city in queryMajorCities2)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
Por otro lado, los dos ejemplos siguientes muestran variables que no son variables de consulta aunque cada una se inicialice con una consulta. No son variables de consulta porque almacenan los resultados:
var highestScore = (
from score in scores
select score
).Max();
// or split the expression
IEnumerable<int> scoreQuery =
from score in scores
select score;
var highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
var 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;
var largeCitiesList2 = largeCitiesQuery.ToList();
Escritura explícita e implícita de variables de consulta
Esta documentación suele proporcionar el tipo explícito de la variable de consulta para mostrar la relación de tipo entre la variable de consulta y la cláusula select . Sin embargo, también puede usar la palabra clave var para indicar al compilador que infiera el tipo de una variable de consulta (o cualquier otra variable local) en tiempo de compilación. Por ejemplo, el ejemplo de consulta que se mostró anteriormente en este artículo también se puede expresar mediante la escritura implícita:
var queryCities =
from city in cities
where city.Population > 100000
select city;
En el ejemplo anterior, el uso de var es opcional. queryCities
es un IEnumerable<City>
que indica si se escribe implícita o explícitamente.
Inicio de una expresión de consulta
Una expresión de consulta debe comenzar con una cláusula from
. Especifica un origen de datos junto con una variable de intervalo. La variable de rango representa cada elemento sucesivo de la secuencia de origen a medida que se recorre la secuencia de origen. La variable de rango está fuertemente tipada según el tipo de elementos presentes en el origen de datos. En el ejemplo siguiente, dado que countries
es una matriz de objetos Country
, la variable de intervalo también se escribe como Country
. Dado que la variable de rango está fuertemente tipada, se puede usar el operador punto para tener acceso a cualquier miembro disponible del tipo.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 20 //sq km
select country;
La variable de rango está en el ámbito hasta que se cierra la consulta con un punto y coma o con una cláusula de continuación.
Una expresión de consulta puede contener varias cláusulas de from
. Use más cláusulas from
cuando cada elemento de la secuencia de origen sea una colección o contenga una colección. Por ejemplo, supongamos que tiene una colección de objetos Country
, cada uno de los cuales contiene una colección de objetos City
denominados Cities
. Para consultar los objetos City
en cada Country
, use 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 clause (Cláusula from).
Finalizar una expresión de consulta
Una expresión de consulta debe terminar con una cláusula group
o una cláusula select
.
La cláusula de grupo
Use la cláusula group
para generar una secuencia de grupos organizados por una clave que especifique. La clave puede ser cualquier tipo de datos. Por ejemplo, la consulta siguiente crea una secuencia de grupos que contiene uno o varios objetos Country
y cuya clave es un tipo de char
con el valor que es la primera letra de los nombres de los países.
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Para obtener más información sobre la agrupación, vea la cláusula de grupo .
select (cláusula)
Use la cláusula select
para generar todos los demás tipos de secuencias. Una cláusula select
simple 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 Country
objetos. La cláusula orderby
simplemente ordena los elementos en un nuevo orden y 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 usar para transformar los datos de origen en secuencias de nuevos tipos. Esta transformación también se denomina proyección. En el ejemplo siguiente, la cláusula select
proyecta una secuencia de los tipos anónimos que contiene solo un subconjunto de los campos del elemento original. Los nuevos objetos se inicializan mediante un inicializador de objeto.
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
Por lo tanto, en este ejemplo, se requiere el var
porque la consulta genera un tipo anónimo.
Para obtener más información sobre todas las formas en que se puede usar una cláusula select
para transformar datos de origen, consulte la cláusula SELECT.
Continuaciones desde hacia
Puede usar la palabra clave into
en una cláusula select
o group
para crear un identificador temporal que almacene una consulta. Use la cláusula into
cuando deba realizar operaciones de consulta adicionales en una consulta después de una operación de agrupación o selección. En el siguiente ejemplo se agrupan los objetos countries
según su población en intervalos de 10 millones. Una vez creados estos grupos, más cláusulas 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 / 1_000
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.
Filtrado, ordenación y combinación
Entre la cláusula de from
inicial y la cláusula final select
o group
, todas las demás cláusulas (where
, join
, orderby
, from
, let
) son opcionales. Cualquier cláusula opcional puede usarse cero veces o varias veces dentro de una consulta.
La cláusula where
Use la cláusula where
para filtrar los elementos de los datos de origen en función de una o varias expresiones de predicado. La cláusula where
del ejemplo siguiente tiene un predicado con dos condiciones.
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
Para obtener más información, vea where (Cláusula).
La cláusula orderby
Use la cláusula orderby
para ordenar los resultados en orden ascendente o descendente. También puede especificar los pedidos de ordenación secundarios. En el ejemplo siguiente se realiza una ordenación principal en 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; es el criterio de ordenación predeterminado si no se especifica ningún criterio. Para obtener más información, vea orderby (Cláusula).
La cláusula de unión
Use la cláusula join
para asociar o combinar elementos de un origen de datos con elementos de otro origen de datos en función de una comparación de igualdad entre claves especificadas en cada elemento. En LINQ, las operaciones de combinación se realizan en secuencias de objetos cuyos elementos son diferentes tipos. Después de combinar dos secuencias, debe usar una instrucción select
o group
para especificar qué elemento almacenar en la secuencia de salida. También puede usar un tipo anónimo para combinar propiedades de cada conjunto de elementos asociados en un nuevo tipo para la secuencia de salida. En el ejemplo siguiente se asocian prod
objetos cuya propiedad Category
coincide con una de las categorías de la matriz de cadenas categories
. Productos cuya Category
no coincide con ninguna cadena en categories
se filtran. 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 grupo almacenando los resultados de la operación de join
en una variable temporal mediante la palabra clave en. Para obtener más información, vea join (Cláusula, Referencia de C#).
La cláusula let
Use la cláusula let
para almacenar el resultado de una expresión, como una llamada de 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 devueltas por Split
.
string[] names = ["Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia"];
IEnumerable<string> queryFirstNames =
from name in names
let firstName = name.Split(' ')[0]
select firstName;
foreach (var s in queryFirstNames)
{
Console.Write(s + " ");
}
//Output: Svetlana Claire Sven Cesar
Para obtener más información, vea let (Cláusula).
Subconsultas en una expresión de consulta
Una cláusula de consulta puede contener una expresión de consulta, que a veces se conoce como subconsulta . Cada subconsulta comienza con su propia cláusula from
que no apunta necesariamente al mismo origen de datos en la primera cláusula from
. Por ejemplo, la consulta siguiente muestra una expresión de consulta que se usa 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.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.ExamScores.Average()
).Max()
};
Para más información, consulte Realizar una subconsulta en una operación de agrupación.