join (Cláusula, Referencia de C#)

La cláusula join sirve para asociar elementos de secuencias de origen diferentes que no tienen ninguna relación directa en el modelo de objetos. El único requisito es que los elementos de cada origen compartan algún valor para el que se pueda comparar la igualdad. Por ejemplo, un distribuidor de comida podría tener una lista de proveedores de cierto producto y una lista de compradores. Se puede utilizar una cláusula join, por ejemplo, para crear una lista de proveedores y compradores de ese producto que se encuentren en la misma región especificada.

Una cláusula join usa dos secuencias de origen como entrada. Los elementos de cada secuencia deben ser o contener una propiedad que se pueda comparar con una propiedad correspondiente en la otra secuencia. La cláusula join compara la igualdad de las claves especificadas utilizando la palabra clave especial equals. Todas las combinaciones realizadas por la cláusula join son combinaciones de igualdad. La forma del resultado de una cláusula join depende del tipo de combinación específico que se está realizando. A continuación se indican los tres tipos de combinación más comunes:

  • Combinación interna

  • Combinación agrupada

  • Combinación externa izquierda

Combinación interna

En el ejemplo siguiente se muestra una combinación de igualdad interna simple. Esta consulta genera una secuencia simple de pares de "nombre de producto / categoría". La misma cadena de categoría aparecerá en varios elementos. Si un elemento de categories no tiene ninguna correspondencia en products, esa categoría no aparecerá en los resultados.

var innerJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID
    select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence

Para obtener más información, vea Cómo: Realizar combinaciones internas (Guía de programación de C#).

Group Join

Una cláusula join con una expresión into se denomina unión agrupada.

var innerGroupJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    select new { CategoryName = category.Name, Products = prodGroup };

Una unión agrupada genera una secuencia de resultados jerárquica, que asocia los elementos de la secuencia de origen izquierda a uno o más elementos coincidentes de la secuencia de origen derecha. Una unión agrupada no tiene equivalente en términos relacionales; es esencialmente una secuencia de matrices de objetos.

Si no se encuentran elementos en la secuencia de origen derecha que coincidan con un elemento del origen izquierdo, la cláusula join genera una matriz vacía para ese elemento. Por consiguiente, la combinación agrupada básicamente sigue siendo una combinación de igualdad interna, con la diferencia de que la secuencia resultante se organiza en grupos.

Si simplemente selecciona los resultados de una combinación agrupada, puede tener acceso a los elementos, pero no puede identificar la clave según la cual coinciden. Por lo tanto, generalmente es más útil seleccionar los resultados de la combinación agrupada en un nuevo tipo que también incluya el nombre de la clave, como se mostraba en el ejemplo anterior.

Por supuesto que también se puede usar el resultado de una combinación agrupada como generador de otra subconsulta:

var innerGroupJoinQuery2 =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from prod2 in prodGroup
    where prod2.UnitPrice > 2.50M
    select prod2;

Para obtener más información, vea Cómo: Realizar combinaciones agrupadas (Guía de programación de C#).

Combinación externa izquierda

En una combinación externa izquierda, se devuelven todos los elementos de la secuencia de origen izquierda, aun cuando no haya elementos correspondientes en la secuencia derecha. Para realizar una combinación externa izquierda en LINQ, utilice el método DefaultIfEmpty junto con una combinación agrupada para especificar el elemento derecho predeterminado que se generará si un elemento izquierdo no tiene coincidencias. Puede utilizar null como valor predeterminado para cualquier tipo de referencia o puede especificar un tipo predeterminado definido por el usuario. En el ejemplo siguiente se muestra un tipo predeterminado definido por el usuario:

var leftOuterJoinQuery =
    from category in categories
    join prod in products on category.ID equals prod.CategoryID into prodGroup
    from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 })
    select new { CatName = category.Name, ProdName = item.Name };

Para obtener más información, vea Cómo: Realizar operaciones de combinación externa izquierda (Guía de programación de C#).

Operador de igualdad

Una cláusula join realiza una combinación de igualdad. En otras palabras, las coincidencias sólo se pueden basar en la igualdad de dos claves. No se admiten otros tipos de comparaciones, como "mayor que" o "distinto de". Para que quede claro que todas las combinaciones son combinaciones de igualdad, la cláusula join utiliza la palabra clave equals en lugar del operador ==. La palabra clave equals sólo se puede utilizar en una cláusula join y difiere del operador == en un aspecto importante. Con equals, la clave izquierda utiliza la secuencia de origen externa y la clave derecha utiliza el origen interno. El origen externo sólo está en ámbito en el lado izquierdo de equals y la secuencia de origen interna sólo está en ámbito en el lado derecho.

Combinaciones de desigualdad

Puede realizar combinaciones de desigualdad, combinaciones cruzadas y otras operaciones de combinación personalizadas utilizando varias cláusulas from para incluir nuevas secuencias en una consulta de manera independiente. Para obtener más información, vea Cómo: Realizar operaciones de combinación personalizadas (Guía de programación de C#).

Combinaciones de colecciones de objetos frente atablas relacionales

En una expresión de consulta LINQ, las operaciones de combinación se realizan en colecciones de objetos. En realidad, las colecciones de objetos no se pueden "combinar" exactamente de la misma manera que dos tablas relacionales. En LINQ, sólo se requieren cláusulas join explícitas cuando dos secuencias de origen no están unidas por ninguna relación. Al trabajar con LINQ to SQL, las tablas de clave externa se representan en el modelo de objetos como propiedades de la tabla principal. Por ejemplo, en la base de datos Northwind, la tabla de clientes tiene una relación de clave externa con la tabla de pedidos. Al asignar las tablas al modelo de objetos, la clase Customer tiene una propiedad Orders que contiene la colección de pedidos asociados a ese cliente. De hecho, la combinación se ha realizado automáticamente.

Para obtener más información sobre cómo realizar consultas en tablas relacionadas en el contexto de LINQ to SQL, vea Cómo: Asignar relaciones de base de datos.

Claves compuestas

Puede comprobar la igualdad de varios valores mediante el uso de una clave compuesta. Para obtener más información, vea Cómo: Realizar una unión usando claves compuestas (Guía de programación de C#). Las claves compuestas también se pueden utilizar en una cláusula group.

Ejemplo

En el ejemplo siguiente se comparan los resultados de una combinación interna, una combinación agrupada y una combinación externa izquierda en los mismos orígenes de datos, utilizando las mismas claves coincidentes. En estos ejemplos se ha agregado código adicional para que los resultados se vean más claros en la consola.

class JoinDemonstration
{
    #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},
        new Category() {  Name="Grains", ID=004},
        new Category() {  Name="Fruit", ID=005}            
    };

    // Specify the second data source.
    List<Product> products = new List<Product>()
   {
      new Product{Name="Cola",  CategoryID=001},
      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},
    };
    #endregion


    static void Main(string[] args)
    {
        JoinDemonstration app = new JoinDemonstration();

        app.InnerJoin();
        app.GroupJoin();
        app.GroupInnerJoin();
        app.GroupJoin3();
        app.LeftOuterJoin();
        app.LeftOuterJoin2();

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

    void InnerJoin()
    {
        // Create the query that selects  
        // a property from each element. 
        var innerJoinQuery =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID
           select new { Category = category.ID, Product = prod.Name };

        Console.WriteLine("InnerJoin:");
        // Execute the query. Access results  
        // with a simple foreach statement. 
        foreach (var item in innerJoinQuery)
        {
            Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
        }
        Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
        Console.WriteLine(System.Environment.NewLine);

    }

    void GroupJoin()
    {
        // This is a demonstration query to show the output 
        // of a "raw" group join. A more typical group join 
        // is shown in the GroupInnerJoin method. 
        var groupJoinQuery =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID into prodGroup
           select prodGroup;

        // Store the count of total items (for demonstration only). 
        int totalItems = 0;

        Console.WriteLine("Simple GroupJoin:");

        // A nested foreach statement is required to access group items. 
        foreach (var prodGrouping in groupJoinQuery)
        {
            Console.WriteLine("Group:");
            foreach (var item in prodGrouping)
            {
                totalItems++;
                Console.WriteLine("   {0,-10}{1}", item.Name, item.CategoryID);
            }
        }
        Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
        Console.WriteLine(System.Environment.NewLine);
    }

    void GroupInnerJoin()
    {
        var groupJoinQuery2 =
            from category in categories
            orderby category.ID
            join prod in products on category.ID equals prod.CategoryID into prodGroup
            select new
            {
                Category = category.Name,
                Products = from prod2 in prodGroup
                           orderby prod2.Name
                           select prod2
            };

        //Console.WriteLine("GroupInnerJoin:");
        int totalItems = 0;

        Console.WriteLine("GroupInnerJoin:");
        foreach (var productGroup in groupJoinQuery2)
        {
            Console.WriteLine(productGroup.Category);
            foreach (var prodItem in productGroup.Products)
            {
                totalItems++;
                Console.WriteLine("  {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
            }
        }
        Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
        Console.WriteLine(System.Environment.NewLine);
    }

    void GroupJoin3()
    {

        var groupJoinQuery3 =
            from category in categories
            join product in products on category.ID equals product.CategoryID into prodGroup
            from prod in prodGroup
            orderby prod.CategoryID
            select new { Category = prod.CategoryID, ProductName = prod.Name };

        //Console.WriteLine("GroupInnerJoin:");
        int totalItems = 0;

        Console.WriteLine("GroupJoin3:");
        foreach (var item in groupJoinQuery3)
        {
            totalItems++;
            Console.WriteLine("   {0}:{1}", item.ProductName, item.Category);
        }

        Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems, groupJoinQuery3.Count());
        Console.WriteLine(System.Environment.NewLine);
    }

    void LeftOuterJoin()
    {
        // Create the query. 
        var leftOuterQuery =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID into prodGroup
           select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });

        // Store the count of total items (for demonstration only). 
        int totalItems = 0;

        Console.WriteLine("Left Outer Join:");

        // A nested foreach statement  is required to access group items 
        foreach (var prodGrouping in leftOuterQuery)
        {
            Console.WriteLine("Group:", prodGrouping.Count());
            foreach (var item in prodGrouping)
            {
                totalItems++;
                Console.WriteLine("  {0,-10}{1}", item.Name, item.CategoryID);
            }
        }
        Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
        Console.WriteLine(System.Environment.NewLine);
    }

    void LeftOuterJoin2()
    {
        // Create the query. 
        var leftOuterQuery2 =
           from category in categories
           join prod in products on category.ID equals prod.CategoryID into prodGroup
           from item in prodGroup.DefaultIfEmpty()
           select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };

        Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());
        // Store the count of total items 
        int totalItems = 0;

        Console.WriteLine("Left Outer Join 2:");

        // Groups have been flattened. 
        foreach (var item in leftOuterQuery2)
        {
            totalItems++;
            Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
        }
        Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", totalItems);
    }
}
/*Output:

InnerJoin:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Peaches   5
Melons    5
InnerJoin: 8 items in 1 group.


Unshaped GroupJoin:
Group:
    Cola      1
    Tea       1
Group:
    Mustard   2
    Pickles   2
Group:
    Carrots   3
    Bok Choy  3
Group:
Group:
    Peaches   5
    Melons    5
Unshaped GroupJoin: 8 items in 5 unnamed groups


GroupInnerJoin:
Beverages
    Cola       1
    Tea        1
Condiments
    Mustard    2
    Pickles    2
Vegetables
    Bok Choy   3
    Carrots    3
Grains
Fruit
    Melons     5
    Peaches    5
GroupInnerJoin: 8 items in 5 named groups


GroupJoin3:
    Cola:1
    Tea:1
    Mustard:2
    Pickles:2
    Carrots:3
    Bok Choy:3
    Peaches:5
    Melons:5
GroupJoin3: 8 items in 1 group


Left Outer Join:
Group:
    Cola      1
    Tea       1
Group:
    Mustard   2
    Pickles   2
Group:
    Carrots   3
    Bok Choy  3
Group:
    Nothing!  4
Group:
    Peaches   5
    Melons    5
LeftOuterJoin: 9 items in 5 groups


LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola      1
Tea       1
Mustard   2
Pickles   2
Carrots   3
Bok Choy  3
Nothing!  4
Peaches   5
Melons    5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/

Comentarios

Una cláusula join que no va seguida de into se convierte en una llamada al método Join``4. Una cláusula join que va seguida de into se convierte en una llamada al método GroupJoin``4.

Vea también

Tareas

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

Cómo: Realizar combinaciones internas (Guía de programación de C#)

Cómo: Realizar combinaciones agrupadas (Guía de programación de C#)

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

Cómo: Realizar una unión usando claves compuestas (Guía de programación de C#)

Cómo: Instalar bases de datos de ejemplo

Referencia

group (Cláusula, Referencia de C#)

Conceptos

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

Operaciones de combinación

Otros recursos

Palabras clave de consultas (Referencia de C#)