Skip to content

Commit 10a53a0

Browse files
CopilotAndriySvyryd
andcommitted
Fix snapshot generation to capture column type for JSON columns (port of #37284 without complex types)
Co-authored-by: AndriySvyryd <[email protected]>
1 parent 775ed24 commit 10a53a0

File tree

3 files changed

+149
-1
lines changed

3 files changed

+149
-1
lines changed

src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Text;
5+
using System.Text.Json;
56
using Microsoft.EntityFrameworkCore.Internal;
67
using Microsoft.EntityFrameworkCore.Metadata.Internal;
78

@@ -837,6 +838,20 @@ protected virtual void GenerateEntityTypeAnnotations(
837838
.FilterIgnoredAnnotations(entityType.GetAnnotations())
838839
.ToDictionary(a => a.Name, a => a);
839840

841+
// Add ContainerColumnType annotation if entity is mapped to JSON but the type annotation is missing
842+
if (annotations.ContainsKey(RelationalAnnotationNames.ContainerColumnName)
843+
&& !annotations.ContainsKey(RelationalAnnotationNames.ContainerColumnType))
844+
{
845+
var containerColumnType = entityType.GetContainerColumnType()
846+
?? Dependencies.RelationalTypeMappingSource.FindMapping(typeof(JsonElement))?.StoreType;
847+
if (containerColumnType != null)
848+
{
849+
annotations[RelationalAnnotationNames.ContainerColumnType] = new Annotation(
850+
RelationalAnnotationNames.ContainerColumnType,
851+
containerColumnType);
852+
}
853+
}
854+
840855
GenerateTableMapping(entityTypeBuilderName, entityType, stringBuilder, annotations);
841856
GenerateSplitTableMapping(entityTypeBuilderName, entityType, stringBuilder);
842857

test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4362,7 +4362,9 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()
43624362
43634363
b1.ToTable("EntityWithOneProperty", "DefaultSchema");
43644364
4365-
b1.ToJson("EntityWithTwoProperties");
4365+
b1
4366+
.ToJson("EntityWithTwoProperties")
4367+
.HasColumnType("nvarchar(max)");
43664368
43674369
b1.WithOwner("EntityWithOneProperty")
43684370
.HasForeignKey("EntityWithOnePropertyId");
@@ -4437,6 +4439,7 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()
44374439

44384440
Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName());
44394441
Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName());
4442+
Assert.Equal("nvarchar(max)", ownedType1.GetContainerColumnType());
44404443

44414444
var ownership2 = ownedType1.FindNavigation(nameof(EntityWithStringKey)).ForeignKey;
44424445
Assert.Equal("EntityWithTwoPropertiesEntityWithOnePropertyId", ownership2.Properties[0].Name);
@@ -4473,6 +4476,83 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()
44734476
Assert.Equal("Name", ownedProperties3[3].Name);
44744477
});
44754478

