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

Various stuff from EFCore.PG JSON work #32255

Merged
merged 1 commit into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2800,6 +2800,14 @@ protected virtual void ValidateJsonEntityProperties(
RelationalStrings.JsonEntityWithMultiplePropertiesMappedToSameJsonProperty(
jsonEntityType.DisplayName(), jsonPropertyName));
}

var jsonValueReaderWriter = property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter;
if (jsonValueReaderWriter is null)
{
throw new InvalidOperationException(
RelationalStrings.JsonValueReadWriterMissingOnTypeMapping(
property.GetTypeMapping().GetType().Name, property.Name, jsonEntityType.DisplayName()));
}
}

foreach (var navigation in jsonEntityType.GetDeclaredNavigations())
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,9 @@
<data name="JsonRequiredEntityWithNullJson" xml:space="preserve">
<value>Entity {entity} is required but the JSON element containing it is null.</value>
</data>
<data name="JsonValueReadWriterMissingOnTypeMapping" xml:space="preserve">
<value>Type mapping type '{typeMapping}', which is being used on property '{property}' on entity type '{entityType}' in a JSON document, has not defined a JsonValueReaderWriter.</value>
</data>
<data name="KeylessMappingStrategy" xml:space="preserve">
<value>The mapping strategy '{mappingStrategy}' used for '{entityType}' is not supported for keyless entity types. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.</value>
</data>
Expand Down
4 changes: 3 additions & 1 deletion src/EFCore.Relational/Update/ModificationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -902,7 +902,9 @@ private void WriteJson(

if (value is not null)
{
(property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter)!.ToJson(writer, value);
var jsonValueReaderWriter = property.GetJsonValueReaderWriter() ?? property.GetTypeMapping().JsonValueReaderWriter;
Check.DebugAssert(jsonValueReaderWriter is not null, "Missing JsonValueReaderWriter on JSON property");
jsonValueReaderWriter.ToJson(writer, value);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ protected override ShapedQueryExpression TransformJsonQueryToTable(JsonQueryExpr
}
}

// Navigations represent nested JSON owned entities, which we also add to the OPENJSON WITH clause, but with AS JSON.
foreach (var navigation in jsonQueryExpression.EntityType.GetNavigationsInHierarchy()
.Where(
n => n.ForeignKey.IsOwnership
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,20 @@ public OriginalValues(InternalEntityEntry entry)
}

public object? GetValue(InternalEntityEntry entry, IProperty property)
{
var index = property.GetOriginalValueIndex();
if (index == -1)
{
throw new InvalidOperationException(
CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringType.DisplayName()));
}

return IsEmpty ? entry[property] : _values[index];
}
=> property.GetOriginalValueIndex() is var index && index == -1
? throw new InvalidOperationException(
CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringType.DisplayName()))
: IsEmpty
? entry[property]
: _values[index];

public T GetValue<T>(InternalEntityEntry entry, IProperty property, int index)
{
if (index == -1)
{
throw new InvalidOperationException(
CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringType.DisplayName()));
}

return IsEmpty ? entry.GetCurrentValue<T>(property) : _values.GetValue<T>(index);
}
=> index == -1
? throw new InvalidOperationException(
CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringType.DisplayName()))
: IsEmpty
? entry.GetCurrentValue<T>(property)
: _values.GetValue<T>(index);

