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

Storing Same Owned Entity In Json Column Does Not Work #33967

Closed
dradovic opened this issue Jun 12, 2024 · 3 comments
Closed

Storing Same Owned Entity In Json Column Does Not Work #33967

dradovic opened this issue Jun 12, 2024 · 3 comments
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@dradovic
Copy link

Description

When the same instance of an object is referenced from multiple entities, it is only stored once and silently discarded in other parents when storing it in a JSON column. IMHO, at least an error should be thrown instead of just dropping the data if for some reason it is not possible to store a JSON serialized copy of "shared" instances or if that would lead to an undesirable design.

How to reproduce - the code

In order for you to reproduce easily, I've extracted the problem to this repository:
https://github.com/dradovic/MissingLocationJsonRepro

The repo contains 3 succeedingly stripped down cases in 3 branches.

  1. main branch: The story starts with an entity called Boat, where Passengers are stored in a JSON column.
public class Boat
{
    public int Id { get; set; }
    public ICollection<PassengerInfo> Passengers { get; set; } = null!;
    public override string ToString() => $"Boat {Id}: {string.Join(" ; ", Passengers)}";
}
...
builder.Entity<Boat>().OwnsMany(b => b.Passengers).ToJson();

A PassengerInfo is reflecting some Person data:

public class PassengerInfo
{
    public string Name { get; set; } = null!;
    public Location Location { get; set; } = null!;
    public override string ToString() => $"Passenger: {Name} @ {Location}";
}

public class Location
{
    public string Address1 { get; set; } = null!;
    public string City { get; set; } = null!;
    public override string ToString() => $"{Address1} in {City}";
}

Populating some boats with passengers whose Location points to the same instance results in:

foreach (var boat in db.Boats)
{
    Console.WriteLine(boat);
    // Boat 5: Passenger: Joe Doe @
    // Boat 6: Passenger: Joe Doe @
    // Boat 7: Passenger: Joe Doe @ Jumpy Rd. 1 in Duville
    // Id	Passengers
    // 5    [{ "Name":"Joe Doe","Location":null}]
    // 6    [{ "Name":"Joe Doe","Location":null}]
    // 7    [{ "Name":"Joe Doe","Location":{ "Address1":"Jumpy Rd. 1","City":"Duville"} }]
}

As we can see, the location is only stored for the last boat's passenger but silently dropped for the others(!).

  1. OnlyLocation branch: I then went on to remove the intermediary PassangerInfo class and to try to store the locations directly.
public class Boat
{
    public int Id { get; set; }
    public ICollection<Location> Passengers { get; set; } = null!;
    public override string ToString() => $"Boat {Id}: {string.Join(" ; ", Passengers)}";
}

Again, data is silently discarded if we try to store the same location in multiple rows:

foreach (var boat in db.Boats)
{
    Console.WriteLine(boat);
    // Boat 8:
    // Boat 9:
    // Boat 10: Jumpy Rd. 1 in Duville
    // Id	Passengers
    // 8    []
    // 9    []
    // 10   [{ "Address1":"Jumpy Rd. 1","City":"Duville"}]
}

This time, JSON serialization does not say null but just discards the whole object.

  1. OnlyOneLocation branch: in this branch, I've changed the collection to be a one-to-one relationship to Location.
public class Boat
{
    public int Id { get; set; }
    public Location Location { get; set; } = null!;
    public override string ToString() => $"Boat {Id}: {Location}";
}

And this time, the data cannot even be persisted as I get a: SqlException: Cannot insert the value NULL into column 'Passenger', table 'MissingLocationJsonRepro.dbo.Boats' if I try to persist the same location referenced from multiple boats which shows that EF again tried to drop data.

Include provider and version information

EF Core version:
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 8.0
Operating system: Windows
IDE: Visual Studio 2022 17.10.1

@dradovic dradovic changed the title Storing of Same Entity In Json Column Drops It Storing Same Entity In Json Column Does Not Work Jun 12, 2024
@ajcvickers
Copy link
Member

Populating some boats with passengers whose Location points to the same instance

When the code move the same instance like this, it is changing the relationship of that single instance. So it is first associated with passenger 1, then removed from passenger 1 and associated with passenger 2, and so on. A single object instance cannot be used to represent multiple different entities. This is one of the reasons why complex types are a better fit than entity types for mapping to JSON.

@dradovic
Copy link
Author

@ajcvickers I see. I think that the confusing part is that Location is used as an owned entity throughout our app. (The model is configured with builder.Owned<Location>();). And so, Location has no Id or key or whatsoever that would imply a sense of referential identity on a DB-level. Which in turn implies a by-copy/value semantic. Even more so, when that object just needs to be dumped into a JSON string.
So, if I have an object graph where here and there a location is being referenced, I would have expected it to be just inlined into the JSON without any "identity tracking". Certainly not that it only appears in one place and silently is discarded in all other places.

Reading your EF Core 8 RC1: Complex types as value objects makes it clear to me that we should switch the modelling of Location to rather be a Complex Type than an Owned Type. However, this will then make us dependent on #31252 (if we want to store locations in JSON) if my understanding is correct.

There's also #32799 which to a large extent seems to be a duplicate of this issue here.

So, if you don't plan on any changes - even throwing an error, which seems to be hard to implement according to your comment here - we can close this issue.

Thanks a lot for your valuable input.

@dradovic dradovic changed the title Storing Same Entity In Json Column Does Not Work Storing Same Owned Entity In Json Column Does Not Work Jun 13, 2024
@ajcvickers
Copy link
Member

Which in turn implies a by-copy/value semantic. Even more so, when that object just needs to be dumped into a JSON string.

Unfortunately, this is not how owned types work. They are still entity types and follow entity semantics.

we should switch the modelling of Location to rather be a Complex Type than an Owned Type.

Correct.

However, this will then make us dependent on #31252 (if we want to store locations in JSON) if my understanding is correct.

Yes, this is not a great place for us to be in, but prioritization of resources has been very difficult. We're working hard to get to a consistent space.

@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Jun 15, 2024
@ajcvickers ajcvickers added the closed-no-further-action The issue is closed and no further action is planned. label Jun 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests

2 participants