dotnet/efcore · error · ArgumentException

SelectExpression can only be built over a JsonQueryExpressio

Error message

SelectExpression can only be built over a JsonQueryExpression that represents a collection within the JSON document.

What it means

CreateSelect over a JsonQueryExpression requires the JSON entity to be a collection (a JSON array). This ArgumentException fires when the JsonQueryExpression.IsCollection is false, i.e. someone tried to build a SelectExpression over a single (scalar/entity) JSON value. Single-valued JSON owned entities are accessed as columns through their owner, not as independent query roots.

Source

Thrown at src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs:855

    }

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    [EntityFrameworkInternal]
    protected virtual SelectExpression CreateSelect(
        JsonQueryExpression jsonQueryExpression,
        TableExpressionBase tableExpressionBase,
        string identifierColumnName,
        Type identifierColumnType,
        RelationalTypeMapping identifierColumnTypeMapping)
    {
        if (!jsonQueryExpression.IsCollection)
        {
            throw new ArgumentException(RelationalStrings.SelectCanOnlyBeBuiltOnCollectionJsonQuery, nameof(jsonQueryExpression));
        }

        var structuralType = jsonQueryExpression.StructuralType;
        var jsonColumn = jsonQueryExpression.JsonColumn;
        var tableAlias = tableExpressionBase.Alias!;

        Check.DebugAssert(
            structuralType.BaseType is null && !structuralType.GetDirectlyDerivedTypes().Any(),
            "Inheritance encountered inside a JSON document");

        // Create a dictionary mapping all properties to their ColumnExpressions, for the SelectExpression's projection.
        var propertyExpressions = new Dictionary<IProperty, ColumnExpression>();
        foreach (var property in structuralType.GetPropertiesInHierarchy())
        {
            // For owned JSON mapping, add column(s) representing key of the parent (non-JSON) entity, on top of all the projections from OPENJSON/json_each/etc.
            if (jsonQueryExpression.KeyPropertyMap?.TryGetValue(property, out var ownerKeyColumn) == true)
            {
                propertyExpressions[property] = ownerKeyColumn;

View on GitHub (pinned to 3a2006ef56)

Solutions

  1. Access a single-valued JSON owned entity through its owner: project owner.Owned.Property rather than querying the owned entity as a root.
  2. If you need to query a collection inside JSON, map it with OwnsMany(...).ToJson(...) so IsCollection is true.
  3. Do not call .AsQueryable()/LINQ operators that turn a single-valued JSON navigation into a SelectExpression root.

Example fix

// before (single-valued JSON owned treated as queryable)
modelBuilder.Entity<Order>().OwnsOne(o => o.Address, a => a.ToJson());
var q = db.Orders.SelectMany(o => new[] { o.Address }.AsQueryable());
// after (project through owner)
var q = db.Orders.Select(o => new { o.Id, o.Address.City });
Defensive patterns

Strategy: validation

Validate before calling

// Do not turn a single-valued JSON owned entity into a query root.
// OwnsOne(...).ToJson() => scalar/entity (not collection); access via the owner.
// OwnsMany(...).ToJson() => collection; safe to compose.
var owneds = db.Model.FindEntityType(typeof(Owner))!
    .GetNavigations().Select(n => n.TargetEntityType)
    .Where(t => t.IsOwned() && t.IsMappedToJson());
foreach (var owned in owneds)
    if (!owned.FindOwnership()!.IsUnique)
        Console.WriteLine($"{owned.Name}: JSON collection (queryable)");
    else
        Console.WriteLine($"{owned.Name}: JSON scalar/entity (access via owner)");

Prevention

When it happens

Trigger: Internal/advanced usage that constructs a JsonQueryExpression for a non-collection JSON owned entity and then tries to build a SelectExpression over it. End-user-reachable through APIs that expose JSON-mapped owned entities as queryable when the owned entity is mapped as a single object (.ToJson() on OwnsOne) rather than a collection (.ToJson() on OwnsMany).

Common situations: Calling .AsQueryable() on a reference (non-collection) JSON-mapped owned navigation and trying to compose; third-party extensions that build JSON queries incorrectly; misconfiguring OwnsOne with ToJson and treating the result as a queryable collection.

Related errors


AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11). Data as JSON: /api/errors/b8a7374500ef838f. Report an issue: GitHub.