Repository navigation
Any/Count over an empty captured collection of value sets (or of ranges beside Contains) throws 'Empty collections are not supported as inline query roots' #137
Description
Any or Count with a predicate over an empty captured collection throws when EF Core translates the query. Memory answers false or 0. It affects every value set type, wherever the collection is used, and every range type when a Contains in the same query reads the collection. Measured on main (86a59a9) against PostgreSQL 18 by CapturedCollectionProbeTests (#136):
var wanted = new List<StringSet>();
db.Items.Where(r => wanted.Any(w => w.Overlaps(r.Tags))); // InvalidOperationException: Empty collections are not supported as inline query roots.
db.Items.Select(r => wanted.Count(w => w.IsSubsetOf(r.Tags))); // the same
var ranges = new List<Int32Range>();
db.Reservations.Where(r => ranges.Contains(r.Seats) || ranges.Any(w => w.Overlaps(r.Seats))); // the sameScope: every value set type (closed, wrapper and NodaTime), every range type beside a Contains, every collection form (array, List, HashSet, IEnumerable), and every predicate the sweep asks (Overlaps, IsSubsetOf, IsEmpty, IsAdjacentTo). Three cases don't fail: Contains (EF Core folds IN () to false), Any(w => w == c) (rewritten to IN), and Count() without a predicate.
Cause: for a captured collection of value sets, or of ranges that a Contains reads, the interceptor switches the parameter to EF Core's ParameterTranslationMode.MultipleParameters (#75, #77). In that mode a collection used as a query root is written as VALUES (@p1), (@p2), …, and EF Core refuses to write VALUES with no rows. Before 9.1.0 these queries failed too, with InvalidCastException (value sets) or 42809 (ranges beside Contains), so nothing that worked broke. But the error doesn't say what to write instead.
Why it matters for #76: a range collection that no Contains reads still goes out as one multirange parameter, and over an empty list that happens to answer correctly (unnest of an empty multirange). #76's fix switches those collections too, so as written it would turn the empty case from a correct answer into this exception. The two need to be fixed together.
Fix (10.0.0): for an empty collection, translate Any(pred) as false and Count(pred) as 0. Or keep the collection as a typed array parameter when it has no elements ('{}'::int4range[] is a valid empty array of ranges, and an empty array of arrays can be typed too). Or at minimum, refuse it in translation with a message that says what to write.
Tests: the rows are registered in test/oracles/candidates.json against this issue (family an empty captured collection cannot be a VALUES query root). Each fails as candidate fixed once the fix lands.
Found by CapturedCollectionProbeTests (route B, #105).
Activity
test: captured collection probe sweep over every range and set type
CaffeinatedCoder commentedon Oct 18, 2026
Fixed in #170 (
fix/empty-captured-collection, stacked on #169, 10.0.0), placed before the #76 fix on purpose since that fix would otherwise turn an empty range list from a correct answer into this exception. What is now true:EmptyCollectionRewriter, run from the decorated parameter-based SQL processor after the provider's expansion: aSELECTwhose table list holds aVALUESwith no rows gets one row of typedNULLs andFALSEon itsWHERE, so it reads no rows —Anyisfalse,Countis0,Allistrue. The SQL is… FROM (VALUES (NULL::text[])) AS w("Value") WHERE FALSE. Only aVALUESwhose referenced column is a registered range or value set type is rewritten.SetCollectionTranslationTests,RangeCollectionTranslationTests,SetCollectionContainsTests,RangeCollectionContainsTestsandCapturedCollectionProbeTests(its Any/Count over an empty captured collection of value sets (or of ranges beside Contains) throws 'Empty collections are not supported as inline query roots' #137 candidate removed); with the rewriter removed they fail with EF Core's message and the probe reports 176 lines. Changelog 10.0.0 Fixed row anddocs/efcore.mdbullet added.