dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.79k stars 3.19k forks source link

Value comparisons with ToJson() #32160

Closed Timovzl closed 1 year ago

Timovzl commented 1 year ago

I have a few questions regarding the equality comparisons of complex value objects mapped with OwnsOne()/OwnsMany() combined with ToJson().

First, how are equality comparisons made? Especially with 8.0's support for non-primitive collections, it seems essential that collections are compared structurally.

Second, since JSON is a case-sensitive format intended for data interchange, shouldn't a binary collation be used for the column? At the very least can the collation be set manually?

Third, what if the column is configured as a key (however unlikely that may be)? Comparisons on key properties are made on the provider value rather than the regular value. Are these string comparisons made as binary/ordinal comparisons?

roji commented 1 year ago

First, how are equality comparisons made? Especially with 8.0's https://github.com/dotnet/efcore/issues/28616, it seems essential that collections are compared structurally.

For this kind of question, it seems easy to just run a query and see. But in any case, owned entities have identity semantics, which means that two owned entity instances are identical if they're the same one in the database ("reference" comparison rather than structural comparison). This indeed isn't very useful behavior, and is one of the major reasons we're introducing complex types as an alternative modeling. For 8.0 complex type support is still quite limited (e.g. no JSON mapping support), but we intend to expand that support to replace owned entities in many scenarios.

Second, since JSON is a case-sensitive format intended for data interchange, shouldn't a binary collation be used for the column? At the very least can the collation be set manually?

That's a good question.. First, setting facets on a JSON column (including collation) is tracked by #28591. Regarding whether the collation should be binary by default, the collation of the column flows through to fragments of the documented extracted from it. That means that in a query such as .Where(b => b.SomeJsonThing.Foo == "hello"), Foo (which gets extracted via the SQL Server JSON_VALUE) has whatever collation is defined on the JSON column (SomeJsonThing). Making that a binary collation would cause all comparisons of contained data to be binary as well, which definitely doesn't seem like a good idea.

Third, what if the column is configured as a key (however unlikely that may be)? Comparisons on key properties are made on the provider value rather than the regular value. Are these string comparisons made as binary/ordinal comparisons?

This is definitely not a scenario we currently support; but I'm not sure I see any fundamental difference between this case and the case of a regular string column used as a key. Regardless, I don't think that the regular vs. provider value distinction is meaningful in the context of a JSON column.

Timovzl commented 1 year ago

owned entities have identity semantics

Whiled focused on ToJson(), I completely forgot about the distinction between owned entities and complex types. Makes sense that owned entities use reference comparisons by default.

As a workaround until complex types support ToJson(), is there a way to configure the comparer in the owned entity JSON scenario? It's not entirely clear to me if that's a part of the issue tracking JSON column facets, the one tracking JSON custom serialization, or neither.

roji commented 1 year ago

Makes sense that owned entities use reference comparisons by default.

It's not by default - it's always.

As a workaround until complex types support ToJson(), is there a way to configure the comparer in the owned entity JSON scenario?

Comparers work at the property level, not at the entity level. EF doesn't compare entities (or complex types) directly to each other, it compares their contained properties one-by-one (as it needs to know which ones changed).