Skip to content

Fix ComplexCollection ToJson migration default value: use "[]" instead of "{}"#37965

Merged
roji merged 4 commits intomainfrom
copilot/fix-complexcollection-tojson-default-value
Mar 25, 2026
Merged

Fix ComplexCollection ToJson migration default value: use "[]" instead of "{}"#37965
roji merged 4 commits intomainfrom
copilot/fix-complexcollection-tojson-default-value

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 21, 2026

When adding a ComplexCollection mapped with ToJson() to an existing table, the generated migration incorrectly sets defaultValue: "{}" (JSON object) instead of defaultValue: "[]" (JSON array), causing a runtime InvalidOperationException when reading entities with the default value.

Root Cause

MigrationsModelDiffer.InitializeJsonColumn unconditionally used "{}" as the default for all non-nullable JSON columns, without distinguishing collections from single objects.

Changes

  • MigrationsModelDiffer.cs: Added IsJsonCollectionColumn helper that inspects the table's ComplexTypeMappings and EntityTypeMappings to detect collection-backed JSON columns:

    • Complex collections: ComplexProperty.IsCollection == true and GetContainerColumnName() matches the column
    • Owned collections (OwnsMany): ownership IsUnique: false
    • Uses "[]" as default for collection columns, "{}" for single-object columns
  • MigrationsModelDifferTest.cs: Added Add_complex_collection_mapped_to_json_uses_empty_array_as_default_value test covering the reported scenario.

// Before fix: defaultValue: "{}"
// After fix:  defaultValue: "[]"
migrationBuilder.AddColumn<string>(
    name: "Items",
    table: "MyEntities",
    type: "nvarchar(max)",
    nullable: false,
    defaultValue: "[]");
Original prompt

This section details on the original issue you should resolve

<issue_title>ComplexCollection ToJson produces a {} default value instead of [] in migrations</issue_title>
<issue_description>### Bug description

  • Add a property e.ComplexCollection(m => m.MyCollection, x => x.ToJson()); to an existing entity.
  • Generate migrations (SQL Server, compat 170)
  • Observe that defaultValue in the migration is {} instead of [].

Your code

// ComplexCollection with ToJson() generates defaultValue: "{}" instead of "[]"
// when adding the column to an existing table.
//
// Steps to reproduce:
//   1. Comment out Items property and ComplexCollection config.
//   2. dotnet ef migrations add Init        (creates the table without Items)
//   3. Uncomment Items property and ComplexCollection config.
//   4. dotnet ef migrations add AddItems    (adds Items column to existing table)
//   5. Inspect Migrations/*_AddItems.cs — the Items column has defaultValue: "{}" instead of "[]".

using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

var db = new BugReproContext();

public class MyEntity
{
    public int Id { get; set; }
    public List<MyComplexType> Items { get; set; } = [];
}

public class MyComplexType
{
    public string Name { get; set; } = "";
    public int Value { get; set; }
}

public class BugReproContext : DbContext
{
    public DbSet<MyEntity> MyEntities { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EfCoreBugRepro;Trusted_Connection=True;",
            o => o.UseCompatibilityLevel(170));

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MyEntity>(e =>
        {
            e.ComplexCollection(m => m.Items, x => x.ToJson());
        });
    }
}

Stack traces

Attempts to read the entity after the incorrect migration runs results in the following due to JSON objects being present where JSON arrays are expected:

