Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
54 changes: 50 additions & 4 deletions src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,32 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
#pragma warning disable CS0618
annotations.Remove(RelationalAnnotationNames.ContainerColumnTypeMapping);
#pragma warning restore CS0618
}

if (annotations.TryGetValue(RelationalAnnotationNames.ContainerColumnType, out var containerColumnTypeAnnotation)
// Always generate the column type for JSON columns to ensure it's captured in the snapshot
if (annotations.TryGetValue(RelationalAnnotationNames.ContainerColumnType, out var containerColumnTypeAnnotation)
&& containerColumnTypeAnnotation is { Value: string containerColumnType })
{
methodCallCodeFragments.Add(
new MethodCallCodeFragment(
nameof(RelationalOwnedNavigationBuilderExtensions.HasColumnType),
containerColumnType));

annotations.Remove(RelationalAnnotationNames.ContainerColumnType);
}
else
{
// If no explicit column type is set, use the default from the type mapping source
var typeMapping = Dependencies.RelationalTypeMappingSource.FindMapping(typeof(string));
if (typeMapping != null)
{
methodCallCodeFragments.Add(
new MethodCallCodeFragment(
nameof(RelationalOwnedNavigationBuilderExtensions.HasColumnType),
typeMapping.StoreType));
}
}
}
else if (annotations.TryGetValue(RelationalAnnotationNames.ContainerColumnType, out var containerColumnTypeAnnotation)
&& containerColumnTypeAnnotation is { Value: string containerColumnType }
&& entityType.IsOwned())
{
Expand Down Expand Up @@ -288,9 +311,32 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
#pragma warning disable CS0618
annotations.Remove(RelationalAnnotationNames.ContainerColumnTypeMapping);
#pragma warning restore CS0618
}

if (annotations.TryGetValue(RelationalAnnotationNames.ContainerColumnType, out var containerColumnTypeAnnotation)
// Always generate the column type for JSON columns to ensure it's captured in the snapshot
if (annotations.TryGetValue(RelationalAnnotationNames.ContainerColumnType, out var containerColumnTypeAnnotation)
&& containerColumnTypeAnnotation is { Value: string containerColumnType })
{
methodCallCodeFragments.Add(
new MethodCallCodeFragment(
nameof(RelationalComplexPropertyBuilderExtensions.HasColumnType),
containerColumnType));

annotations.Remove(RelationalAnnotationNames.ContainerColumnType);
}
else
{
// If no explicit column type is set, use the default from the type mapping source
var typeMapping = Dependencies.RelationalTypeMappingSource.FindMapping(typeof(string));
if (typeMapping != null)
{
methodCallCodeFragments.Add(
new MethodCallCodeFragment(
nameof(RelationalComplexPropertyBuilderExtensions.HasColumnType),
typeMapping.StoreType));
}
}
}
else if (annotations.TryGetValue(RelationalAnnotationNames.ContainerColumnType, out var containerColumnTypeAnnotation)
&& containerColumnTypeAnnotation is { Value: string containerColumnType })
{
methodCallCodeFragments.Add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4347,7 +4347,9 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()

b1.ToTable("EntityWithOneProperty", "DefaultSchema");

b1.ToJson("EntityWithTwoProperties");
b1
.ToJson("EntityWithTwoProperties")
.HasColumnType("nvarchar(max)");

b1.WithOwner("EntityWithOneProperty")
.HasForeignKey("EntityWithOnePropertyId");
Expand Down Expand Up @@ -4417,6 +4419,7 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()

Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName());
Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName());
Assert.Equal("nvarchar(max)", ownedType1.GetContainerColumnType());

var ownership2 = ownedType1.FindNavigation(nameof(EntityWithStringKey)).ForeignKey;
Assert.Equal("EntityWithTwoPropertiesEntityWithOnePropertyId", ownership2.Properties[0].Name);
Expand Down Expand Up @@ -4453,6 +4456,81 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()
Assert.Equal("Name", ownedProperties3[3].Name);
});

[ConditionalFact]
public virtual void Owned_types_mapped_to_json_with_explicit_column_type_are_stored_in_snapshot()
=> Test(
builder =>
{
builder.Entity<EntityWithOneProperty>(b =>
{
b.HasKey(x => x.Id).HasName("PK_Custom");

b.OwnsOne(
x => x.EntityWithTwoProperties, bb =>
{
bb.ToJson().HasColumnType("json");
bb.Ignore(x => x.Id);
bb.Property(x => x.AlternateId).HasJsonPropertyName("NotKey");
bb.WithOwner(e => e.EntityWithOneProperty);
});
});
},
AddBoilerPlate(
GetHeading()
+ """
modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));

b.HasKey("Id")
.HasName("PK_Custom");

b.ToTable("EntityWithOneProperty", "DefaultSchema");
});

modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b =>
{
b.OwnsOne("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithTwoProperties", "EntityWithTwoProperties", b1 =>
{
b1.Property<int>("EntityWithOnePropertyId");

b1.Property<int>("AlternateId")
.HasJsonPropertyName("NotKey");

b1.HasKey("EntityWithOnePropertyId");

b1.ToTable("EntityWithOneProperty", "DefaultSchema");

b1
.ToJson("EntityWithTwoProperties")
.HasColumnType("json");

b1.WithOwner("EntityWithOneProperty")
.HasForeignKey("EntityWithOnePropertyId");

b1.Navigation("EntityWithOneProperty");
});

b.Navigation("EntityWithTwoProperties");
});
""", usingSystem: false),
o =>
{
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
Assert.Equal("PK_Custom", entityWithOneProperty.GetKeys().Single().GetName());

var ownership1 = entityWithOneProperty.FindNavigation(nameof(EntityWithOneProperty.EntityWithTwoProperties))
.ForeignKey;
var ownedType1 = ownership1.DeclaringEntityType;
Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName());
Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName());
Assert.Equal("json", ownedType1.GetContainerColumnType());
});

