join-sats (C#-referens)

join Satsen är användbar för att associera element från olika källsekvenser som inte har någon direkt relation i objektmodellen. Det enda kravet är att elementen i varje källa delar ett värde som kan jämföras för likhet. En livsmedelsdistributör kan till exempel ha en lista över leverantörer av en viss produkt och en lista över köpare. En join sats kan till exempel användas för att skapa en lista över leverantörer och köpare av den produkten som alla befinner sig i samma angivna region.

En join sats tar två källsekvenser som indata. Elementen i varje sekvens måste antingen vara eller innehålla en egenskap som kan jämföras med en motsvarande egenskap i den andra sekvensen. join Satsen jämför de angivna nycklarna för likhet med hjälp av det särskilda equals nyckelordet. Alla kopplingar som utförs av join -satsen är likvärdiga. Formen på utdata från en join sats beror på vilken typ av koppling du utför. Följande är tre vanligaste kopplingstyper:

  • Inre koppling

  • Gruppkoppling

  • Vänster yttre koppling

Inre koppling

I följande exempel visas ett enkelt inre equijoin. Den här frågan skapar en platt sekvens med "produktnamn/kategori"-par. Samma kategoristräng visas i flera element. Om ett element från categories inte har någon matchande productsvisas inte den kategorin i resultatet.

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

Mer information finns i Utföra inre kopplingar.

Gruppkoppling

En join sats med ett into uttryck kallas gruppkoppling.

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 };

En gruppkoppling skapar en hierarkisk resultatsekvens som associerar element i den vänstra källsekvensen med ett eller flera matchande element i källsekvensen till höger. En gruppkoppling har ingen motsvarighet i relationella termer. det är i princip en sekvens av objektmatriser.

Om det inte finns några element från den högra källsekvensen som matchar ett element i den vänstra källan, join skapar satsen en tom matris för objektet. Därför är gruppkopplingen fortfarande i princip en inre-equijoin förutom att resultatsekvensen är organiserad i grupper.

Om du bara väljer resultatet av en gruppkoppling kan du komma åt objekten, men du kan inte identifiera den nyckel som de matchar på. Därför är det vanligtvis mer användbart att välja resultatet av gruppkopplingen till en ny typ som också har nyckelnamnet, som du ser i föregående exempel.

Du kan naturligtvis också använda resultatet av en gruppkoppling som generator för en annan underfråga:

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;

Mer information finns i Utför grupperade kopplingar.

Vänster yttre koppling

I en vänster yttre koppling returneras alla element i den vänstra källsekvensen, även om inga matchande element finns i den högra sekvensen. Om du vill utföra en vänster yttre koppling i LINQ använder du DefaultIfEmpty metoden i kombination med en gruppkoppling för att ange ett standardelement på höger sida för att skapa om ett element på vänster sida inte har några matchningar. Du kan använda null som standardvärde för alla referenstyper eller ange en användardefinierad standardtyp. I följande exempel visas en användardefinierad standardtyp:

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 };

Mer information finns i Utför yttre kopplingar till vänster.

Operatorn lika med

En join sats utför ett likvärdigt jobb. Med andra ord kan du bara basera matchningar på likheten mellan två nycklar. Andra typer av jämförelser som "större än" eller "inte lika med" stöds inte. För att klargöra att alla kopplingar är likvärdiga join använder satsen nyckelordet equals i stället för operatorn == . Nyckelordet equals kan bara användas i en join sats och det skiljer sig från operatorn == på några viktiga sätt. När du jämför strängar, equals har en överlagring att jämföra med värde och operatorn == använder referensjämlikhet. När båda sidor av jämförelsen har identiska strängvariabler equals och == når samma resultat: sant. Det beror på att när ett program deklarerar två eller flera motsvarande strängvariabler lagrar kompilatorn alla på samma plats. Detta kallas internering. En annan viktig skillnad är null-jämförelsen: null equals null utvärderas som false med equals operatorn i stället för == operatorn som utvärderar den som sann. Slutligen skiljer sig omfångsbeteendet: med equalsanvänder den vänstra nyckeln den yttre källsekvensen och den högra nyckeln förbrukar den inre källan. Den yttre källan finns bara i omfånget till vänster i equals och den inre källsekvensen finns bara i omfånget till höger.

Icke-equijoins

Du kan utföra icke-equijoins, korskopplingar och andra anpassade kopplingsåtgärder genom att använda flera from satser för att introducera nya sekvenser oberoende av varandra i en fråga. Mer information finns i Utföra anpassade kopplingsåtgärder.

Kopplingar till objektsamlingar jämfört med relationstabeller

I ett LINQ-frågeuttryck utförs kopplingsåtgärder på objektsamlingar. Objektsamlingar kan inte "kopplas" på exakt samma sätt som två relationstabeller. I LINQ krävs endast explicita join satser när två källsekvenser inte är knutna till någon relation. När du arbetar med LINQ till SQL representeras sekundärnyckeltabeller i objektmodellen som egenskaper för den primära tabellen. I northwind-databasen har tabellen Kund till exempel en sekundärnyckelrelation med tabellen Beställningar. När du mappar tabellerna till objektmodellen har klassen Kund en orderegenskap som innehåller samlingen Beställningar som är associerade med kunden. I själva verket har kopplingen redan gjorts åt dig.

Mer information om frågor mellan relaterade tabeller i linq till SQL finns i Så här: Mappa databasrelationer.

Sammansatta nycklar

Du kan testa likheten mellan flera värden med hjälp av en sammansatt nyckel. Mer information finns i Koppla med hjälp av sammansatta nycklar. Sammansatta nycklar kan också användas i en group sats.

Exempel

I följande exempel jämförs resultatet av en inre koppling, en gruppkoppling och en vänster yttre koppling i samma datakällor med hjälp av samma matchande nycklar. Lite extra kod läggs till i de här exemplen för att klargöra resultatet i konsolvisningen.

class JoinDemonstration
{
    #region Data

    class Product
    {
        public required string Name { get; init; }
        public required int CategoryID { get; init; }
    }

    class Category
    {
        public required string Name { get; init; }
        public required int ID { get; init; }
    }

    // Specify the first data source.
    List<Category> categories =
    [
        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 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();
    }

    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);
        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:");
            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.
*/

Kommentarer

En join sats som inte följs av into översätts till ett Join metodanrop. En join sats som följs av into översätts till ett GroupJoin metodanrop.

Se även