Professional Documents
Culture Documents
Restriction Operators: Where - Simple 1
Restriction Operators: Where - Simple 1
Where - Simple 1
This sample uses where to find all elements of an array less than 5.
var lowNums =
from n in numbers
where n < 5
select n;
Result
Numbers < 5:
4
1
3
2
0
Where - Simple 2
This sample uses where to find all products that are out of stock.
var soldOutProducts =
from p in products
where p.UnitsInStock == 0
select p;
Where - Simple 3
This sample uses where to find all products that are in stock and cost more than 3.00 per unit.
var expensiveInStockProducts =
from p in products
where p.UnitsInStock > 0 && p.UnitPrice > 3.00M
select p;
Result
Where - Drilldown
This sample uses where to find all customers in Washington and then uses the resulting sequence
to drill down into their orders.
Result
Where - Indexed
This sample demonstrates an indexed Where clause that returns digits whose name is shorter
than their value.
Console.WriteLine("Short digits:");
foreach (var d in shortDigits)
{
Console.WriteLine("The word {0} is shorter than its value.", d);
}
}
Result
Short digits:
The word five is shorter than its value.
The word six is shorter than its value.
The word seven is shorter than its value.
The word eight is shorter than its value.
The word nine is shorter than its value.
Projection Operators
Select - Simple 1
This sample uses select to produce a sequence of ints one higher than those in an existing array
of ints.
Console.WriteLine("Numbers + 1:");
foreach (var i in numsPlusOne)
{
Console.WriteLine(i);
}
}
Result
Numbers + 1:
6
5
2
4
10
9
7
8
3
1
Select - Simple 2
This sample uses select to return a sequence of just the names of a list of products.
Console.WriteLine("Product Names:");
foreach (var productName in productNames)
{
Console.WriteLine(productName);
}
}
Result
Product Names:
Chai
Chang
Aniseed Syrup
Select - Transformation
This sample uses select to produce a sequence of strings representing the text version of a
sequence of ints.
var textNums =
from n in numbers
select strings[n];
Console.WriteLine("Number strings:");
foreach (var s in textNums)
{
Console.WriteLine(s);
}
}
Result
Number strings:
five
four
one
three
nine
eight
six
seven
two
zero
This sample uses select to produce a sequence of the uppercase and lowercase versions of each
word in the original array.
var upperLowerWords =
from w in words
select new { Upper = w.ToUpper(), Lower = w.ToLower() };
Result
This sample uses select to produce a sequence containing text representations of digits and
whether their length is even or odd.
var digitOddEvens =
from n in numbers
select new { Digit = strings[n], Even = (n % 2 == 0) };
Result
This sample uses select to produce a sequence containing some properties of Products, including
UnitPrice which is renamed to Price in the resulting type.
var productInfos =
from p in products
select new { p.ProductName, p.Category, Price = p.UnitPrice };
Console.WriteLine("Product Info:");
foreach (var productInfo in productInfos)
{
Console.WriteLine("{0} is in the category {1} and costs {2} per
unit.", productInfo.ProductName, productInfo.Category, productInfo.Price);
}
}
Result
Product Info:
Chai is in the category Beverages and costs 18.0000 per unit.
Chang is in the category Beverages and costs 19.0000 per unit.
Select - Indexed
This sample uses an indexed Select clause to determine if the value of ints in an array match their
position in the array.
Console.WriteLine("Number: In-place?");
foreach (var n in numsInPlace)
{
Console.WriteLine("{0}: {1}", n.Num, n.InPlace);
}
}
Result
Number: In-place?
5: False
4: False
1: False
3: True
9: False
8: False
6: True
7: True
2: False
0: False
Select - Filtered
This sample combines select and where to make a simple query that returns the text form of each
digit less than 5.
var lowNums =
from n in numbers
where n < 5
select digits[n];
Result
Numbers < 5:
four
one
three
two
zero
This sample uses a compound from clause to make a query that returns all pairs of numbers from
both arrays such that the number from numbersA is less than the number from numbersB.
public void Linq14()
{
int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 };
int[] numbersB = { 1, 3, 5, 7, 8 };
var pairs =
from a in numbersA
from b in numbersB
where a < b
select new { a, b };
Result
This sample uses a compound from clause to select all orders where the order total is less than
500.00.
var orders =
from c in customers
from o in c.Orders
where o.Total < 500.00M
select new { c.CustomerID, o.OrderID, o.Total };
ObjectDumper.Write(orders);
}
Result
var orders =
from c in customers
from o in c.Orders
where o.OrderDate >= new DateTime(1998, 1, 1)
select new { c.CustomerID, o.OrderID, o.OrderDate };
ObjectDumper.Write(orders);
}
Result
SelectMany - Indexed
This sample uses an indexed SelectMany clause to select all orders, while referring to customers
by the order in which they are returned from the query.
var customerOrders =
customers.SelectMany(
(cust, custIndex) =>
cust.Orders.Select(o => "Customer #" + (custIndex + 1) +
" has an order with OrderID " +
o.OrderID));
ObjectDumper.Write(customerOrders);
}
Result
Take - Simple
This sample uses Take to get only the first 3 elements of the array.
{
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var first3Numbers = numbers.Take(3);
Console.WriteLine("First 3 numbers:");
foreach (var n in first3Numbers)
{
Console.WriteLine(n);
}
}
Result
First 3 numbers:
5
4
1
Take - Nested
This sample uses Take to get the first 3 orders from customers in Washington.
{
List<Customer> customers = GetCustomerList();
var first3WAOrders = (
from c in customers
from o in c.Orders
where c.Region == "WA"
select new { c.CustomerID, o.OrderID, o.OrderDate })
.Take(3);
Console.WriteLine("First 3 orders in WA:");
foreach (var order in first3WAOrders)
{
ObjectDumper.Write(order);
}
}
Result
Skip - Simple
This sample uses Skip to get all but the first 4 elements of the array.
Result
Skip - Nested
This sample uses Take to get all but the first 2 orders from customers in Washington.
var waOrders =
from c in customers
from o in c.Orders
where c.Region == "WA"
select new { c.CustomerID, o.OrderID, o.OrderDate };
Result
All but first 2 orders in WA:
CustomerID=TRAIH OrderID=10574 OrderDate=6/19/1997
CustomerID=TRAIH OrderID=10577 OrderDate=6/23/1997
TakeWhile - Simple
This sample uses TakeWhile to return elements starting from the beginning of the array until a
number is hit that is not less than 6.
Result
TakeWhile - Indexed
This sample uses TakeWhile to return elements starting from the beginning of the array until a
number is hit that is less than its position in the array.
Result
This sample uses SkipWhile to get the elements of the array starting from the first element
divisible by 3.
Result
SkipWhile - Indexed
This sample uses SkipWhile to get the elements of the array starting from the first element less
than its position.
Result
All elements starting from first element less than its position:
1
3
9
8
6
7
2
0
Ordering Operators
OrderBy - Simple 1
var sortedWords =
from w in words
orderby w
select w;
Result
OrderBy - Simple 2
var sortedWords =
from w in words
orderby w.Length
select w;
Result
OrderBy - Simple 3
var sortedProducts =
from p in products
orderby p.ProductName
select p;
ObjectDumper.Write(sortedProducts);
}
Result
OrderBy - Comparer
This sample uses an OrderBy clause with a custom comparer to do a case-insensitive sort of the
words in an array.
ObjectDumper.Write(sortedWords);
}
Result
AbAcUs
aPPLE
BlUeBeRrY
bRaNcH
cHeRry
ClOvEr
OrderByDescending - Simple 1
This sample uses orderby and descending to sort a list of doubles from highest to lowest.
var sortedDoubles =
from d in doubles
orderby d descending
select d;
Result
This sample uses orderby to sort a list of products by units in stock from highest to lowest.
var sortedProducts =
from p in products
orderby p.UnitsInStock descending
select p;
ObjectDumper.Write(sortedProducts);
}
Result
OrderByDescending - Comparer
This sample uses an OrderBy clause with a custom comparer to do a case-insensitive descending
sort of the words in an array.
ObjectDumper.Write(sortedWords);
}
Result
ClOvEr
cHeRry
bRaNcH
BlUeBeRrY
aPPLE
AbAcUs
ThenBy - Simple
This sample uses a compound orderby to sort a list of digits, first by length of their name, and
then alphabetically by the name itself.
var sortedDigits =
from d in digits
orderby d.Length, d
select d;
Console.WriteLine("Sorted digits:");
foreach (var d in sortedDigits)
{
Console.WriteLine(d);
}
}
Result
Sorted digits:
one
six
two
five
four
nine
zero
eight
seven
three
ThenBy - Comparer
This sample uses an OrderBy and a ThenBy clause with a custom comparer to sort first by word
length and then by a case-insensitive sort of the words in an array.
var sortedWords =
words.OrderBy(a => a.Length)
.ThenBy(a => a, new CaseInsensitiveComparer());
ObjectDumper.Write(sortedWords);
}
Result
aPPLE
AbAcUs
bRaNcH
cHeRry
ClOvEr
BlUeBeRrY
ThenByDescending - Simple
This sample uses a compound orderby to sort a list of products, first by category, and then by
unit price, from highest to lowest.
var sortedProducts =
from p in products
orderby p.Category, p.UnitPrice descending
select p;
ObjectDumper.Write(sortedProducts);
}
Result
ThenByDescending - Comparer
This sample uses an OrderBy and a ThenBy clause with a custom comparer to sort first by word
length and then by a case-insensitive descending sort of the words in an array.
var sortedWords =
words.OrderBy(a => a.Length)
.ThenByDescending(a => a, new CaseInsensitiveComparer());
ObjectDumper.Write(sortedWords);
}
Result
aPPLE
ClOvEr
cHeRry
bRaNcH
AbAcUs
BlUeBeRrY
Reverse
This sample uses Reverse to create a list of all digits in the array whose second letter is 'i' that is
reversed from the order in the original array.
var reversedIDigits = (
from d in digits
where d[1] == 'i'
select d)
.Reverse();
Result
Grouping Operators
GroupBy - Simple 1
This sample uses group by to partition a list of numbers by their remainder when divided by 5.
var numberGroups =
from n in numbers
group n by n % 5 into g
select new { Remainder = g.Key, Numbers = g };
Result
GroupBy - Simple 2
This sample uses group by to partition a list of words by their first letter.
var wordGroups =
from w in words
group w by w[0] into g
select new { FirstLetter = g.Key, Words = g };
Result
Words that start with the letter 'b':
blueberry
banana
Words that start with the letter 'c':
chimpanzee
cheese
Words that start with the letter 'a':
abacus
apple
GroupBy - Simple 3
var orderGroups =
from p in products
group p by p.Category into g
select new { Category = g.Key, Products = g };
ObjectDumper.Write(orderGroups, 1);
}
Result
Category=Beverages Products=...
Products: ProductID=1 ProductName=Chai Category=Beverages UnitPrice=18.0000
UnitsInStock=39
GroupBy - Nested
This sample uses group by to partition a list of each customer's orders, first by year, and then by
month.
var customerOrderGroups =
from c in customers
select
new
{
c.CompanyName,
YearGroups =
from o in c.Orders
group o by o.OrderDate.Year into yg
select
new
{
Year = yg.Key,
MonthGroups =
from o in yg
group o by o.OrderDate.Month into mg
select new { Month = mg.Key, Orders = mg }
}
};
ObjectDumper.Write(customerOrderGroups, 3);
}
Result
GroupBy - Comparer
This sample uses GroupBy to partition trimmed elements of an array using a custom comparer
that matches words that are anagrams of each other.
ObjectDumper.Write(orderGroups, 1);
}
Result
...
from
form
...
salt
last
...
earn
near
This sample uses GroupBy to partition trimmed elements of an array using a custom comparer
that matches words that are anagrams of each other, and then converts the results to uppercase.
ObjectDumper.Write(orderGroups, 1);
}
Result
...
FROM
FORM
...
SALT
LAST
...
EARN
NEAR
Set Operators
Distinct - 1
This sample uses Distinct to remove duplicate elements in a sequence of factors of 300.
Result
Distinct - 2
var categoryNames = (
from p in products
select p.Category)
.Distinct();
Console.WriteLine("Category names:");
foreach (var n in categoryNames)
{
Console.WriteLine(n);
}
}
Result
Category names:
Beverages
Condiments
Produce
Meat/Poultry
Seafood
Dairy Products
Confections
Grains/Cereals
Union - 1
This sample uses Union to create one sequence that contains the unique values from both arrays.
Result
Union - 2
This sample uses Union to create one sequence that contains the unique first letter from both
product and customer names.
var productFirstChars =
from p in products
select p.ProductName[0];
var customerFirstChars =
from c in customers
select c.CompanyName[0];
Result
Intersect - 1
This sample uses Intersect to create one sequence that contains the common values shared by
both arrays.
public void Linq50()
{
int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 };
int[] numbersB = { 1, 3, 5, 7, 8 };
Result
Intersect - 2
This sample uses Intersect to create one sequence that contains the common first letter from both
product and customer names.
var productFirstChars =
from p in products
select p.ProductName[0];
var customerFirstChars =
from c in customers
select c.CompanyName[0];
Result
Except - 1
This sample uses Except to create a sequence that contains the values from numbersAthat are not
also in numbersB.
Result
Except - 2
This sample uses Except to create one sequence that contains the first letters of product names
that are not also first letters of customer names.
var productFirstChars =
from p in products
select p.ProductName[0];
var customerFirstChars =
from c in customers
select c.CompanyName[0];
Result
First letters from Product names, but not from Customer names:
U
J
Z
Conversion Operators
ToArray
var sortedDoubles =
from d in doubles
orderby d descending
select d;
var doublesArray = sortedDoubles.ToArray();
Result
ToList
var sortedWords =
from w in words
orderby w
select w;
var wordList = sortedWords.ToList();
Result
ToDictionary
This sample uses ToDictionary to immediately evaluate a sequence and a related key expression
into a dictionary.
Result
OfType
This sample uses OfType to return only the elements of the array that are of type double.
Result
Element Operators
First - Simple
This sample uses First to return the first matching element as a Product, instead of as a sequence
containing a Product.
Product product12 = (
from p in products
where p.ProductID == 12
select p)
.First();
ObjectDumper.Write(product12);
Result
ProductID=12 ProductName=Queso Manchego La Pastora Category=Dairy Products
UnitPrice=38.0000 UnitsInStock=86
First - Condition
This sample uses First to find the first element in the array that starts with 'o'.
Result
FirstOrDefault - Simple
This sample uses FirstOrDefault to try to return the first element of the sequence, unless there are
no elements, in which case the default value for that type is returned.
Result
FirstOrDefault - Condition
This sample uses FirstOrDefault to return the first product whose ProductID is 789 as a single
Product object, unless there is no match, in which case null is returned.
ElementAt
This sample uses ElementAt to retrieve the second number greater than 5 from an array.
Result
Generation Operators
Range
This sample uses Range to generate a sequence of numbers from 100 to 149 that is used to find
which numbers in that range are odd and even.
Result
Repeat
This sample uses Repeat to generate a sequence that contains the number 7 ten times.
Result
7
7
7
Quantifiers
Any - Simple
This sample uses Any to determine if any of the words in the array contain the substring 'ei'.
Result
There is a word that contains in the list that contains 'ei': True
Any - Grouped
This sample uses Any to return a grouped a list of products only for categories that have at least
one product that is out of stock.
ObjectDumper.Write(productGroups, 1);
}
Result
Category=Condiments Products=...
Products: ProductID=3 ProductName=Aniseed Syrup Category=Condiments
UnitPrice=10.0000 UnitsInStock=13
All - Simple
This sample uses All to determine whether an array contains only odd numbers.
Result
All - Grouped
This sample uses All to return a grouped a list of products only for categories that have all of
their products in stock.
Result
Category=Beverages Products=...
Products: ProductID=1
ProductName=Chai Category=Beverages UnitPrice=18.0000 UnitsInStock=39
Products: ProductID=2 ProductName=Chang
Category=Beverages UnitPrice=19.0000 UnitsInStock=17
Aggregator Operators
Count - Simple
This sample uses Count to get the number of unique factors of 300.
Result
Count - Conditional
This sample uses Count to get the number of odd ints in the array.
Result
Count - Nested
This sample uses Count to return a list of customers and how many orders each has.
public void Linq76()
{
List<Customer> customers = GetCustomerList();
var orderCounts =
from c in customers
select new { c.CustomerID, OrderCount = c.Orders.Count() };
ObjectDumper.Write(orderCounts);
}
Result
Count - Grouped
This sample uses Count to return a list of categories and how many products each have.
var categoryCounts =
from p in products
group p by p.Category into g
select new { Category = g.Key, ProductCount = g.Count() };
ObjectDumper.Write(categoryCounts
}
Result
Category=Beverages ProductCount=12
Category=Condiments ProductCount=12
Category=Produce ProductCount=5
Category=Meat/Poultry ProductCount=6
Category=Seafood ProductCount=12
Category=Dairy Products ProductCount=10
ProductCount=13
Category=Confections ProductCount=7
Category=Grains/Cereals
Sum - Simple
This sample uses Sum to get the total of the numbers in an array.
Result
Sum - Projection
This sample uses Sum to get the total number of characters of all words in the array.
Result
Sum - Grouped
This sample uses Sum to get the total units in stock for each product category.
var categories =
from p in products
group p by p.Category into g
select new { Category = g.Key, TotalUnitsInStock = g.Sum(p =>
p.UnitsInStock) };
ObjectDumper.Write(categories);
}
Result
Category=Beverages TotalUnitsInStock=559
Category=Condiments TotalUnitsInStock=507
Category=Produce TotalUnitsInStock=100
Category=Meat/Poultry TotalUnitsInStock=165
Category=Seafood TotalUnitsInStock=701
Category=Dairy Products TotalUnitsInStock=393
TotalUnitsInStock=386
Category=Confections TotalUnitsInStock=308
Category=Grains/Cereals
Min - Simple
Result
Min - Projection
This sample uses Min to get the length of the shortest word in an array.
Result
Min - Grouped
This sample uses Min to get the cheapest price among each category's products.
ObjectDumper.Write(categories);
}
Result
Category=Beverages CheapestPrice=4.5000
Category=Condiments CheapestPrice=10.0000
Category=Produce CheapestPrice=10.0000
Category=Meat/Poultry CheapestPrice=7.4500
Category=Seafood CheapestPrice=6.0000
Category=Dairy Products CheapestPrice=2.5000
Category=Confections CheapestPrice=9.2000
Category=Grains/Cereals CheapestPrice=7.0000
Min - Elements
This sample uses Min to get the products with the cheapest price in each category.
var categories =
from p in products
group p by p.Category into g
let minPrice = g.Min(p => p.UnitPrice)
select new { Category = g.Key, CheapestProducts = g.Where(p =>
p.UnitPrice == minPrice) };
ObjectDumper.Write(categories, 1);
}
Result
Category=Beverages CheapestProducts=...
CheapestProducts:
ProductID=24 ProductName=Guaraná Fantástica Category=Beverages UnitPrice=4.500
0 UnitsInStock=20
Max - Simple
This sample uses Max to get the highest number in an array.
Result
Max - Projection
This sample uses Max to get the length of the longest word in an array.
Result
Max - Grouped
This sample uses Max to get the most expensive price among each category's products.
var categories =
from p in products
group p by p.Category into g
select new { Category = g.Key, MostExpensivePrice = g.Max(p =>
p.UnitPrice) };
ObjectDumper.Write(categories);
}
Result
Category=Beverages MostExpensivePrice=263.5000
Category=Condiments MostExpensivePrice=43.9000
Category=Produce MostExpensivePrice=53.0000
Category=Meat/Poultry MostExpensivePrice=123.7900
Category=Seafood MostExpensivePrice=62.5000
Category=Dairy Products MostExpensivePrice=55.0000
MostExpensivePrice=81.0000
Category=Confections MostExpensivePrice=38.0000
Category=Grains/Cereals
Max - Elements
This sample uses Max to get the products with the most expensive price in each category.
var categories =
from p in products
group p by p.Category into g
let maxPrice = g.Max(p => p.UnitPrice)
select new { Category = g.Key, MostExpensiveProducts = g.Where(p =>
p.UnitPrice == maxPrice) };
ObjectDumper.Write(categories, 1);
}
Result
Category=Beverages MostExpensiveProducts=...
MostExpensiveProducts: ProductID=38 ProductName=Côte de Blaye
Category=Beverages UnitPrice=263.5000 UnitsInStock=17
Average - Simple
This sample uses Average to get the average of all numbers in an array.
Result
Average - Projection
This sample uses Average to get the average length of the words in the array.
Result
Average - Grouped
This sample uses Average to get the average price of each category's products.
var categories =
from p in products
group p by p.Category into g
select new { Category = g.Key, AveragePrice = g.Average(p =>
p.UnitPrice) };
ObjectDumper.Write(categories);
}
Result
Category=Beverages AveragePrice=37.979166666666666666666666667
Category=Condiments AveragePrice=23.0625
Category=Produce AveragePrice=32.3700
Category=Meat/Poultry AveragePrice=54.006666666666666666666666667
Category=Seafood AveragePrice=20.6825
Category=Dairy Products AveragePrice=28.7300
AveragePrice=25.1600
Category=Confections AveragePrice=20.2500
Category=Grains/Cereals
Aggregate - Simple
This sample uses Aggregate to create a running product on the array that calculates the total
product of all elements.
Result
Aggregate - Seed
This sample uses Aggregate to create a running account balance that subtracts each withdrawal
from the initial balance of 100, as long as the balance never drops below 0
double endBalance =
attemptedWithdrawals.Aggregate(startBalance,
(balance, nextWithdrawal) =>
((nextWithdrawal <= balance) ? (balance - nextWithdrawal) :
balance));
Result
Ending balance: 20
Miscellaneous Operators
Concat - 1
This sample uses Concat to create one sequence that contains each array's values, one after the
other.
Result
Concat - 2
This sample uses Concat to create one sequence that contains the names of all customers and
products, including any duplicates.
Result
EqualAll - 1
This sample uses EqualAll to see if two sequences match on all elements in the same order.
Result
EqualAll - 2
This sample uses EqualAll to see if two sequences match on all elements in the same order.
Result
Combine
This sample calculates the dot product of two integer vectors. It uses a user-created sequence
operator, Combine, to calculate the dot product, passing it a lambda function to multiply two
arrays, element by element, and sum the result.
Result
Query Execution
Deferred Execution
The following sample shows how query execution is deferred until the query is enumerated at a
foreach statement.
public void Linq99()
{
// Sequence operators form first-class queries that
// are not executed until you enumerate over them.
int i = 0;
var q =
from n in numbers
select ++i;
Result
v = 1, i = 1
v = 2, i = 2
v = 3, i = 3
v = 4, i = 4
v = 5, i = 5
v = 6, i = 6
v = 7, i = 7
v = 8, i = 8
v = 9, i = 9
v = 10, i = 10
Immediate Execution
The following sample shows how queries can be executed immediately with operators such as
ToList().
int i = 0;
var q = (
from n in numbers
select ++i)
.ToList();
Result
v = 1, i = 10
v = 2, i = 10
v = 3, i = 10
v = 4, i = 10
v = 5, i = 10
v = 6, i = 10
v = 7, i = 10
v = 8, i = 10
v = 9, i = 10
v = 10, i = 10
Query Reuse
The following sample shows how, because of deferred execution, queries can be used again after
data changes and will then operate on the new data.
Result
GetProductList code
Join Operators
Cross Join
This sample shows how to efficiently join elements of two sequences based on equality between
key expressions over the two.
foreach (var v in q)
{
Console.WriteLine(v.ProductName + ": " + v.Category);
}
}
Result
Chai: Beverages
Chang: Beverages
Guaraná Fantástica: Beverages
Sasquatch Ale: Beverages
Steeleye Stout: Beverages
Group Join
Using a group join you can get all the products that match a given category bundled as a
sequence.
foreach (var v in q)
{
Console.WriteLine(v.Category + ":");
foreach (var p in v.Products)
{
Console.WriteLine(" " + p.ProductName);
}
}
}
Result
Beverages:
Chai
Chang
Guaraná Fantástica
Sasquatch Ale
The group join operator is more general than join, as this slightly more verbose version of the
cross join sample shows.
Result
Chai: Beverages
Chang: Beverages
Guaraná Fantástica: Beverages
Sasquatch Ale: Beverages
A so-called outer join can be expressed with a group join. A left outer joinis like a cross join,
except that all the left hand side elements get included at least once, even if they don't match any
right hand side elements. Note how Vegetablesshows up in the output even though it has no
matching products.
{
string[] categories = new string[]{
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood" };
List<Product> products = GetProductList();
var q =
from c in categories
join p in products on c equals p.Category into ps
from p in ps.DefaultIfEmpty()
select new { Category = c, ProductName = p == null ? "(No products)" :
p.ProductName };
foreach (var v in q)
{
Console.WriteLine(v.ProductName + ": " + v.Category);
}
}
Result
Chai: Beverages
Chang: Beverages
Guaraná Fantástica: Beverages
Sasquatch Ale: Beverages