private class Order
{
public int Id { get; set; }
Expand Down Expand Up @@ -6142,6 +6220,125 @@ public readonly struct Coordinates(decimal latitude, decimal longitude)

[ConditionalFact]
public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot()
=> Test(
builder =>
{
builder.Entity<EntityWithOneProperty>(b =>
{
b.HasKey(x => x.Id).HasName("PK_Custom");

b.ComplexProperty(
x => x.EntityWithTwoProperties, bb =>
{
bb.ToJson("TwoProps");
bb.Property(x => x.AlternateId).HasJsonPropertyName("NotKey");
bb.ComplexProperty(
x => x.EntityWithStringKey, bbb =>
{
bbb.ComplexCollection(x => x.Properties, bbbb => bbbb.HasJsonPropertyName("JsonProps"));
});
bb.ComplexProperty(
x => x.Coordinates, bbb =>
{
bbb.Property(c => c.Latitude).HasJsonPropertyName("Lat");
bbb.Property(c => c.Longitude).HasJsonPropertyName("Lon");
});
});
});
},
AddBoilerPlate(
GetHeading()
+ """
modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));

b.ComplexProperty(typeof(Dictionary<string, object>), "EntityWithTwoProperties", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties", b1 =>
{
b1.Property<int>("AlternateId")
.HasJsonPropertyName("NotKey");

b1.Property<int>("Id");

b1.ComplexProperty(typeof(Dictionary<string, object>), "Coordinates", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.Coordinates#Coordinates", b2 =>
{
b2.IsRequired();

b2.Property<decimal>("Latitude")
.HasJsonPropertyName("Lat");

b2.Property<decimal>("Longitude")
.HasJsonPropertyName("Lon");
});

b1.ComplexProperty(typeof(Dictionary<string, object>), "EntityWithStringKey", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey", b2 =>
{
b2.Property<string>("Id");

b2.ComplexCollection(typeof(List<Dictionary<string, object>>), "Properties", "Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty.EntityWithTwoProperties#EntityWithTwoProperties.EntityWithStringKey#EntityWithStringKey.Properties#EntityWithStringProperty", b3 =>
{
b3.Property<int>("Id");

b3.Property<string>("Name");

b3.HasJsonPropertyName("JsonProps");
});
});

b1
.ToJson("TwoProps")
.HasColumnType("nvarchar(max)");
});

b.HasKey("Id")
.HasName("PK_Custom");

b.ToTable("EntityWithOneProperty", "DefaultSchema");
});
""", usingSystem: false, usingCollections: true),
o =>
{
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
Assert.Equal("PK_Custom", entityWithOneProperty.GetKeys().Single().GetName());

var complexProperty1 = entityWithOneProperty.FindComplexProperty(nameof(EntityWithOneProperty.EntityWithTwoProperties));
Assert.False(complexProperty1.IsCollection);
Assert.True(complexProperty1.IsNullable);
var complexType1 = complexProperty1.ComplexType;
Assert.Equal("TwoProps", complexType1.GetContainerColumnName());
Assert.Equal("nvarchar(max)", complexType1.GetContainerColumnType());

var alternateIdProperty = complexType1.FindProperty(nameof(EntityWithTwoProperties.AlternateId));
Assert.Equal("NotKey", alternateIdProperty.GetJsonPropertyName());

var coordinatesComplexProperty = complexType1.FindComplexProperty(nameof(EntityWithTwoProperties.Coordinates));
Assert.False(coordinatesComplexProperty.IsCollection);
Assert.False(coordinatesComplexProperty.IsNullable);
var coordinatesComplexType = coordinatesComplexProperty.ComplexType;
var latitudeProperty = coordinatesComplexType.FindProperty(nameof(Coordinates.Latitude));
Assert.Equal("Lat", latitudeProperty.GetJsonPropertyName());
var longitudeProperty = coordinatesComplexType.FindProperty(nameof(Coordinates.Longitude));
Assert.Equal("Lon", longitudeProperty.GetJsonPropertyName());

var entityWithStringKeyComplexProperty =
complexType1.FindComplexProperty(nameof(EntityWithTwoProperties.EntityWithStringKey));
Assert.False(entityWithStringKeyComplexProperty.IsCollection);
Assert.True(entityWithStringKeyComplexProperty.IsNullable);
var entityWithStringKeyComplexType = entityWithStringKeyComplexProperty.ComplexType;

var propertiesComplexCollection =
entityWithStringKeyComplexType.FindComplexProperty(nameof(EntityWithStringKey.Properties));
Assert.True(propertiesComplexCollection.IsCollection);
Assert.Equal("JsonProps", propertiesComplexCollection.GetJsonPropertyName());
Assert.Equal(typeof(List<Dictionary<string, object>>), propertiesComplexCollection.ClrType);
});

[ConditionalFact]
public virtual void Complex_types_mapped_to_json_with_explicit_column_type_are_stored_in_snapshot()
=> Test(
builder =>
{
Expand Down
Loading