Chapter 09 · Advanced LINQ: Joins, Grouping, Subqueries, Set Operations, and Raw SQL Composition

EXISTS, Any, All, Contains, Correlated Subqueries, and Efficient Membership Tests

Express existence and membership with Any/All/Contains, correlated subqueries, and EF Core 10 parameterized-collection modes backed by plan evidence.

Intermediate115–145 minutesEXISTS/Contains + collection-translation labEF Core 10.0.11 · .NET 10.0.11SQLite provider 10.0.11 baselineSDK checkpoint: 10.0.400Last reviewed: August 2026

Learning outcomes

ServiceHub needs filters such as “work orders that have at least one safety tag,” “technicians for whom every assigned order is non-high-priority,” and “work orders whose IDs are in a caller-supplied set.” These are membership/existence questions. Their efficient relational forms are often EXISTS, NOT EXISTS, or IN, but exact translation—including collection parameters—depends on EF Core version and provider.

01

Relate Any, All, and Contains to EXISTS/NOT EXISTS/IN-style relational forms.

02

Build correlated subqueries through navigations/join entities without materializing child collections.

03

Explain vacuous truth for All over an empty set.

04

Account for NULL semantics in anti-membership/NOT EXISTS logic.

05

Teach EF Core 10 parameterized collection translation as multiple scalar parameters by default, including per-query overrides.

06

Choose a scalable membership strategy by measuring collection size, provider limits, plan quality, and network/parameter cost.

1. Any asks whether one qualifying row exists

csharp · stable ServiceHub query model
// Existing course model — Chapter 09 extends queries, not persistence identity.public sealed partial class WorkOrder{    public int Id { get; private set; }    public string WorkOrderNumber { get; private set; } = string.Empty;    public string CustomerName { get; private set; } = string.Empty;    public WorkOrderPriority Priority { get; private set; }    public int? AssignedTechnicianId { get; private set; }    public Technician? AssignedTechnician { get; private set; }    public ICollection<WorkOrderTag> WorkOrderTags { get; } = new List<WorkOrderTag>();    public Guid Revision { get; private set; }}public sealed class Technician{    public int Id { get; private set; }    public string EmployeeCode { get; private set; } = string.Empty;    public string DisplayName { get; private set; } = string.Empty;    public IReadOnlyCollection<WorkOrder> WorkOrders => _workOrders;    private readonly List<WorkOrder> _workOrders = new();}public sealed class WorkOrderTag{    public int WorkOrderId { get; private set; }    public WorkOrder WorkOrder { get; private set; } = null!;    public int TagId { get; private set; }    public Tag Tag { get; private set; } = null!;    public DateTime AppliedUtc { get; private set; }    public string AppliedBy { get; private set; } = string.Empty;    public int DisplayOrder { get; private set; }}
csharp · work orders with a safety tag
var safetyOrders = db.WorkOrders    .Where(w => w.WorkOrderTags.Any(        wt => wt.Tag.Name == "safety"))    .Select(w => new { w.Id, w.WorkOrderNumber });Console.WriteLine(safetyOrders.ToQueryString());
sql · representative correlated EXISTS
SELECT w.work_order_id, w.work_order_numberFROM work_orders AS wWHERE EXISTS (    SELECT 1    FROM work_order_tags AS wt    INNER JOIN tags AS t ON wt.tag_id = t.tag_id    WHERE wt.work_order_id = w.work_order_id      AND t.name = @__tag_0);

The subquery is correlated because it references the current outer work-order key. The database can stop searching after it proves existence; do not replace this with loading the entire tag collection just to call Any() in memory.

2. All usually becomes NOT EXISTS of a counterexample

csharp · technicians whose assigned work is never high priority
var technicians = db.Technicians    .Where(t => t.WorkOrders.All(        w => w.Priority != WorkOrderPriority.High));

Mathematically, “all rows satisfy P” is equivalent to “there does not exist a row that violates P.” A provider commonly emits NOT EXISTS (... WHERE NOT P ...). Note the vacuous truth rule: a technician with zero work orders satisfies All. If the business requires at least one assignment, combine Any() with All(...).

csharp · require at least one assignment
var activeAndSafe = db.Technicians    .Where(t => t.WorkOrders.Any() &&                t.WorkOrders.All(w => w.Priority != WorkOrderPriority.High));

3. Contains over a query becomes relational membership

csharp · subquery membership
var taggedIds = db.Set<WorkOrderTag>()    .Where(wt => wt.AppliedBy == actor)    .Select(wt => wt.WorkOrderId);var orders = db.WorkOrders    .Where(w => taggedIds.Contains(w.Id));

This can translate to IN (subquery) or an equivalent EXISTS form. Do not force one spelling because the optimizer/provider may normalize them differently. Compare generated SQL and plan evidence for the production-like engine.

