Tutorial: Escribir consultas en C# (LINQ)

Este tutorial muestra las características del lenguaje C# que se usan para escribir expresiones de consulta de LINQ.

Crear un proyecto de C#

Nota

Las siguientes instrucciones se aplican a Visual Studio. Si usa otro entorno de desarrollo, cree un proyecto de consola con una referencia a System.Core.dll y una directiva using para el espacio de nombres System.Linq.

Para crear un proyecto en Visual Studio

  1. Inicie Visual Studio.

  2. En la barra de menús, elija Archivo, Nuevo, Proyecto.

    Aparece el cuadro de diálogo Nuevo proyecto .

  3. Expanda Instalado, Plantillas, Visual C# y luego elija Aplicación de consola.

  4. En el cuadro de texto Nombre, escriba otro nombre o acepte el predeterminado y luego elija el botón Aceptar.

    El proyecto nuevo aparece en el Explorador de soluciones.

  5. Observe que el proyecto tiene una referencia a System.Core.dll y una directiva using para el espacio de nombres System.Linq.

Crear un origen de datos en memoria

El origen de datos de las consultas es una simple lista de objetos Student. Cada registro Student tiene un nombre, un apellido y una matriz de enteros que representa sus puntuaciones de las pruebas en la clase. Copie este código en el proyecto. Observe las siguientes características:

  • La clase Student consta de propiedades implementadas automáticamente.

  • Cada alumno de la lista se inicializa con un inicializador de objeto.

  • La propia lista se inicializa con un inicializador de colección.

Toda la estructura de datos se inicializará y creará una instancia sin llamadas explícitas a ningún constructor ni acceso a miembro explícito. Para obtener más información sobre estas nuevas características, vea Propiedades autoimplementadas (Propiedades implementadas automáticamente) y Inicializadores de objeto y de colección.

Para agregar el origen de datos

  • Agregue la clase Student y la lista inicializada de alumnos a la clase Program del proyecto.

    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }
    
    // Create a data source by using a collection initializer.
    static List<Student> students = 
    [
        new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 92, 81, 60}},
        new Student {First="Claire", Last="O'Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
        new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {88, 94, 65, 91}},
        new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {97, 89, 85, 82}},
        new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {35, 72, 91, 70}},
        new Student {First="Fadi", Last="Fakhouri", ID=116, Scores= new List<int> {99, 86, 90, 94}},
        new Student {First="Hanying", Last="Feng", ID=117, Scores= new List<int> {93, 92, 80, 87}},
        new Student {First="Hugo", Last="Garcia", ID=118, Scores= new List<int> {92, 90, 83, 78}},
        new Student {First="Lance", Last="Tucker", ID=119, Scores= new List<int> {68, 79, 88, 92}},
        new Student {First="Terry", Last="Adams", ID=120, Scores= new List<int> {99, 82, 81, 79}},
        new Student {First="Eugene", Last="Zabokritski", ID=121, Scores= new List<int> {96, 85, 91, 60}},
        new Student {First="Michael", Last="Tucker", ID=122, Scores= new List<int> {94, 92, 91, 91}}
    ];
    

Para agregar un nuevo alumno a la lista de alumnos

  1. Agregue un nuevo Student a la lista Students y use el nombre y las puntuaciones de las pruebas que prefiera. Pruebe a escribir toda la nueva información de alumno para conocer mejor la sintaxis del inicializador de objeto.

Crear la consulta

Para crear una consulta simple

  • En el método Main de la aplicación, cree una consulta simple que, cuando se ejecute, genere una lista de todos los alumnos cuya puntuación en la primera prueba haya sido superior a 90. Tenga en cuenta que, dado que se ha seleccionado todo el objeto Student, el tipo de la consulta es IEnumerable<Student>. Aunque el código podría usar tipos implícitos con la palabra clave var, se usan tipos explícitos para mostrar claramente los resultados. (Para obtener más información sobre var, vea Implicitly Typed Local Variables [Variables locales con tipo implícito]).

    Tenga también en cuenta que la variable de rango de la consulta, student, actúa como referencia a cada Student del origen, lo que proporciona acceso a miembro para cada objeto.

// Create the query.
// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
    from student in students
    where student.Scores[0] > 90
    select student;

Ejecutar la consulta

Para ejecutar la consulta

  1. Escriba ahora el bucle foreach que hará que la consulta se ejecute. Tenga en cuenta los siguiente sobre el código:

    • A cada elemento de la secuencia devuelta se accede mediante la variable de iteración del bucle foreach.

    • El tipo de esta variables es Student y el tipo de la variable de consulta es compatible, IEnumerable<Student>.

  2. Tras agregar este código, compile y ejecute la aplicación para ver los resultados en la ventana Consola.

