This article covers the LINQ methods presented in the accompanying video and shows how to use them with EF Core to filter, sort, project, group, combine, and execute queries.
The video demonstrates these methods primarily with IEnumerable. Most concepts also apply to EF Core IQueryable queries, but EF Core adds query translation and differences between database and in-memory execution.
The examples assume Product and Category entities exposed through context.Products and context.Categories. They use asynchronous EF Core methods when available.
The examples target .NET 10 and EF Core 10. The
LeftJoinandRightJoinoperators require these versions. Earlier versions use the traditionalGroupJoin,SelectMany, andDefaultIfEmptypattern for left joins.
EF Core does not provide a dedicated reference for all LINQ methods. You can find the standard LINQ operator references here and here.
LINQ Methods Video Timeline
Select a timestamp to view the corresponding method in the video.
| Method | Category | Timestamp |
|---|---|---|
Aggregate |
Calculate Values | 01:15 |
All |
Check Conditions | 06:12 |
Any |
Check Conditions | 09:02 |
AsEnumerable |
Convert Sequences | 12:06 |
Average |
Calculate Values | 14:10 |
Cast |
Convert Sequences | 15:27 |
Concat |
Combine Sequences | 16:30 |
Contains |
Check Conditions | 18:34 |
Count |
Calculate Values | 22:08 |
DefaultIfEmpty |
Get Elements | 24:12 |
Distinct |
Set Operations and Sequence Comparison | 26:28 |
ElementAt |
Get Elements | 29:31 |
ElementAtOrDefault |
Get Elements | 29:31 |
Empty |
Create Sequences | 31:01 |
Except |
Set Operations and Sequence Comparison | 31:50 |
First |
Get Elements | 33:39 |
FirstOrDefault |
Get Elements | 33:39 |
GroupBy |
Group Data | 35:38 |
GroupJoin |
Join Data | 38:17 |
Intersect |
Set Operations and Sequence Comparison | 41:21 |
Join |
Join Data | 42:32 |
Last |
Get Elements | 48:12 |
LastOrDefault |
Get Elements | 48:12 |
LeftJoin |
Join Data | 44:48 |
LongCount |
Calculate Values | 50:08 |
Max |
Calculate Values | 51:10 |
Min |
Calculate Values | 51:10 |
OfType |
Filter Data | 52:16 |
OrderBy |
Sort Data | 53:19 |
OrderByDescending |
Sort Data | 53:19 |
Range |
Create Sequences | 55:02 |
Repeat |
Create Sequences | 56:01 |
Reverse |
Sort Data | 57:13 |
RightJoin |
Join Data | 45:14 |
Select |
Select Data | 58:17 |
SelectMany |
Select Data | 59:55 |
SequenceEqual |
Set Operations and Sequence Comparison | 1:01:58 |
Single |
Get Elements | 1:03:52 |
SingleOrDefault |
Get Elements | 1:03:52 |
Skip |
Page or Partition Results | 1:06:19 |
Take |
Page or Partition Results | 1:06:19 |
SkipWhile |
Page or Partition Results | 1:08:23 |
Sum |
Calculate Values | 1:10:05 |
TakeWhile |
Page or Partition Results | 1:10:57 |
ThenBy |
Sort Data | 1:12:26 |
ThenByDescending |
Sort Data | 1:12:26 |
ToArray |
Convert Sequences | 1:13:13 |
ToDictionary |
Convert Sequences | 1:13:53 |
ToList |
Convert Sequences | 1:14:43 |
ToLookup |
Group Data | 1:15:26 |
Union |
Set Operations and Sequence Comparison | 1:16:36 |
Where |
Filter Data | 1:17:57 |
Check Conditions
Use these methods to check whether elements exist or satisfy a condition.
Any
Any checks whether a sequence contains at least one element or whether at least one element satisfies a condition.
With EF Core, use AnyAsync() to perform the existence check against the database:
var hasOutOfStockProducts = await context.Products
.AnyAsync(product => product.Stock == 0);
The result is true when at least one product has no stock. The matching products are not loaded.
All
All checks whether every element in a sequence satisfies a condition.
The following query checks whether every product in a selected category has a Price of at least 10:
var categoryId = 1;
var allPricesAreAtLeastTen = await context.Products
.Where(product => product.CategoryId == categoryId)
.AllAsync(product => product.Price >= 10m);
The result is true only when every product returned by the filtered query satisfies the condition.
AllAsync() returns true for an empty sequence because no element fails the condition.
Contains
Contains checks whether a sequence includes a specified value.
A common EF Core use is filtering rows against a local collection of values:
var categoryIds = new[] { 1, 3 };
var products = await context.Products
.Where(product => categoryIds.Contains(product.CategoryId))
.ToListAsync();
This query returns products whose CategoryId is included in categoryIds.
When the source is an IQueryable of scalar values, ContainsAsync() can also execute the membership check as a terminal database operation.
Calculate Values
Use these methods to count elements, calculate numeric values, or accumulate a custom result.
Count
Count returns the total number of elements in a sequence or the number that satisfy a condition.
With EF Core, use CountAsync() to perform the count in the database:
var activeProductCount = await context.Products
.CountAsync(product => product.IsActive);
The query returns the number of active products without loading the matching entities.
LongCount
LongCount in the video (50:08)
LongCount works like Count but returns a long instead of an int.
Use LongCountAsync() when the result may exceed the range supported by int:
var productCount = await context.Products.LongCountAsync();
The calculation is performed in the database, and the result is returned as a 64-bit integer.
Sum
Sum calculates the total of a numeric value in a sequence.
The following query calculates the total stock for all active products:
var totalStock = await context.Products
.Where(product => product.IsActive)
.SumAsync(product => product.Stock);
SumAsync() performs the calculation in the database without loading the matching products.
Average
Average calculates the average of a numeric value in a sequence.
The following query calculates the average product price:
var averagePrice = await context.Products
.AverageAsync(product => product.Price);
AverageAsync() returns a single numeric result without loading the products.
If a filtered query may be empty, project the value to a nullable numeric type when the application should receive null.
Min
Min returns the lowest value in a sequence.
The following query returns the lowest price among active products:
var lowestActiveProductPrice = await context.Products
.Where(product => product.IsActive)
.MinAsync(product => product.Price);
If the filtered query may be empty, project Price to decimal? when the application should receive null.
Max
Max returns the highest value in a sequence.
The following query returns the highest price among active products:
var highestActiveProductPrice = await context.Products
.Where(product => product.IsActive)
.MaxAsync(product => product.Price);
If the filtered query may be empty, project Price to decimal? when the application should receive null.
Aggregate
Aggregate in the video (01:15)
Aggregate applies an accumulator function to each element and returns the final accumulated value.
Do not assume that EF Core can translate an arbitrary accumulator function into SQL. Load only the required values, and then apply Aggregate in memory:
var stockValues = await context.Products
.Where(product => product.IsActive)
.Select(product => product.Stock)
.ToListAsync();
var totalStock = stockValues.Aggregate(
0,
(total, stock) => total + stock);
EF Core executes the query when ToListAsync() is called. Aggregate then calculates totalStock from the materialized values in the application.
For a simple sum, use SumAsync() to perform the calculation in the database without loading the values first.
Get Elements
Use these methods to retrieve an element by order, position, or expected result count, with or without a default result.
First
First returns the first element in a sequence and throws an exception when the sequence is empty.
The following query defines a deterministic order before retrieving the first product:
var firstProduct = await context.Products
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.FirstAsync();
FirstOrDefault
FirstOrDefault in the video (33:39)
FirstOrDefault returns the first element in a sequence or the default value when the sequence is empty.
var firstInactiveProduct = await context.Products
.Where(product => !product.IsActive)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.FirstOrDefaultAsync();
For a reference type such as Product, the result is null when no row matches.
Last
Last returns the last element in a sequence and throws an exception when the sequence is empty.
The following query defines a deterministic order before retrieving the last product:
var lastProduct = await context.Products
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.LastAsync();
LastOrDefault
LastOrDefault in the video (48:12)
LastOrDefault returns the last element in a sequence or the default value when the sequence is empty.
var lastInactiveProduct = await context.Products
.Where(product => !product.IsActive)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.LastOrDefaultAsync();
The result is null when no inactive product matches.
Single
Single returns the only element in a sequence. It throws an exception when the sequence is empty or contains more than one element.
var productId = 5;
var product = await context.Products
.SingleAsync(product => product.ProductId == productId);
Use SingleAsync() when the query is expected to match exactly one row.
SingleOrDefault
SingleOrDefault in the video (1:03:52)
SingleOrDefault returns the only element in a sequence or the default value when the sequence is empty. It throws an exception when more than one element matches.
var productId = 5;
var product = await context.Products
.SingleOrDefaultAsync(product => product.ProductId == productId);
The result is null when no product matches.
ElementAt
ElementAt in the video (29:31)
ElementAt returns the element at a specified zero-based position and throws an exception when that position is outside the sequence.
Apply an explicit order before selecting an element by position:
var thirdProduct = await context.Products
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ElementAtAsync(2);
The index 2 represents the third product. ElementAtAsync() uses the position in the ordered result, not the entity key.
ElementAtOrDefault
ElementAtOrDefault in the video (29:31)
ElementAtOrDefault returns the element at a specified zero-based position or the default value when that position is outside the sequence.
var tenthProduct = await context.Products
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ElementAtOrDefaultAsync(9);
For a reference type such as Product, the result is null when the ordered sequence contains fewer than ten elements.
DefaultIfEmpty
DefaultIfEmpty in the video (24:12)
DefaultIfEmpty returns the original sequence when it contains elements. When the sequence is empty, it returns a sequence containing the default value for the element type or a specified fallback value.
The following example loads product names for a category and then applies DefaultIfEmpty in memory:
var categoryId = 4;
var productNames = await context.Products
.Where(product => product.CategoryId == categoryId)
.Select(product => product.Name)
.ToListAsync();
var namesOrDefault = productNames.DefaultIfEmpty("No products");
When productNames is empty, namesOrDefault contains one element with the value "No products". Without an argument, the sequence contains the default value for its element type.
In EF Core queries, DefaultIfEmpty() is also used in the traditional LINQ pattern for a left join.
Filter Data
Use these methods to keep only elements that match a condition or a specified type.
Where
Where filters a sequence by applying a condition to each element.
The following query returns active products that have stock available:
var availableProducts = await context.Products
.Where(product => product.IsActive && product.Stock > 0)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ToListAsync();
Where adds the condition to the IQueryable, and only matching products are materialized when ToListAsync() executes the query.
Multiple Where calls can be composed before the query executes.
OfType
OfType<TResult> keeps only elements assignable to the specified type.
In EF Core, it is commonly used with an inheritance hierarchy. The following example assumes that DigitalProduct derives from Product and is mapped in the EF Core model:
var digitalProducts = await context.Products
.OfType<DigitalProduct>()
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ToListAsync();
The query returns only entities represented by the DigitalProduct type. EF Core translates the type filter according to the configured inheritance mapping and database provider.
Unlike Cast<TResult>, OfType<TResult> removes incompatible elements instead of requiring every element to be compatible.
Select Data
Use these methods to shape query results or flatten related collections.
Select
Select projects each element into a new form.
The following query retrieves only the values required for a product summary:
var productSummaries = await context.Products
.Where(product => product.IsActive)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.Select(product => new
{
product.ProductId,
product.Name,
product.Price
})
.ToListAsync();
EF Core requests only the projected columns. Because the result contains scalar values rather than entities, no Product instances are created or tracked.
Select can project a single property, an anonymous type, or a custom result type.
SelectMany
SelectMany in the video (59:55)
SelectMany projects each source element to a collection and flattens the resulting collections into one sequence.
The following example assumes that Category has a collection navigation named Products:
var productsByCategory = await context.Categories
.SelectMany(
category => category.Products,
(category, product) => new
{
CategoryName = category.Name,
ProductName = product.Name
})
.OrderBy(item => item.CategoryName)
.ThenBy(item => item.ProductName)
.ToListAsync();
The result contains one row for each related product together with its category name. Categories with no products do not contribute an element to the flattened sequence.
The result-selector overload preserves values from both the source element and the collection element. When only the collection elements are needed, use the overload without a result selector.
Sort Data
Use these methods to define the order of query results or reverse an existing sequence.
OrderBy
OrderBy sorts a sequence in ascending order by a specified key.
The following query orders products by name:
var products = await context.Products
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ToListAsync();
OrderBy defines the primary sort key, while ThenBy provides a deterministic tie-breaker. The database applies both orderings when ToListAsync() executes the query.
Calling another OrderBy or OrderByDescending starts a new primary ordering. Use ThenBy or ThenByDescending to add another sort key.
OrderByDescending
OrderByDescending in the video (53:19)
OrderByDescending sorts a sequence in descending order by a specified key.
The following query orders products from the highest price to the lowest:
var products = await context.Products
.OrderByDescending(product => product.Price)
.ThenBy(product => product.ProductId)
.ToListAsync();
ProductId provides a deterministic order when multiple products have the same price.
Calling another OrderBy or OrderByDescending starts a new primary ordering. Use ThenBy or ThenByDescending to add another sort key.
ThenBy
ThenBy adds an ascending secondary ordering to an already ordered sequence.
The following query orders products by category and then by name:
var products = await context.Products
.OrderBy(product => product.CategoryId)
.ThenBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ToListAsync();
Each ThenBy applies when elements have equal values for the preceding sort keys. The final ProductId ordering makes the result deterministic when products share the same category and name.
ThenByDescending
ThenByDescending in the video (1:12:26)
ThenByDescending adds a descending secondary ordering to an already ordered sequence.
The following query places the most expensive products first within each category:
var products = await context.Products
.OrderBy(product => product.CategoryId)
.ThenByDescending(product => product.Price)
.ThenBy(product => product.ProductId)
.ToListAsync();
CategoryId remains the primary sort key. ThenByDescending affects only products in the same category.
Reverse
Reverse inverts the current order of a sequence.
The following example loads products in a defined order and then reverses the materialized list:
var products = await context.Products
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ToListAsync();
products.Reverse();
ToListAsync() executes the ordered query, and List<T>.Reverse() then reverses the list in memory.
Without an explicit order, the result is not deterministic. Prefer OrderByDescending when the reverse order can be expressed directly in the database query.
Group Data
Use these methods to organize elements by a key or create an in-memory lookup.
GroupBy
GroupBy organizes elements into groups that share the same key.
The following query groups products by category and projects the grouping key with aggregate values:
var categorySummaries = await context.Products
.GroupBy(product => product.CategoryId)
.Select(group => new
{
CategoryId = group.Key,
ProductCount = group.Count(),
MinimumPrice = group.Min(product => product.Price),
MaximumPrice = group.Max(product => product.Price)
})
.OrderBy(summary => summary.CategoryId)
.ToListAsync();
The result contains one row for each CategoryId present in the source sequence.
group.Key contains the value used to create each group. This query projects the grouping key and scalar aggregates rather than returning the groups themselves.
ToLookup
ToLookup in the video (1:15:26)
ToLookup creates an ILookup<TKey, TElement> that associates each key with one or more elements.
ToLookup is a LINQ to Objects operation. Load only the required values before creating the lookup in memory:
var productData = await context.Products
.Select(product => new
{
product.CategoryId,
product.Name
})
.ToListAsync();
var productsByCategory = productData.ToLookup(
product => product.CategoryId,
product => product.Name);
var categoryId = 1;
var productNames = productsByCategory[categoryId];
The lookup contains a group of product names for each CategoryId.
Unlike a dictionary, a lookup can associate multiple elements with the same key. When a key does not exist, it returns an empty sequence rather than throwing a key-not-found exception.
Join Data
Use these methods to combine two sequences by matching their key values.
Join
Join combines elements from two sequences when their key values match.
The following query joins products with their categories:
var productsWithCategories = await context.Products
.Join(
context.Categories,
product => product.CategoryId,
category => category.CategoryId,
(product, category) => new
{
product.ProductId,
category.CategoryId,
ProductName = product.Name,
CategoryName = category.Name
})
.OrderBy(item => item.CategoryName)
.ThenBy(item => item.ProductName)
.ThenBy(item => item.ProductId)
.ToListAsync();
The key selectors match Product.CategoryId with Category.CategoryId, and the result selector defines the values returned for each matching pair.
With a relational database provider, this query shape is typically translated to an INNER JOIN. Elements without a matching key in the other sequence are not included.
GroupJoin
GroupJoin in the video (38:17)
GroupJoin associates each element from the first sequence with all matching elements from the second sequence.
Because the result contains a nested collection, the following example loads only the required values and performs the GroupJoin in memory:
var categoryData = await context.Categories
.Select(category => new
{
category.CategoryId,
category.Name
})
.ToListAsync();
var productData = await context.Products
.Select(product => new
{
product.CategoryId,
product.Name
})
.ToListAsync();
var categoriesWithProducts = categoryData
.GroupJoin(
productData,
category => category.CategoryId,
product => product.CategoryId,
(category, products) => new
{
CategoryName = category.Name,
ProductNames = products.Select(product => product.Name)
})
.ToList();
The result contains every category together with its matching product names. When a category has no matching products, ProductNames is empty.
GroupJoin can also be combined with SelectMany and DefaultIfEmpty to form the traditional left-join pattern.
LeftJoin
LeftJoin preserves every element from the first sequence and produces one result for each matching element from the second sequence.
The following query preserves every product and supplies category data when a matching category exists:
var productsWithCategories = await context.Products
.LeftJoin(
context.Categories,
product => product.CategoryId,
category => category.CategoryId,
(product, category) => new
{
product.ProductId,
ProductName = product.Name,
CategoryName = category == null
? "No category"
: category.Name
})
.OrderBy(item => item.ProductName)
.ThenBy(item => item.ProductId)
.ToListAsync();
When no matching category exists, the category passed to the result selector is null.
RightJoin
RightJoin in the video (45:14)
RightJoin preserves every element from the second sequence and produces one result for each matching element from the first sequence.
The following query preserves every category, including categories without products:
var categoriesWithProducts = await context.Products
.RightJoin(
context.Categories,
product => product.CategoryId,
category => category.CategoryId,
(product, category) => new
{
category.CategoryId,
ProductId = product == null
? (int?)null
: product.ProductId,
CategoryName = category.Name,
ProductName = product == null
? "No products"
: product.Name
})
.OrderBy(item => item.CategoryName)
.ThenBy(item => item.CategoryId)
.ThenBy(item => item.ProductId)
.ToListAsync();
When a category has no matching product, the product passed to the result selector is null.
Page or Partition Results
Use these methods to select part of an ordered sequence by position or condition.
Skip
Skip bypasses a specified number of elements and returns the remaining sequence.
The following query retrieves the third page of products when each page contains ten products:
var pageNumber = 3;
var pageSize = 10;
var products = await context.Products
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
The database omits the products from the preceding pages before returning the requested page.
Use a fully unique ordering before applying Skip and Take so that page boundaries remain deterministic.
Take
Take returns a specified number of elements from the beginning of a sequence.
The following query returns the ten most expensive products:
var products = await context.Products
.OrderByDescending(product => product.Price)
.ThenBy(product => product.ProductId)
.Take(10)
.ToListAsync();
The ordering determines which products are returned. When fewer than ten products exist, Take returns all available elements.
SkipWhile
SkipWhile in the video (1:08:23)
SkipWhile bypasses elements while a condition remains true. After the first element that does not satisfy the condition, it returns that element and all remaining elements.
The following example loads the required values in a defined order and then applies SkipWhile in memory:
var productData = await context.Products
.OrderBy(product => product.Price)
.ThenBy(product => product.ProductId)
.Select(product => new
{
product.ProductId,
product.Name,
product.Price
})
.ToListAsync();
var productsFromFifty = productData
.SkipWhile(product => product.Price < 50m)
.ToList();
The result starts with the first product whose price is at least 50. Unlike Where, SkipWhile removes only the matching prefix of the ordered sequence.
TakeWhile
TakeWhile in the video (1:10:57)
TakeWhile returns elements while a condition remains true and stops at the first element that does not satisfy it.
The following example loads the required values in a defined order and then applies TakeWhile in memory:
var productData = await context.Products
.OrderBy(product => product.Price)
.ThenBy(product => product.ProductId)
.Select(product => new
{
product.ProductId,
product.Name,
product.Price
})
.ToListAsync();
var productsBelowFifty = productData
.TakeWhile(product => product.Price < 50m)
.ToList();
The result contains the initial products whose price is below 50. Unlike Where, TakeWhile returns only the matching prefix of the ordered sequence.
Set Operations and Sequence Comparison
Use these methods to remove duplicates, compare sequences, or perform set operations.
Distinct
Distinct removes duplicate elements from a sequence.
The following query returns the unique category identifiers used by products:
var categoryIds = await context.Products
.Select(product => product.CategoryId)
.Distinct()
.OrderBy(categoryId => categoryId)
.ToListAsync();
Distinct compares the projected CategoryId values as part of the database query, so each identifier appears only once. Because the result has no guaranteed order, apply OrderBy when a defined order is required.
Except
Except returns the distinct elements from the first sequence that do not appear in the second sequence.
The following query returns category identifiers used by products but not by any active product:
var allProductCategoryIds = context.Products
.Select(product => product.CategoryId);
var activeProductCategoryIds = context.Products
.Where(product => product.IsActive)
.Select(product => product.CategoryId);
var inactiveOnlyCategoryIds = await allProductCategoryIds
.Except(activeProductCategoryIds)
.OrderBy(categoryId => categoryId)
.ToListAsync();
Each matching CategoryId appears only once.
Except is directional. Reversing the source sequences can produce a different result.
Intersect
Intersect in the video (41:21)
Intersect returns the distinct elements that appear in both sequences.
The following query returns category identifiers that contain both active products and out-of-stock products:
var activeProductCategoryIds = context.Products
.Where(product => product.IsActive)
.Select(product => product.CategoryId);
var outOfStockProductCategoryIds = context.Products
.Where(product => product.Stock == 0)
.Select(product => product.CategoryId);
var matchingCategoryIds = await activeProductCategoryIds
.Intersect(outOfStockProductCategoryIds)
.OrderBy(categoryId => categoryId)
.ToListAsync();
Only identifiers present in both sequences are returned, and duplicate values are removed.
SequenceEqual
SequenceEqual in the video (1:01:58)
SequenceEqual determines whether two sequences contain the same number of elements with equal values in the same order.
The following example loads ordered product identifiers from two categories and compares the resulting sequences:
var firstCategoryId = 1;
var secondCategoryId = 2;
var firstProductIds = await context.Products
.Where(product => product.CategoryId == firstCategoryId)
.OrderBy(product => product.ProductId)
.Select(product => product.ProductId)
.ToListAsync();
var secondProductIds = await context.Products
.Where(product => product.CategoryId == secondCategoryId)
.OrderBy(product => product.ProductId)
.Select(product => product.ProductId)
.ToListAsync();
var productIdsAreEqual = firstProductIds
.SequenceEqual(secondProductIds);
After both queries are materialized, SequenceEqual compares the lists in memory.
The sequences must contain the same values in the same order. Use an explicit ordering when database results are compared by position.
Union
Union returns the distinct elements that appear in either of two sequences.
The following query returns category identifiers associated with active or out-of-stock products:
var activeProductCategoryIds = context.Products
.Where(product => product.IsActive)
.Select(product => product.CategoryId);
var outOfStockProductCategoryIds = context.Products
.Where(product => product.Stock == 0)
.Select(product => product.CategoryId);
var categoryIds = await activeProductCategoryIds
.Union(outOfStockProductCategoryIds)
.OrderBy(categoryId => categoryId)
.ToListAsync();
Each category identifier appears once, even when it occurs in both source sequences.
Combine Sequences
Use this method to append one sequence to another while preserving duplicate values.
Concat
Concat appends the elements from the second sequence after the elements from the first sequence.
The following example loads active and inactive product names separately and then combines them:
var activeProductNames = await context.Products
.Where(product => product.IsActive)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.Select(product => product.Name)
.ToListAsync();
var inactiveProductNames = await context.Products
.Where(product => !product.IsActive)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.Select(product => product.Name)
.ToListAsync();
var productNames = activeProductNames
.Concat(inactiveProductNames)
.ToList();
After both queries are materialized, Concat appends the inactive product names to the active product names in memory.
Unlike Union, Concat preserves duplicate values.
Create Sequences
Use these methods to define sequences in the application without querying the database.
Empty
Enumerable.Empty<TResult>() returns an empty sequence of the specified type.
The following example creates an empty sequence of product names:
var productNames = Enumerable.Empty<string>();
Empty is useful when a method must return an IEnumerable<T> but has no elements to provide.
Range
Range creates a sequence of consecutive integers.
The following example creates page numbers from 1 through 5:
var pageNumbers = Enumerable.Range(1, 5);
The first argument specifies the starting value, and the second specifies how many integers to generate. The resulting sequence contains 1, 2, 3, 4, and 5.
Repeat
Repeat creates a sequence containing the same value a specified number of times.
The following example creates the same label three times:
var placeholders = Enumerable.Repeat("Not available", 3);
The first argument specifies the value, and the second specifies the number of elements to create.
Convert Sequences
Use these methods to change how a sequence is processed, convert its element type, or materialize its results in a collection.
AsEnumerable
AsEnumerable in the video (12:06)
AsEnumerable exposes an IQueryable<T> as an IEnumerable<T> so that subsequent operators use LINQ to Objects.
The following example applies a database filter before switching to client-side processing:
static bool UsesCustomClientLogic(Product product)
{
return product.Name.Length <= 20;
}
var productNames = context.Products
.Where(product => product.IsActive)
.AsEnumerable()
.Where(product => UsesCustomClientLogic(product))
.Select(product => product.Name);
The first Where remains part of the EF Core query. After AsEnumerable(), the second Where and Select run in the application when the sequence is enumerated.
AsEnumerable() does not execute or buffer the query by itself. It changes which LINQ implementation is used by subsequent operators.
Cast
Cast<TResult> casts every element in a sequence to the specified type.
The following example loads products, exposes them through the non-generic IEnumerable interface, and casts them back to Product:
var products = await context.Products
.OrderBy(product => product.Name)
.ToListAsync();
System.Collections.IEnumerable values = products;
var typedProducts = values.Cast<Product>();
The database query executes before the cast. Cast<Product>() then attempts to cast each in-memory element to Product when the sequence is enumerated.
Every element must be compatible with Product; otherwise, enumeration throws an InvalidCastException. Unlike OfType<Product>(), Cast<Product>() does not remove incompatible elements.
ToArray
ToArray in the video (1:13:13)
ToArray materializes a sequence as an array.
The following query materializes active product names in a string[]:
var productNames = await context.Products
.Where(product => product.IsActive)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.Select(product => product.Name)
.ToArrayAsync();
Use an explicit ordering when the position of elements in the resulting array matters.
ToDictionary
ToDictionary in the video (1:13:53)
ToDictionary materializes a sequence into a dictionary by selecting a unique key for each element.
The following query associates each product identifier with its name:
var productsById = await context.Products
.Select(product => new
{
product.ProductId,
product.Name
})
.ToDictionaryAsync(
product => product.ProductId,
product => product.Name);
ToDictionaryAsync() returns a Dictionary<int, string>.
Duplicate keys cause ToDictionaryAsync() to throw an ArgumentException.
ToList
ToList materializes the elements of a sequence into a List<T>.
The following query materializes active Product entities in a list:
var products = await context.Products
.Where(product => product.IsActive)
.OrderBy(product => product.Name)
.ThenBy(product => product.ProductId)
.ToListAsync();
The returned entities are tracked by the current DbContext unless the query uses AsNoTracking().
Subsequent LINQ operations on products run in memory.