Cómo: Realizar operaciones de combinación personalizadas (Guía de programación de C#)

En este ejemplo se muestra cómo realizar operaciones de combinación que no son posibles con la cláusula join. En una expresión de consulta, la cláusula join se limita a combinaciones de igualdad, para las que está optimizada. Se trata, con diferencia, del tipo más habitual de operación de combinación. Al realizar una combinación de igualdad, probablemente siempre obtendrá el máximo rendimiento si utiliza la cláusula join.

Sin embargo, la cláusula join no se puede usar en los siguientes casos:

  • Cuando la combinación se predica en una expresión de desigualdad (que no es una combinación de igualdad).

  • Cuando la combinación se predica en más de una expresión de igualdad o desigualdad.

  • Cuando tiene que especificar una variable de rango temporal para la secuencia (interna) lateral derecha antes de la operación de combinación.

Para realizar combinaciones que no sean de igualdad, puede utilizar varias cláusulas from a fin de presentar cada origen de datos independientemente. A continuación, aplique una expresión de predicado en una cláusula where a la variable de rango de cada origen. La expresión también puede tener la forma de una llamada a un método.

NotaNota

No confunda este tipo de operación de combinación personalizada con el uso de varias cláusulas from para tener acceso a colecciones internas.Para obtener más información, consulte join (Cláusula, Referencia de C#).

Ejemplo

El primer método del ejemplo siguiente muestra una combinación cruzada simple. Las combinaciones cruzadas deben utilizarse con precaución porque pueden generar conjuntos de resultados muy grandes. Sin embargo, pueden resultar útiles en algunos escenarios para crear secuencias de origen en las que se ejecutan consultas adicionales.

El segundo método genera una secuencia de todos los productos cuyo id. de categoría se incluye en la lista de categorías que aparece a la izquierda. Tenga en cuenta el uso de la cláusula let y el método Contains para crear una matriz temporal. También es posible crear la matriz antes que la consulta y eliminar la primera cláusula from.

     class CustomJoins
     {

         #region Data

         class Product
         {
             public string Name { get; set; }
             public int CategoryID { get; set; }
         }

         class Category
         {
             public string Name { get; set; }
             public int ID { get; set; }
         }

         // Specify the first data source.
         List<Category> categories = new List<Category>()
 { 
     new Category(){Name="Beverages", ID=001},
     new Category(){ Name="Condiments", ID=002},
     new Category(){ Name="Vegetables", ID=003},         
 };

         // Specify the second data source.
         List<Product> products = new List<Product>()
{
   new Product{Name="Tea",  CategoryID=001},
   new Product{Name="Mustard", CategoryID=002},
   new Product{Name="Pickles", CategoryID=002},
   new Product{Name="Carrots", CategoryID=003},
   new Product{Name="Bok Choy", CategoryID=003},
   new Product{Name="Peaches", CategoryID=005},
   new Product{Name="Melons", CategoryID=005},
   new Product{Name="Ice Cream", CategoryID=007},
   new Product{Name="Mackerel", CategoryID=012},
 };
         #endregion

         static void Main()
         {
             CustomJoins app = new CustomJoins();
             app.CrossJoin();
             app.NonEquijoin();

             Console.WriteLine("Press any key to exit.");
             Console.ReadKey();
         }

         void CrossJoin()
         {
             var crossJoinQuery =
                 from c in categories
                 from p in products
                 select new { c.ID, p.Name };

             Console.WriteLine("Cross Join Query:");
             foreach (var v in crossJoinQuery)
             {
                 Console.WriteLine("{0,-5}{1}", v.ID, v.Name);
             }
         }

         void NonEquijoin()
         {
             var nonEquijoinQuery =
                 from p in products
                 let catIds = from c in categories
                              select c.ID
                 where catIds.Contains(p.CategoryID) == true 
                 select new { Product = p.Name, CategoryID = p.CategoryID };

             Console.WriteLine("Non-equijoin query:");
             foreach (var v in nonEquijoinQuery)
             {
                 Console.WriteLine("{0,-5}{1}", v.CategoryID, v.Product);
             }
         }
     }
     /* Output:
 Cross Join Query:
 1    Tea
 1    Mustard
 1    Pickles
 1    Carrots
 1    Bok Choy
 1    Peaches
 1    Melons
 1    Ice Cream
 1    Mackerel
 2    Tea
 2    Mustard
 2    Pickles
 2    Carrots
 2    Bok Choy
 2    Peaches
 2    Melons
 2    Ice Cream
 2    Mackerel
 3    Tea
 3    Mustard
 3    Pickles
 3    Carrots
 3    Bok Choy
 3    Peaches
 3    Melons
 3    Ice Cream
 3    Mackerel
 Non-equijoin query:
 1    Tea
 2    Mustard
 2    Pickles
 3    Carrots
 3    Bok Choy
 Press any key to exit.
      */

En el ejemplo siguiente, la consulta debe combinar dos secuencias en función de las claves coincidentes que, en el caso de la secuencia interna (lateral derecha), no se pueden obtener antes de la propia cláusula de combinación. Si esta combinación se realizara con una cláusula join, debería llamarse al método Split para cada elemento. El uso de varias cláusulas from permite que la consulta evite la sobrecarga de la llamada a método repetida. Sin embargo, dado que la cláusula join está optimizada, en este caso concreto podría ser aún más rápido que usar varias cláusulas from. Los resultados variarán dependiendo principalmente de lo costosa que sea la llamada a método.

class MergeTwoCSVFiles
{
    static void Main()
    {
        // See section Compiling the Code for information about the data files. 
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type. 
        // You could use var instead of an explicit type for the query.
        IEnumerable<Student> queryNamesScores =
            // Split each line in the data files into an array of strings. 
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            // Look for matching IDs from the two data files. 
            where x[2] == s[0]
            // If the IDs match, build a Student object. 
            select new Student()
            {
                FirstName = x[0],
                LastName = x[1],
                ID = Convert.ToInt32(x[2]),
                ExamScores = (from scoreAsText in s.Skip(1)
                              select Convert.ToInt32(scoreAsText)).
                              ToList()
            };

        // Optional. Store the newly created student objects in memory 
        // for faster access in future queries
        List<Student> students = queryNamesScores.ToList();

        foreach (var student in students)
        {
            Console.WriteLine("The average score of {0} {1} is {2}.",
                student.FirstName, student.LastName, student.ExamScores.Average());
        }

        //Keep console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public List<int> ExamScores { get; set; }
}

/* Output: 
    The average score of Omelchenko Svetlana is 82.5.
    The average score of O'Donnell Claire is 72.25.
    The average score of Mortensen Sven is 84.5.
    The average score of Garcia Cesar is 88.25.
    The average score of Garcia Debra is 67.
    The average score of Fakhouri Fadi is 92.25.
    The average score of Feng Hanying is 88.
    The average score of Garcia Hugo is 85.75.
    The average score of Tucker Lance is 81.75.
    The average score of Adams Terry is 85.25.
    The average score of Zabokritski Eugene is 83.
    The average score of Tucker Michael is 92.
 */

Compilar el código

  • Cree un proyecto de aplicación de consola de Visual Studio que tenga como destino .NET Framework 3.5 o una versión posterior. De manera predeterminada, el proyecto incluye una referencia a System.Core.dll y una directiva using para el espacio de nombres System.Linq.

  • Reemplace el código de la clase Program por el código del ejemplo anterior.

  • Siga las instrucciones que se describen en Cómo: Combinar contenido de archivos no similares (LINQ) para configurar los archivos de datos: scores.csv y names.csv.

  • Presione F5 para compilar y ejecutar el programa.

  • Presione cualquier tecla para salir de la ventana de consola.

Vea también

Tareas

Cómo: Ordenar los resultados de una cláusula join (Guía de programación de C#)

Referencia

join (Cláusula, Referencia de C#)

Conceptos

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

Operaciones de combinación