Realizar operaciones de combinación personalizadasPerform custom join operations

En este ejemplo se muestra cómo realizar operaciones de combinación que no son posibles con la cláusula join.This example shows how to perform join operations that aren't possible with the join clause. En una expresión de consulta, la cláusula join se limita a combinaciones de igualdad (y se optimiza para estas), que son con diferencia el tipo más común de operación de combinación.In a query expression, the join clause is limited to, and optimized for, equijoins, which are by far the most common type of join operation. Al realizar una combinación de igualdad, probablemente obtendrá siempre el mejor rendimiento con la cláusula join.When performing an equijoin, you will probably always get the best performance by using the join clause.

En cambio, la cláusula join no se puede usar en los siguientes casos:However, the join clause cannot be used in the following cases:

  • Cuando la combinación se basa en una expresión de desigualdad (una combinación que no es de igualdad).When the join is predicated on an expression of inequality (a non-equijoin).

  • Cuando la combinación se basa en más de una expresión de igualdad o desigualdad.When the join is predicated on more than one expression of equality or inequality.

  • Cuando tenga que introducir una variable de rango temporal para la secuencia de la derecha (interior) antes de la operación de combinación.When you have to introduce a temporary range variable for the right side (inner) sequence before the join operation.

Para realizar combinaciones que no son de igualdad, puede usar varias cláusulas from para presentar cada origen de datos de forma independiente.To perform joins that aren't equijoins, you can use multiple from clauses to introduce each data source independently. Después, aplique una expresión de predicado de una cláusula where a la variable de rango para cada origen.You then apply a predicate expression in a where clause to the range variable for each source. La expresión también puede adoptar la forma de una llamada de método.The expression also can take the form of a method call.

Nota

No confunda este tipo de operación de combinación personalizada con el uso de varias cláusulas from para acceder a colecciones internas.Don't confuse this kind of custom join operation with the use of multiple from clauses to access inner collections. Para obtener más información, vea join (Cláusula, Referencia de C#).For more information, see join clause.

EjemploExample

En el primer método del siguiente ejemplo se muestra una combinación cruzada sencilla.The first method in the following example shows a simple cross join. Las combinaciones cruzadas se deben usar con precaución porque pueden generar conjuntos de resultados muy grandes.Cross joins must be used with caution because they can produce very large result sets. En cambio, pueden resultar útiles en algunos escenarios para crear secuencias de origen en las que se ejecutan consultas adicionales.However, they can be useful in some scenarios for creating source sequences against which additional queries are run.

El segundo método genera una secuencia de todos los productos cuyo identificador de categoría aparece en la lista de categorías en el lado izquierdo.The second method produces a sequence of all the products whose category ID is listed in the category list on the left side. Observe el uso de la cláusula let y el método Contains para crear una matriz temporal.Note the use of the let clause and the Contains method to create a temporary array. También se puede crear la matriz antes de la consulta y eliminar la primera cláusula from.It also is possible to create the array before the query and eliminate the first from clause.

     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($"{v.ID:-5}{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($"{v.CategoryID:-5}{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.
      */

EjemploExample

En el ejemplo siguiente, la consulta debe combinar dos secuencias basándose en las claves coincidentes que, en el caso de la secuencia interna (lado derecho), no se pueden obtener antes de la propia cláusula de combinación.In the following example, the query must join two sequences based on matching keys that, in the case of the inner (right side) sequence, cannot be obtained prior to the join clause itself. Si esta combinación se realizara con una cláusula join, se tendría que llamar al método Split para cada elemento.If this join were performed with a join clause, then the Split method would have to be called for each element. El uso de varias cláusulas from permite a la consulta evitar la sobrecarga que supone la llamada al método repetida.The use of multiple from clauses enables the query to avoid the overhead of the repeated method call. En cambio, puesto que join está optimizada, en este caso particular puede que siga resultando más rápido que usar varias cláusulas from.However, since join is optimized, in this particular case it might still be faster than using multiple from clauses. Los resultados variarán dependiendo principalmente de cuántos recursos requiera la llamada de método.The results will vary depending primarily on how expensive the method call is.

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 {student.FirstName} {student.LastName} is 
                {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.
 */

Vea tambiénSee also