// Execute the query.
// var could be used here also.
foreach (Student student in studentQuery)
{
    Console.WriteLine("{0}, {1}", student.Last, student.First);
}

// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

Para agregar otra condición de filtro

  1. Puede combinar varias condiciones booleanas en la cláusula where para delimitar aún más una consulta. El código siguiente agrega una condición para que la consulta devuelva los alumnos cuya primera puntuación haya sido superior a 90 y cuya última puntuación haya sido inferior a 80. La cláusula where debería ser similar al código siguiente.

    where student.Scores[0] > 90 && student.Scores[3] < 80  
    

    Para obtener más información, vea where (Cláusula).

Modificar la consulta

Para ordenar los resultados

  1. Le resultará más fácil examinar los resultados si se muestran con algún tipo de orden. Puede ordenar la secuencia devuelta por cualquier campo accesible de los elementos de origen. Por ejemplo, la cláusula orderby siguiente ordena los resultados alfabéticamente de la A a la Z por el apellido de cada alumno. Agregue la cláusula orderby siguiente a la consulta, inmediatamente después de la instrucción where y antes de la instrucción select:

    orderby student.Last ascending  
    
  2. Cambie ahora la cláusula orderby para que ordene los resultados en orden inverso según la puntuación en la primera prueba, de la puntuación más alta a la más baja.

    orderby student.Scores[0] descending  
    
  3. Cambie la cadena de formato WriteLine para poder ver las puntuaciones:

    Console.WriteLine("{0}, {1} {2}", student.Last, student.First, student.Scores[0]);  
    

    Para obtener más información, vea orderby (Cláusula).

Para agrupar los resultados

  1. La agrupación es una funcionalidad de gran eficacia en expresiones de consulta. Una consulta con una cláusula group genera una secuencia de grupos y cada grupo propiamente dicho contiene un Key y una secuencia que consta de todos los miembros del grupo. La siguiente nueva consulta agrupa los alumnos usando la primera letra del apellido como clave.

    IEnumerable<IGrouping<char, Student>> studentQuery2 =
        from student in students
        group student by student.Last[0];
    
  2. Tenga en cuenta que el tipo de la consulta ha cambiado. Ahora genera una secuencia de grupos que tienen un tipo char como clave y una secuencia de objetos Student. Dado que el tipo de la consulta ha cambiado, el siguiente código cambia también el bucle de ejecución foreach:

    foreach (IGrouping<char, Student> studentGroup in studentQuery2)
    {
        Console.WriteLine(studentGroup.Key);
        foreach (Student student in studentGroup)
        {
            Console.WriteLine("   {0}, {1}",
                        student.Last, student.First);
        }
    }
    
    // Output:
    // O
    //   Omelchenko, Svetlana
    //   O'Donnell, Claire
    // M
    //   Mortensen, Sven
    // G
    //   Garcia, Cesar
    //   Garcia, Debra
    //   Garcia, Hugo
    // F
    //   Fakhouri, Fadi
    //   Feng, Hanying
    // T
    //   Tucker, Lance
    //   Tucker, Michael
    // A
    //   Adams, Terry
    // Z
    //   Zabokritski, Eugene
    
  3. Ejecute la aplicación y vea los resultados en la ventana Consola.

    Para obtener más información, vea group (Cláusula).

Para que las variables tengan tipo implícito

  1. La codificación explícita con IEnumerables de IGroupings puede resultar tediosa. Puede escribir la misma consulta y el bucle foreach mucho más cómodamente con var. La palabra clave var no cambia los tipos de los objetos; solo indica al compilador que deduzca los tipos. Cambie el tipo de studentQuery y la variable de iteración group a var y vuelva a ejecutar la consulta. Tenga en cuenta que en el bucle foreach interior, la variable de iteración sigue teniendo como tipo Student y la consulta funciona igual que antes. Cambie la variable de iteración student a var y vuelva a ejecutar la consulta. Como puede ver, obtiene exactamente los mismos resultados.

    var studentQuery3 =
        from student in students
        group student by student.Last[0];
    
    foreach (var groupOfStudents in studentQuery3)
    {
        Console.WriteLine(groupOfStudents.Key);
        foreach (var student in groupOfStudents)
        {
            Console.WriteLine("   {0}, {1}",
                student.Last, student.First);
        }
    }
    
    // Output:
    // O
    //   Omelchenko, Svetlana
    //   O'Donnell, Claire
    // M
    //   Mortensen, Sven
    // G
    //   Garcia, Cesar
    //   Garcia, Debra
    //   Garcia, Hugo
    // F
    //   Fakhouri, Fadi
    //   Feng, Hanying
    // T
    //   Tucker, Lance
    //   Tucker, Michael
    // A
    //   Adams, Terry
    // Z
    //   Zabokritski, Eugene
    

    Para obtener más información sobre var, vea Variables locales con asignación implícita de tipos.