4. EF Core 10 changed parameterized collection translation

For a caller-supplied primitive collection, EF Core 10 defaults to multiple scalar SQL parameters when possible. EF Core 8/9 commonly used a single JSON-like collection parameter for SQL Server. This is a version-sensitive behavior, not timeless LINQ semantics.

csharp · caller-supplied ID set
int[] ids = [101, 205, 309];var selected = db.WorkOrders    .Where(w => ids.Contains(w.Id));Console.WriteLine(selected.ToQueryString());
sql · representative EF Core 10 shape
SELECT ...FROM work_orders AS wWHERE w.work_order_id IN (@ids1, @ids2, @ids3);

EF may pad parameter counts to reduce the number of distinct SQL shapes. Exact parameter naming/padding is provider/version-specific; inspect your real command log.

5. Per-query collection translation controls are evidence tools

csharp · EF Core 10 per-query modes
var defaultMode = db.WorkOrders    .Where(w => EF.MultipleParameters(ids).Contains(w.Id));var constants = db.WorkOrders    .Where(w => EF.Constant(ids).Contains(w.Id));var singleParameter = db.WorkOrders    .Where(w => EF.Parameter(ids).Contains(w.Id));

These APIs let you test plan/cache behavior instead of globally guessing. EF.Constant can produce different SQL text per collection and affect plan-cache reuse. EF.Parameter asks for a single collection parameter where the provider supports a representation. None is universally fastest; record collection cardinality and plan evidence.

6. Deliberately wrong: huge request list as “just another Contains”

csharp · dangerous assumption
// Imagine 40,000 caller-supplied IDs.var rows = await db.WorkOrders    .Where(w => request.Ids.Contains(w.Id))    .ToListAsync(ct);

Even if translation succeeds, the request can create a large SQL/parameter payload, hit engine/provider limits, increase compilation cost, or produce poor plans. The repair depends on the engine and topology: bounded/chunked requests, a temporary/staging table, SQL Server table-valued parameter, PostgreSQL array/unnest strategy, or other provider-supported bulk-membership mechanism. Keep the mandatory lab small and free/local; discuss provider-native mechanisms without pretending they are portable EF abstractions.

7. NULL and anti-membership deserve special care

NOT IN in SQL behaves surprisingly when the compared set contains NULL because three-valued logic can turn the predicate UNKNOWN. NOT EXISTS often expresses anti-existence more robustly. EF’s LINQ translation includes null-semantics compensation in many cases, but always inspect SQL when nullable keys/values participate.

csharp · anti-existence through navigation
var untagged = db.WorkOrders    .Where(w => !w.WorkOrderTags.Any());

This directly states “no related join row exists” and avoids materializing a list of IDs to negate in memory.

8. Plan evidence: syntax is not the cost model

For small lists, IN with scalar parameters may be ideal. For large lists, cardinality knowledge, parameter limits, index selectivity, and compilation behavior matter. Capture ToQueryString, executed parameter count, and a database plan. On SQLite, use EXPLAIN QUERY PLAN; on production-like SQL Server/PostgreSQL use the engine’s real plan tooling.

9. Hands-on lab: existence and collection modes

  1. Seed tags so some work orders have “safety,” some have other tags, and some have none.
  2. Run Any and capture the EXISTS-shaped SQL.
  3. Run All for technicians including one with zero work; explain vacuous truth, then add Any.
  4. Run Contains with 3, 20, and 100 IDs; record generated parameter counts and command text.
  5. Compare default EF.MultipleParameters, EF.Constant, and EF.Parameter only where your provider supports them; record translation/plan differences.
  6. Run the anti-existence query for untagged work orders.
  7. Do not extrapolate one SQLite plan to SQL Server/PostgreSQL production behavior.

Check your understanding

  1. What relational idea does Any usually express?
  2. Why can All return true for a technician with no work orders?
  3. What changed for parameterized Contains collections in EF Core 10?
  4. Is EF.Constant universally faster than parameters?
  5. Why can a huge Contains list be operationally dangerous?
  6. Why is NOT EXISTS often clearer for anti-relationship checks?
Review the answers

Existence of at least one qualifying related row, commonly EXISTS.

Universal quantification over an empty set is true; there is no counterexample.

Multiple scalar parameters became the default translation when possible.

No. It trades parameterization/plan reuse characteristics and must be measured.

It can create large command/parameter payloads, hit limits, and yield poor compilation/plans.

It directly states absence of matching rows and avoids some NULL pitfalls associated with NOT IN.

10. Production judgment and bridge

Use Any/All to express existence semantics, not collection-loading convenience. Treat external membership lists as an input-size/design problem, especially after EF Core 10’s translation change. Lesson 4 now combines entire query result sets with Union, Concat, Intersect, and Except, where shape compatibility and bag-vs-set semantics become decisive.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.

\n