efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Columns with name "Id" in JSon object causes System.ArgumentException in materialization

Open jonnybee opened this issue 2 years ago • 22 comments

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: image

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''

jonnybee avatar Oct 17 '22 15:10 jonnybee

@maumar Looks like it could be a problem with constructor binding for JSON objects in the materializer.

ajcvickers avatar Oct 17 '22 19:10 ajcvickers

@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 avatar Oct 17 '22 19:10 jonnybee

@jonnybee Yes, I suspect that it is something to do with that.

ajcvickers avatar Oct 17 '22 19:10 ajcvickers

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

maumar avatar Oct 17 '22 19:10 maumar

we should at least throw in validation until flexible ordinal key is implemented

maumar avatar Oct 17 '22 20:10 maumar

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");
        });
    });

ajcvickers avatar Oct 20 '22 11:10 ajcvickers

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.

maumar avatar Mar 31 '23 01:03 maumar

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.

ajcvickers avatar Apr 06 '23 11:04 ajcvickers

  • 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?

Tizioincognit0 avatar Jul 12 '23 10:07 Tizioincognit0

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...

thomas-darling avatar Nov 21 '23 13:11 thomas-darling

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.

markushaslinger avatar Dec 27 '23 17:12 markushaslinger

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.

btecu avatar Jan 14 '24 19:01 btecu

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.

oleg-varlamov avatar Jan 31 '24 03:01 oleg-varlamov

I have just run into the same issue.

VaclavElias avatar Jan 31 '24 15:01 VaclavElias

Everyone, please upvote this issue (👍) rather than posting "me too" comments. This is how we gauge user demand when prioritizing.

roji avatar Jan 31 '24 19:01 roji

I just 👍your comment, but also the issue itself. Thanks for great work @roji here and also in the Community Standups.

VaclavElias avatar Jan 31 '24 21:01 VaclavElias

This issue wasted my whole day and finally found the reason here

linchenrr avatar Feb 25 '24 08:02 linchenrr

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.

ajcvickers avatar Feb 29 '24 09:02 ajcvickers

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.

markushaslinger avatar Feb 29 '24 17:02 markushaslinger

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");
        });
    });

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 avatar Mar 05 '24 17:03 sclarke81

@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.

Xen0byte avatar Apr 03 '24 19:04 Xen0byte

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; }
}

alecgorge avatar Apr 20 '24 16:04 alecgorge

Are there any new developments?☺️

WhiteScorpion avatar Jun 16 '24 03:06 WhiteScorpion

Any updates?

joseangelyanez avatar Jul 17 '24 19:07 joseangelyanez