An exception of type 'System.InvalidOperationException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'Invalid token type: 'StartObject'.'
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.IncludeJsonEntityCollection[TIncludingEntity,TIncludedCollectionElement](QueryContext queryContext, Object[] keyPropertyValues, JsonReaderData jsonReaderData, TIncludingEntity entity, Func`4 innerShaper, Action`1 getOrCreateCollectionObject, Action`2 fixup, Boolean performFixup)
   at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.<MoveNextAsync>d__21.MoveNext()
   at System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1.ConfiguredValueTaskAwaiter.GetResult()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.<ToListAsync>d__67`1.MoveNext()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.<ToListAsync>d__67`1.MoveNext()

Verbose output

public partial class AddItems : Migration
    {
        /// <inheritdoc />
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.AddColumn<string>(
                name: "Items",
                table: "MyEntities",
                type: "json",
                nullable: false,
                defaultValue: "{}");
        }

        /// <inheritdoc />
        protected override void Down(MigrationBuilder migrationBuilder)
        {
            migrationBuilder.DropColumn(
                name: "Items",
                table: "MyEntities");
        }
    }

EF Core version

10.0.5

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.NET 10

Operating system

Windows 11

IDE

VS Code</issue_description>

Comments on the Issue (you are @copilot in this section)

@roji # AI Triage

The below is an AI-generated analysis and may contain inaccuracies.

Summary

Confirmed bug. When adding a ComplexCollection mapped with ToJson() to an existing entity via migrations, the generated migration sets defaultValue: "{}" (JSON object) instead of defaultValue: "[]" (JSON array). This causes a runtime InvalidOperationException ("Invalid token type: 'StartObject'") when reading entities with the default value.

Root Cause

In MigrationsModelDiffer.InitializeJsonColumn, the default value is unconditionally set to "{}" for all non-nullable, non-inline JSON columns:

columnOperation.DefaultValue = inline || isNullable
    ? null
    : "{}";

This does not distinguish between complex properties (JSON objects → "{}" is correct) and complex collections (JS...


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

Copilot AI changed the title [WIP] Fix ComplexCollection ToJson default value in migrations Fix ComplexCollection ToJson migration default value: use "[]" instead of "{}" Mar 21, 2026
Copilot AI requested a review from roji March 21, 2026 07:55
Copilot finished work on behalf of roji March 21, 2026 07:55
Copy link
Copy Markdown
Member

@roji roji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test covering the owned (non-complex) case, which you seem to have fixed as well.

Copilot AI requested a review from roji March 21, 2026 09:02
Copilot stopped work on behalf of roji due to an error March 21, 2026 09:02
@roji roji force-pushed the copilot/fix-complexcollection-tojson-default-value branch from ec09fdb to be88836 Compare March 21, 2026 09:12
Copilot AI and others added 2 commits March 21, 2026 10:33
Addresses review feedback: adds a test covering the owned (non-complex)
collection case mapped to JSON. Owned collections are always nullable
(IsUnique is false), so the default value is null rather than [].

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roji roji force-pushed the copilot/fix-complexcollection-tojson-default-value branch from be88836 to 4823084 Compare March 21, 2026 09:34
The IsJsonCollectionColumn check was incorrectly matching a nested
OwnsMany inside an OwnsOne reference, because the nested entity's
GetContainerColumnName() returns the root JSON column name and its
ownership has IsUnique: false. This caused Convert_string_column_to_
a_json_column_containing_required_reference to fail with '[]' instead
of '{}'.

Fix: only consider root-level entity mappings whose principal is not
itself mapped to JSON. Added a regression test for this case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roji roji marked this pull request as ready for review March 21, 2026 19:36
@roji roji requested a review from a team as a code owner March 21, 2026 19:36
Copilot AI review requested due to automatic review settings March 21, 2026 19:37
@roji roji enabled auto-merge (squash) March 21, 2026 19:37
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes migration generation for ComplexCollection(...).ToJson() so non-nullable JSON collection columns get an empty JSON array default ("[]") rather than an empty JSON object ("{}"), preventing runtime failures when materializing entities from the default value.

Changes:

  • Update MigrationsModelDiffer.InitializeJsonColumn to choose "[]" vs "{}" based on whether the JSON column represents a collection.
  • Add IsJsonCollectionColumn(JsonColumn) helper to detect collection-backed JSON container columns.
  • Add migration-diff tests covering the complex-collection scenario (and additional JSON owned-type scenarios).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs Adjusts default JSON value generation for non-nullable JSON container columns, introducing collection detection.
test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs Adds regression coverage asserting "[]" default for complex collections mapped to JSON and validating behaviors for owned JSON mappings.

…lex references

was already applied to the owned entity branch. Without this, a nested
ComplexCollection inside a ComplexProperty (reference) would falsely
classify the root JSON column as a collection, producing '[]' instead
of '{}'. Added regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roji roji merged commit ba4ebfe into main Mar 25, 2026
10 checks passed
@roji roji deleted the copilot/fix-complexcollection-tojson-default-value branch March 25, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ComplexCollection ToJson produces a {} default value instead of [] in migrations

4 participants