Para ordenar los grupos por su valor clave

  1. Al ejecutar la consulta anterior, observará que los grupos no están en orden alfabético. Para cambiar esto, debe especificar una cláusula orderby después de la cláusula group. Pero para usar una cláusula orderby, necesita primero un identificador que actúe como referencia a los grupos creados por la cláusula group. El identificador se especifica con la palabra clave into, de la manera siguiente:

    var studentQuery4 =
        from student in students
        group student by student.Last[0] into studentGroup
        orderby studentGroup.Key
        select studentGroup;
    
    foreach (var groupOfStudents in studentQuery4)
    {
        Console.WriteLine(groupOfStudents.Key);
        foreach (var student in groupOfStudents)
        {
            Console.WriteLine("   {0}, {1}",
                student.Last, student.First);
        }
    }
    
    // Output:
    //A
    //   Adams, Terry
    //F
    //   Fakhouri, Fadi
    //   Feng, Hanying
    //G
    //   Garcia, Cesar
    //   Garcia, Debra
    //   Garcia, Hugo
    //M
    //   Mortensen, Sven
    //O
    //   Omelchenko, Svetlana
    //   O'Donnell, Claire
    //T
    //   Tucker, Lance
    //   Tucker, Michael
    //Z
    //   Zabokritski, Eugene
    

    Cuando ejecute esta consulta, verá que los grupos aparecen ahora ordenados alfabéticamente.

Para incluir un identificador mediante let

  1. Puede usar la palabra clave let para incluir un identificador con cualquier resultado de la expresión en la expresión de consulta. Este identificador puede resultar cómodo, como en el ejemplo siguiente, o mejorar el rendimiento almacenando los resultados de una expresión para que no tenga que calcularse varias veces.

    // studentQuery5 is an IEnumerable<string>
    // This query returns those students whose
    // first test score was higher than their
    // average score.
    var studentQuery5 =
        from student in students
        let totalScore = student.Scores[0] + student.Scores[1] +
            student.Scores[2] + student.Scores[3]
        where totalScore / 4 < student.Scores[0]
        select student.Last + " " + student.First;
    
    foreach (string s in studentQuery5)
    {
        Console.WriteLine(s);
    }
    
    // Output:
    // Omelchenko Svetlana
    // O'Donnell Claire
    // Mortensen Sven
    // Garcia Cesar
    // Fakhouri Fadi
    // Feng Hanying
    // Garcia Hugo
    // Adams Terry
    // Zabokritski Eugene
    // Tucker Michael
    

    Para obtener más información, vea let (Cláusula).

Para usar la sintaxis de método en una expresión de consulta

  1. Tal y como se describe en Sintaxis de consulta y sintaxis de método en LINQ, algunas operaciones de consulta solo pueden expresarse con una sintaxis de método. El código siguiente calcula la puntuación total de cada Student de la secuencia de origen y luego llama al método Average() en los resultados de esa consulta para calcular la puntuación media de la clase.

    var studentQuery6 =
        from student in students
        let totalScore = student.Scores[0] + student.Scores[1] +
            student.Scores[2] + student.Scores[3]
        select totalScore;
    
    double averageScore = studentQuery6.Average();
    Console.WriteLine("Class average score = {0}", averageScore);
    
    // Output:
    // Class average score = 334.166666666667
    

Para transformar o proyectar en la cláusula select

  1. Es muy frecuente que una consulta genere una secuencia cuyos elementos difieren de los elementos de las secuencias de origen. Elimine la consulta y el bucle de ejecución anteriores o conviértalos en comentario, y reemplácelos por el código siguiente. Tenga en cuenta que la consulta devuelve una secuencia de cadenas (no Students) y este hecho se refleja en el bucle foreach.

    IEnumerable<string> studentQuery7 =
        from student in students
        where student.Last == "Garcia"
        select student.First;
    
    Console.WriteLine("The Garcias in the class are:");
    foreach (string s in studentQuery7)
    {
        Console.WriteLine(s);
    }
    
    // Output:
    // The Garcias in the class are:
    // Cesar
    // Debra
    // Hugo
    
  2. El código anterior en este tutorial indicaba que la puntuación media de la clase es aproximadamente 334. Para generar una secuencia de Students cuya puntuación total sea superior al promedio de la clase, junto con su Student ID, puede usar un tipo anónimo en la instrucción select:

Pasos siguientes

Cuando se haya familiarizado con los aspectos básicos del uso de consultas en C#, estará preparado para leer la documentación y ejemplos del tipo específico de proveedor LINQ que le interese:

LINQ to SQL

LINQ to DataSet

LINQ to XML (C#)

LINQ to Objects (C#)

Vea también