Skip to content

Updating an owned JSON array throws The value of shadow key property '<model>.__synthesizedOrdinal' is unknown when attempting to save changes #35269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
thomasrea0113 opened this issue Dec 4, 2024 · 2 comments

Comments

@thomasrea0113
Copy link

Here is a minimum reproduction scenario: https://github.com/thomasrea0113/EFCoreIssue.UpdateJSONCollectionProperty. I believe this is related to #34616 in some way, but the proposed fix did not resolve the issue. I supposed this is also likely to be fixed by #31252, but I need a workaround in the meantime.

My only goal is to have an array of items stored as a JSON array in the database. This is easy enough to configure and works great for adding, however the issue comes about when trying to update that array.

I do not care about order, or being able to query/update individual items in the array in the database. The array is read-only and will only ever be updated as a whole on the client side. Is there a way to do away with this synthetic id all together? I don't see how it's relevant for my use case.

------------------------------------------------------------------------------
You may only use the Microsoft Visual Studio .NET/C/C++ Debugger (vsdbg) with
Visual Studio Code, Visual Studio or Visual Studio for Mac software to help you
develop and test your applications.
------------------------------------------------------------------------------
warn: 12/4/2024 10:10:13.637 CoreEventId.SensitiveDataLoggingEnabledWarning[10400] (Microsoft.EntityFrameworkCore.Infrastructure)
      Sensitive data logging is enabled. Log entries and exception messages may include sensitive application data; this mode should only be enabled during development.
info: 12/4/2024 10:10:13.769 RelationalEventId.AcquiringMigrationLock[20411] (Microsoft.EntityFrameworkCore.Migrations)
      Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
info: 12/4/2024 10:10:13.802 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (20ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsLock' AND "type" = 'table';
info: 12/4/2024 10:10:13.812 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (8ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      INSERT OR IGNORE INTO "__EFMigrationsLock"("Id", "Timestamp") VALUES(1, '2024-12-04 15:10:13.8040956+00:00');
      SELECT changes();
info: 12/4/2024 10:10:13.846 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" (
          "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY,
          "ProductVersion" TEXT NOT NULL
      );
info: 12/4/2024 10:10:13.854 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT COUNT(*) FROM "sqlite_master" WHERE "name" = '__EFMigrationsHistory' AND "type" = 'table';
info: 12/4/2024 10:10:13.856 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT "MigrationId", "ProductVersion"
      FROM "__EFMigrationsHistory"
      ORDER BY "MigrationId";
info: 12/4/2024 10:10:13.862 RelationalEventId.MigrationsNotApplied[20405] (Microsoft.EntityFrameworkCore.Migrations)
      No migrations were applied. The database is already up to date.
info: 12/4/2024 10:10:13.866 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      DELETE FROM "__EFMigrationsLock";
info: 12/4/2024 10:10:13.974 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (2ms) [Parameters=[@p0='[{"Id":1,"Name":"Name 1"},{"Id":2,"Name":"Name 2"}]' (Nullable = false) (Size = 51), @p1='Model 1' (Nullable = false) (Size = 7)], CommandType='Text', CommandTimeout='30']
      INSERT INTO "Models" ("JsonArray", "Name")
      VALUES (@p0, @p1)
      RETURNING "Id";
fail: 12/4/2024 10:10:14.003 CoreEventId.SaveChangesFailed[10000] (Microsoft.EntityFrameworkCore.Update)
      An exception occurred in the database while saving changes for context type 'EFCoreIssue.UpdateJSONCollectionProperty.AppDbContext'.
      System.InvalidOperationException: The value of shadow key property 'JsonArrayItem.__synthesizedOrdinal' is unknown when attempting to save changes. This is because shadow property values cannot be preserved when the entity is not being tracked. Consider adding the property to the entity's .NET type. See https://aka.ms/efcore-docs-owned-collections for more information.
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.<PrepareToSave>g__CheckForUnknownKey|113_0(IProperty property, <>c__DisplayClass113_0&)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.PrepareToSave()
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.GetEntriesToSave(Boolean cascadeChanges)
         at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll
info: 12/4/2024 10:10:14.014 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[@p1='8', @p0='Model 1' (Nullable = false) (Size = 7)], CommandType='Text', CommandTimeout='30']
      UPDATE "Models" SET "Name" = @p0
      WHERE "Id" = @p1
      RETURNING 1;
The program '[19652] EFCoreIssue.UpdateJSONCollectionProperty.exe' has exited with code -1 (0xffffffff).

EF Core version: 9.0.0
Database provider: Microsoft.EntityFrameworkCore.Sqlite
Target framework: net9.0
Operating system: Windows 11
IDE: VS Code 1.95.3

@PhilippeWaeber
Copy link

PhilippeWaeber commented Apr 2, 2025

Hi, we are encountering the exact same issue. Did you find a workaround for it?
We intend to move away from a json owned property, as we couldn't figure out a satisfying solution, and use a join table instead.

We're working with this model

public class ValueSetEntry
{
    public string Id { get; set; };
    public required string Value { get; set; };
    public int? SortOrder { get; set; }
    public ICollection<Description> Descriptions { get; set; } = new List<Description>();
}

public record Description
{
    public required DescriptionType Type { get; set; }
    public required string Language { get; set; }
    public required string Text { get; set; }
}

public enum DescriptionType
{
    Standard,
    Short,
    Long
}

Where we set up the db context with

internal class BaseValueSetConfiguration<TEntity>(int maxLength = 75) : IEntityTypeConfiguration<TEntity>
    where TEntity : ValueSetEntry
{
    public override void Configure(EntityTypeBuilder<TEntity> builder)
    {
        builder.HasKey(entry => entry.Id);

        builder.HasIndex(entry => entry.Value).IsUnique();
        builder.Property(entry => entry.Value).HasMaxLength(maxLength);

        builder.OwnsMany(entry => entry.Descriptions).ToJson();
    }
}

All I'm expecting from EF in regards to Descriptions is to persist and load them. No need for any kind of change tracking on them.

@HubbleLiu
Copy link

You need to configure a foreign key relationship for your list property. Although EF will not write this key value to the database even if a foreign key relationship is configured for the JSON column, EF validation will fail without this relationship.

public record Description
{
[JsonIgnore] If you don't need this property, you can not serialize it.
public string? OwnerId{ get; set; }; //you need this
public required DescriptionType Type { get; set; }
public required string Language { get; set; }
public required string Text { get; set; }
}

builder.OwnsMany(entry => entry.Descriptions).ToJson();
builder.WithOwner().HasForeignKey(f => f.OwnerId); //you need this

After this configuration, you can query, update and delete

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants