Update nested Json with generic lists - System.ArgumentNullException: 'Value cannot be null. Arg_ParamName_Name'
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
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<PollCategory> 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.
@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.
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
Oh man! One should read the docs. Sorry to have wasted your time. Works like a charm now! Thanks!
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
after a bug fixing war I found this thank you