4479+
[ConditionalFact]
4480+
public virtual void Owned_types_mapped_to_json_with_explicit_column_type_are_stored_in_snapshot()
4481+
=> Test(
4482+
builder =>
4483+
{
4484+
builder.Entity<EntityWithOneProperty>(b =>
4485+
{
4486+
b.HasKey(x => x.Id).HasName("PK_Custom");
4487+
4488+
b.OwnsOne(
4489+
x => x.EntityWithTwoProperties, bb =>
4490+
{
4491+
bb.ToJson().HasColumnType("json");
4492+
bb.Ignore(x => x.Id);
4493+
bb.Property(x => x.AlternateId).HasJsonPropertyName("NotKey");
4494+
bb.WithOwner(e => e.EntityWithOneProperty);
4495+
});
4496+
});
4497+
},
4498+
AddBoilerPlate(
4499+
GetHeading()
4500+
+ """
4501+
modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b =>
4502+
{
4503+
b.Property<int>("Id")
4504+
.ValueGeneratedOnAdd()
4505+
.HasColumnType("int");
4506+
4507+
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
4508+
4509+
b.HasKey("Id")
4510+
.HasName("PK_Custom");
4511+
4512+
b.ToTable("EntityWithOneProperty", "DefaultSchema");
4513+
});
4514+
4515+
modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b =>
4516+
{
4517+
b.OwnsOne("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithTwoProperties", "EntityWithTwoProperties", b1 =>
4518+
{
4519+
b1.Property<int>("EntityWithOnePropertyId")
4520+
.HasColumnType("int");
4521+
4522+
b1.Property<int>("AlternateId")
4523+
.HasColumnType("int")
4524+
.HasAnnotation("Relational:JsonPropertyName", "NotKey");
4525+
4526+
b1.HasKey("EntityWithOnePropertyId");
4527+
4528+
b1.ToTable("EntityWithOneProperty", "DefaultSchema");
4529+
4530+
b1
4531+
.ToJson("EntityWithTwoProperties")
4532+
.HasColumnType("json");
4533+
4534+
b1.WithOwner("EntityWithOneProperty")
4535+
.HasForeignKey("EntityWithOnePropertyId");
4536+
4537+
b1.Navigation("EntityWithOneProperty");
4538+
});
4539+
4540+
b.Navigation("EntityWithTwoProperties");
4541+
});
4542+
""", usingSystem: false),
4543+
o =>
4544+
{
4545+
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
4546+
Assert.Equal("PK_Custom", entityWithOneProperty.GetKeys().Single().GetName());
4547+
4548+
var ownership1 = entityWithOneProperty.FindNavigation(nameof(EntityWithOneProperty.EntityWithTwoProperties))
4549+
.ForeignKey;
4550+
var ownedType1 = ownership1.DeclaringEntityType;
4551+
Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName());
4552+
Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName());
4553+
Assert.Equal("json", ownedType1.GetContainerColumnType());
4554+
});
4555+
44764556
private class Order
44774557
{
44784558
public int Id { get; set; }

test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,4 +1900,57 @@ public void Rebuild_index_with_different_datacompression_value()
19001900

19011901
Assert.Equal(DataCompressionType.Page, annotationValue);
19021902
});
1903+
1904+
[ConditionalFact]
1905+
public void Alter_column_from_nvarchar_max_to_json_for_owned_type()
1906+
=> Execute(
1907+
_ => { },
1908+
source => source.Entity(
1909+
"Blog",
1910+
x =>
1911+
{
1912+
x.Property<int>("Id");
1913+
x.HasKey("Id");
1914+
x.OwnsOne(
1915+
"Details", "Details", d =>
1916+
{
1917+
d.Property<string>("Author");
1918+
d.Property<int>("Viewers");
1919+
d.ToJson();
1920+
});
1921+
}),
1922+
target => target.Entity(
1923+
"Blog",
1924+
x =>
1925+
{
1926+
x.Property<int>("Id");
1927+
x.HasKey("Id");
1928+
x.OwnsOne(
1929+
"Details", "Details", d =>
1930+
{
1931+
d.Property<string>("Author");
1932+
d.Property<int>("Viewers");
1933+
d.ToJson().HasColumnType("json");
1934+
});
1935+
}),
1936+
upOps =>
1937+
{
1938+
Assert.Equal(1, upOps.Count);
1939+
1940+
var operation = Assert.IsType<AlterColumnOperation>(upOps[0]);
1941+
Assert.Equal("Blog", operation.Table);
1942+
Assert.Equal("Details", operation.Name);
1943+
Assert.Equal("json", operation.ColumnType);
1944+
Assert.Equal("nvarchar(max)", operation.OldColumn.ColumnType);
1945+
},
1946+
downOps =>
1947+
{
1948+
Assert.Equal(1, downOps.Count);
1949+
1950+
var operation = Assert.IsType<AlterColumnOperation>(downOps[0]);
1951+
Assert.Equal("Blog", operation.Table);
1952+
Assert.Equal("Details", operation.Name);
1953+
Assert.Equal("nvarchar(max)", operation.ColumnType);
1954+
Assert.Equal("json", operation.OldColumn.ColumnType);
1955+
});
19031956
}

0 commit comments

Comments
 (0)