efcore
efcore copied to clipboard
Columns with name "Id" in JSon object causes System.ArgumentException in materialization
Using EF Core 7.0.0-rc.2.22472.11 (also same in v7.0.0-rtm.22504.12)
When JSon child object has a column named Id (f.ex of type Int) the materialization will throw: System.ArgumentException: 'Expression of type 'System.Object' cannot be used for constructor parameter of type 'System.Int32'
Make these modifications to NewInEFCore7 sample in EntityFramework.Docs to reproduce the bug:
public class Commit
{
public Commit(int id, DateTime committedOn, string comment)
{
Id = id;
CommittedOn = committedOn;
Comment = comment;
}
public int Id {get; private set; }
public DateTime CommittedOn { get; private set; }
public string Comment { get; set; }
}
// Update BuildPostMetadata method to send in an id as constructor parameter
for (var j = 0; j < random.Next(3); j++)
{
update.Commits.Add(new(j+1, DateTime.Today, $"Commit #{j + 1}"));
}
When you run the sample it will now throw ArgumentException here:
If I change Id to a property with { get; set; } and no parameters to the CTOR this exception is thrown: System.ArgumentException: 'Expression of type 'System.Object' cannot be used for assignment to type 'System.Int32''
@maumar Looks like it could be a problem with constructor binding for JSON objects in the materializer.
@maumar @ajcvickers Other properties of type int is deserialized OK - but when named Id will throw the ArgumentException. Could it be that Id is considered Key by convention for that type?
@jonnybee Yes, I suspect that it is something to do with that.
owned types that are part of the collection have hard-coded ordinal key named "Id" of type int. Related issue: https://github.com/dotnet/efcore/issues/28594
we should at least throw in validation until flexible ordinal key is implemented
Note from triage: we should investigate if this is a materialization bug separate from #28594.
Possible workaround if the entity doesn't need the property to be called Id
but Id
is required in the JSON:
modelBuilder.Entity<Customer>()
.OwnsOne(customer => customer.Contact, b =>
{
b.ToJson();
b.OwnsMany(e => e.PhoneNumbers, b =>
{
b.Property(e => e.Key).HasJsonPropertyName("Id");
});
});
the problem comes from the fact that the Id property is considered to be a key. In case of JSON entity each property is either column access (in case of key - we project the key column and access value based on that column index) or JsonElement.TryGetProperty (in case of normal JSON property). In this case Id column is considered to be a key, so we try to access it based on index (which isn't there) and hence the bug.
There doesn't seem to be a separate materialization bug, apart from https://github.com/dotnet/efcore/issues/28594.
Notes from triage:
- It should be possible to have a property called "Id" that is not the ordinal key.
- Being able to explicitly map the key (#28594) is also important, but different from this issue.
- It should be possible to have a property called "Id" that is not the ordinal key.
Is there a way to have this feature on EF 7.0.8?
According to the Npgsql docs, they've deprecated their support for .HasColumnType("jsonb")
, which worked perfectly, in favor of .ToJson()
- which won't work for us until this is fixed, as we just got blocked by https://github.com/dotnet/efcore/issues/32360.
Please prioritize fixing this - it's been open for more than a year already...
I ran into this today as well and it took me quite some time to figure it out (found this issue only after having identified the root cause). If this can't be fixed soon I'd suggest adding a warning to the documentation at least.
Just ran into this issue and spent a couple of hours until I got to this ticket. A warning would be useful but even better fixing the issue.
I also encountered this problem today. Microsoft writes everywhere about good JSON support in the EF, but in fact we have many problems that prevent the use of JSON in real applications. We are waiting for a solution from the EFCore team.
I have just run into the same issue.
Everyone, please upvote this issue (👍) rather than posting "me too" comments. This is how we gauge user demand when prioritizing.
I just 👍your comment, but also the issue itself. Thanks for great work @roji here and also in the Community Standups.
This issue wasted my whole day and finally found the reason here
Note from team triage: really fixing this in 8.0 is not feasible. However, we can throw a better exception message with directions to the workarounds.
Note from team triage: really fixing this in 8.0 is not feasible. However, we can throw a better exception message with directions to the workarounds.
Thanks for the update. While I do hope a full fix will be possible for 9.0, I think a proper error message that tells people to 'use any name but Id' should be an ok stopgap measure for now.
Note from triage: we should investigate if this is a materialization bug separate from #28594.
Possible workaround if the entity doesn't need the property to be called
Id
butId
is required in the JSON:modelBuilder.Entity<Customer>() .OwnsOne(customer => customer.Contact, b => { b.ToJson(); b.OwnsMany(e => e.PhoneNumbers, b => { b.Property(e => e.Key).HasJsonPropertyName("Id"); }); });
If you do need/want the property to be called Id as well, the following worked for me. It feels messy, but I haven't found a better way.
modelBuilder.Entity<Customer>()
.OwnsOne(customer => customer.Contact, b =>
{
b.ToJson();
b.OwnsMany(e => e.PhoneNumbers, b =>
{
b.Property<string>("_identifier");
});
});
public class PhoneNumber
{
[JsonPropertyName("id")]
[JsonInclude]
private string _identifier;
[NotMapped]
[JsonIgnore]
public string Id
{
get => _identifier;
set => _identifier = value;
}
}
@sclarke81 ...
Thank you so much for this! It's a little messy but I'd rather use it and have consistent property names across my entities.
Thanks @sclarke81 for the example! I ended up using a slight variation (removing [JsonIgnore]
) to get parsing from a JSON API response to work as well:
public class Tag
{
[JsonPropertyName("Id")]
[JsonInclude]
private int _identifier;
[NotMapped]
public int Id
{
get => _identifier;
init => _identifier = value;
}
public required string Name { get; init; }
}
Are there any new developments?☺️
Any updates?