public void SetValue(IProperty property, object? value, int index)
{
Expand Down
3 changes: 1 addition & 2 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -958,8 +958,7 @@ public T ReadTemporaryValue<T>(int storeGeneratedIndex)
=> _temporaryValues.GetValue<T>(storeGeneratedIndex);

private static readonly MethodInfo GetCurrentValueMethod
= typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethods(nameof(GetCurrentValue)).Single(
m => m.IsGenericMethod);
= typeof(InternalEntityEntry).GetTypeInfo().GetDeclaredMethods(nameof(GetCurrentValue)).Single(m => m.IsGenericMethod);

[UnconditionalSuppressMessage(
"ReflectionAnalysis", "IL2060",
Expand Down
38 changes: 17 additions & 21 deletions src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,29 +115,25 @@ protected virtual Expression CreateSnapshotExpression(
for (var i = 0; i < count; i++)
{
var propertyBase = propertyBases[i];
if (propertyBase == null)
{
arguments[i] = Expression.Constant(null);
types[i] = typeof(object);
continue;
}

if (propertyBase is IProperty property)
{
arguments[i] = CreateSnapshotValueExpression(CreateReadValueExpression(parameter, property), property);
continue;
}

if (propertyBase is IComplexProperty complexProperty)
{
arguments[i] = CreateSnapshotValueExpression(CreateReadValueExpression(parameter, complexProperty), complexProperty);
continue;
}

if (propertyBase.IsShadowProperty())
switch (propertyBase)
{
arguments[i] = CreateSnapshotValueExpression(CreateReadShadowValueExpression(parameter, propertyBase), propertyBase);
continue;
case null:
arguments[i] = Expression.Constant(null);
types[i] = typeof(object);
continue;

case IProperty property:
arguments[i] = CreateSnapshotValueExpression(CreateReadValueExpression(parameter, property), property);
continue;

case IComplexProperty complexProperty:
arguments[i] = CreateSnapshotValueExpression(CreateReadValueExpression(parameter, complexProperty), complexProperty);
continue;

case var _ when propertyBase.IsShadowProperty():
arguments[i] = CreateSnapshotValueExpression(CreateReadShadowValueExpression(parameter, propertyBase), propertyBase);
continue;
}

var memberInfo = propertyBase.GetMemberInfo(forMaterialization: false, forSet: false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,53 @@ public virtual Task Can_read_write_collection_of_ASCII_string_JSON_values(object
{ RelationalAnnotationNames.StoreType, storeType }, { CoreAnnotationNames.Unicode, false }
});

public override Task Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json)
{
if (value == EnumU64.Max)
{
json = """{"Prop":-1}"""; // Because ulong is converted to long on SQL Server
}

return base.Can_read_write_ulong_enum_JSON_values(value, json);
}

public override Task Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json)
{
if (Equals(value, ulong.MaxValue))
{
json = """{"Prop":-1}"""; // Because ulong is converted to long on SQL Server
}

return base.Can_read_write_nullable_ulong_enum_JSON_values(value, json);
}

public override Task Can_read_write_collection_of_ulong_enum_JSON_values()
=> Can_read_and_write_JSON_value<EnumU64CollectionType, List<EnumU64>>(
nameof(EnumU64CollectionType.EnumU64),
[
EnumU64.Min,
EnumU64.Max,
EnumU64.Default,
EnumU64.One,
(EnumU64)8
],
"""{"Prop":[0,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server
mappedCollection: true);

public override Task Can_read_write_collection_of_nullable_ulong_enum_JSON_values()
=> Can_read_and_write_JSON_value<NullableEnumU64CollectionType, List<EnumU64?>>(
nameof(NullableEnumU64CollectionType.EnumU64),
[
EnumU64.Min,
null,
EnumU64.Max,
EnumU64.Default,
EnumU64.One,
(EnumU64?)8
],
"""{"Prop":[0,null,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server
mappedCollection: true);

protected override void AssertElementFacets(IElementType element, Dictionary<string, object?>? facets)
{
base.AssertElementFacets(element, facets);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,7 @@ public Func<DbContext> GetContextCreator()
=> () => CreateContext();

public virtual ISetSource GetExpectedData()
{
if (_expectedData == null)
{
_expectedData = new JsonQueryData();
}

return _expectedData;
}
=> _expectedData ??= new JsonQueryData();

public IReadOnlyDictionary<Type, object> EntitySorters { get; } = new Dictionary<Type, Func<object, object>>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ public void Json_multiple_json_entities_mapped_to_the_same_column()
}

[ConditionalFact]
public void Json_entity_with_defalt_value_on_a_property()
public void Json_entity_with_default_value_on_a_property()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity<ValidatorJsonEntityBasic>(
Expand Down Expand Up @@ -421,6 +421,31 @@ public void Json_entity_with_property_and_navigation_mapped_to_same_json_name()
modelBuilder);
}

[ConditionalFact]
public void Json_entity_with_property_without_JsonValueReaderWriter()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity<ValidatorJsonEntityBasic>(
b =>
{
b.OwnsOne(
x => x.OwnedReference, bb =>
{
bb.Property(x => x.Name).Metadata.SetJsonValueReaderWriterType(null);
bb.Property(x => x.Number).HasJsonPropertyName("Foo");
bb.ToJson("reference");
bb.Ignore(x => x.NestedReference);
bb.Ignore(x => x.NestedCollection);
});
b.Ignore(x => x.OwnedCollection);
});

// The test model uses TestStringTypeMapping, which indeed doesn't have a JsonValueReaderWriter
VerifyError(
RelationalStrings.JsonValueReadWriterMissingOnTypeMapping("TestStringTypeMapping", "Name", "ValidatorJsonOwnedRoot"),
modelBuilder);
}

[ConditionalFact]
public void Json_on_base_and_derived_mapped_to_same_column_throws()
{
Expand Down
9 changes: 1 addition & 8 deletions test/EFCore.Specification.Tests/Query/Ef6GroupByTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -813,14 +813,7 @@ protected override Task SeedAsync(ArubaContext context)
}

public virtual ISetSource GetExpectedData()
{
if (_expectedData == null)
{
_expectedData = new ArubaData();
}

return _expectedData;
}
=> _expectedData ??= new ArubaData();

public IReadOnlyDictionary<Type, object> EntitySorters { get; } = new Dictionary<Type, Func<object, object>>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,12 @@ namespace Microsoft.EntityFrameworkCore;

public abstract class JsonTypesSqlServerTestBase : JsonTypesRelationalTestBase
{
public override Task Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json)
{
if (value == EnumU64.Max)
{
json = """{"Prop":-1}"""; // Because ulong is converted to long on SQL Server
}

return base.Can_read_write_ulong_enum_JSON_values(value, json);
}

public override Task Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json)
{
if (Equals(value, ulong.MaxValue))
{
json = """{"Prop":-1}"""; // Because ulong is converted to long on SQL Server
}

return base.Can_read_write_nullable_ulong_enum_JSON_values(value, json);
}

public override Task Can_read_write_collection_of_ulong_enum_JSON_values()
=> Can_read_and_write_JSON_value<EnumU64CollectionType, List<EnumU64>>(
nameof(EnumU64CollectionType.EnumU64),
[
EnumU64.Min,
EnumU64.Max,
EnumU64.Default,
EnumU64.One,
(EnumU64)8
],
"""{"Prop":[0,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server
mappedCollection: true);

public override Task Can_read_write_collection_of_nullable_ulong_enum_JSON_values()
=> Can_read_and_write_JSON_value<NullableEnumU64CollectionType, List<EnumU64?>>(
nameof(NullableEnumU64CollectionType.EnumU64),
[
EnumU64.Min,
null,
EnumU64.Max,
EnumU64.Default,
EnumU64.One,
(EnumU64?)8
],
"""{"Prop":[0,null,-1,0,1,8]}""", // Because ulong is converted to long on SQL Server
mappedCollection: true);

public override Task Can_read_write_collection_of_fixed_length_string_JSON_values(object? storeType)
=> base.Can_read_write_collection_of_fixed_length_string_JSON_values("nchar(32)");

public override Task Can_read_write_collection_of_ASCII_string_JSON_values(object? storeType)
=> base.Can_read_write_collection_of_ASCII_string_JSON_values("varchar(max)");


protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;

Expand Down
Loading