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 products
visas 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 equals
anvä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.