Skip to content
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

Update nested Json with generic lists - System.ArgumentNullException: 'Value cannot be null. Arg_ParamName_Name' #33597

Open
FM1973 opened this issue Apr 23, 2024 · 5 comments

Comments

@FM1973
Copy link

FM1973 commented Apr 23, 2024

Hello.

I´m trying to update a Json-Column with nested complex objects. I can create the inital object without a hassle.
When I try to update (add a new object to a list inside the Json-Field) I get an exception:

System.ArgumentNullException: 'Value cannot be null. Arg_ParamName_Name'

   at System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) in System\ThrowHelper.cs:line 247
   at System.Collections.Generic.Dictionary`2.FindValue(TKey key) in System.Collections.Generic\Dictionary.cs:line 1036
   at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value) in System.Collections.Generic\Dictionary.cs:line 1388
   at Microsoft.EntityFrameworkCore.Update.ModificationCommand.<GenerateColumnModifications>g__HandleJson|41_4(List`1 columnModifications, <>c__DisplayClass41_0& ) in Microsoft.EntityFrameworkCore.Update\ModificationCommand.cs:line 517
   at Microsoft.EntityFrameworkCore.Update.ModificationCommand.GenerateColumnModifications() in Microsoft.EntityFrameworkCore.Update\ModificationCommand.cs:line 302
   at Microsoft.EntityFrameworkCore.Update.ModificationCommand.<>c.<get_ColumnModifications>b__33_0(ModificationCommand command) in Microsoft.EntityFrameworkCore.Update\ModificationCommand.cs:line 146
   at Microsoft.EntityFrameworkCore.Internal.NonCapturingLazyInitializer.EnsureInitialized[TParam,TValue](TValue& target, TParam param, Func`2 valueFactory) in Microsoft.EntityFrameworkCore.Internal\NonCapturingLazyInitializer.cs:line 16
   at Microsoft.EntityFrameworkCore.Update.ModificationCommand.get_ColumnModifications() in Microsoft.EntityFrameworkCore.Update\ModificationCommand.cs:line 146
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.<CreateCommandBatches>d__10.MoveNext() in Microsoft.EntityFrameworkCore.Update.Internal\CommandBatchPreparer.cs:line 80
   at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.<BatchCommands>d__8.MoveNext() in Microsoft.EntityFrameworkCore.Update.Internal\CommandBatchPreparer.cs:line 63
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.<ExecuteAsync>d__9.MoveNext() in Microsoft.EntityFrameworkCore.Update.Internal\BatchExecutor.cs:line 111
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__111.MoveNext() in Microsoft.EntityFrameworkCore.ChangeTracking.Internal\StateManager.cs:line 851
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__115.MoveNext() in Microsoft.EntityFrameworkCore.ChangeTracking.Internal\StateManager.cs:line 927
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.<ExecuteAsync>d__7`2.MoveNext()
   at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__63.MoveNext() in Microsoft.EntityFrameworkCore\DbContext.cs:line 351
   at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__63.MoveNext() in Microsoft.EntityFrameworkCore\DbContext.cs:line 375
   at EfTestJson.Data.ApplicationDbContext.<SaveChangesAsync>d__7.MoveNext() in D:\Projects\EFCore\EfTestJson\EfTestJson\Data\ApplicationDbContext.cs:line 31
   at Program.<<Main>$>d__0.MoveNext() in D:\Projects\EFCore\EfTestJson\EfTestJson\Program.cs:line 86
   at Program.<Main>(String[] args)

I´m using the .ToJson function. The configuration looks like this:

internal sealed class PollConfiguration : IEntityTypeConfiguration<Poll>
{
    public void Configure(EntityTypeBuilder<Poll> builder)
    {
        builder.ToTable("Polls");

        builder.HasKey(c => c.Id);
        builder.Property(c => c.Title).HasMaxLength(255).IsRequired();
        builder.Property(c => c.Description).HasMaxLength(1000).IsRequired(false);
        builder.Property(c => c.Start).IsRequired(false);
        builder.Property(c => c.End).IsRequired(false);
        builder.Property(c => c.Status).IsRequired(true);
        builder.OwnsMany(c => c.Categories, d =>
        {
            d.ToJson();
            d.OwnsMany(e => e.Tasks, e =>
            {
                e.ToJson();
                e.OwnsMany(f => f.TasksUsers, g =>
                {
                    g.ToJson();
                    g.Property(f => f.Percent).HasPrecision(18, 2);
                });
            });
        });
        builder.Property(c => c.CreationDate).IsRequired(false);
        builder.Property(c => c.LastUpdate).IsRequired(false);
        builder.Property(c => c.Version).IsRowVersion();
    }
}

So there is a poll object. This contains a list of "categories" stored as Json. Each category has a list of tasks. The tasks have a list of "TaskUsers".

The workflow is like this:

  • the user creates a poll (works)
  • later the user adds a category (works - the category does not contain tasks at this time)
  • then the user adds a task to a category - this is when the exception occurs

You can find a repo here: https://github.com/FM1973/EfTestJson.git

What am I missing?
Any help is appreciated. Thanks!

PS: when I add a category already containing tasks there is no error. When I try to add another task later, the error occurs again.

Provider and version information

EF Core version: 8.0.4
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 8
Operating system: Windows 10
IDE: Visual Studio 2022 17.9.6

@maumar maumar self-assigned this Apr 23, 2024
@FM1973
Copy link
Author

FM1973 commented Apr 23, 2024

Just for info:
what does work is to get the list of categories in a local variable and add a task to this local varaible.
Then I set the categories list of the poll class to a new List and save the changes (all categories gone).
Then I set the polls categories list to the local variable I created before and save the changes.

This is a workaround, but a very bad one.

@maumar
Copy link
Contributor

maumar commented Apr 24, 2024

@FM1973 try the following configuration instead:

internal sealed class PollConfiguration : IEntityTypeConfiguration<Poll>
{
    public void Configure(EntityTypeBuilder<Poll> builder)
    {
        builder.ToTable("Polls");

        builder.HasKey(c => c.Id);
        builder.Property(c => c.Title).HasMaxLength(255).IsRequired();
        builder.Property(c => c.Description).HasMaxLength(1000).IsRequired(false);
        builder.Property(c => c.Start).IsRequired(false);
        builder.Property(c => c.End).IsRequired(false);
        builder.Property(c => c.Status).IsRequired(true);
        builder.OwnsMany(c => c.Categories, d =>
        {
            d.ToJson();
            d.OwnsMany(e => e.Tasks, e =>
            {
                e.OwnsMany(f => f.TasksUsers, g =>
                {
                    g.Property(f => f.Percent).HasPrecision(18, 2);
                });
            });
        });
        builder.Property(c => c.CreationDate).IsRequired(false);
        builder.Property(c => c.LastUpdate).IsRequired(false);
        builder.Property(c => c.Version).IsRowVersion();
    }
}

Basically only make call to ToJson() on the top level.

@maumar
Copy link
Contributor

maumar commented Apr 24, 2024

Problem here is that if inner OwnsMany contains a ToJson call, the navigation gets mapped to a Tasks JSON column in the database model, instead of the top level JSON column (Categories). Instead, it should be a noop

@maumar maumar added this to the 9.0.0 milestone Apr 24, 2024
@FM1973
Copy link
Author

FM1973 commented Apr 25, 2024

Oh man! One should read the docs. Sorry to have wasted your time.
Works like a charm now!
Thanks!

@FM1973 FM1973 closed this as completed Apr 25, 2024
@maumar
Copy link
Contributor

maumar commented Apr 25, 2024

We still should fix this, so that other people don't get hit by this. Will re-open, but it's not a high priority, given the easy workaround

@maumar maumar reopened this Apr 25, 2024
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

2 participants