Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
63 changes: 56 additions & 7 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -421,10 +421,24 @@ protected virtual void GenerateProperties(
string entityTypeBuilderName,
IEnumerable<IProperty> properties,
IndentedStringBuilder stringBuilder)
=> GenerateProperties(entityTypeBuilderName, properties, stringBuilder, isInComplexCollection: false);

/// <summary>
/// Generates code for <see cref="IProperty" /> objects.
/// </summary>
/// <param name="entityTypeBuilderName">The name of the builder variable.</param>
/// <param name="properties">The properties.</param>
/// <param name="stringBuilder">The builder code is added to.</param>
/// <param name="isInComplexCollection">Whether the properties are in a complex collection.</param>
protected virtual void GenerateProperties(
string entityTypeBuilderName,
IEnumerable<IProperty> properties,
IndentedStringBuilder stringBuilder,
bool isInComplexCollection)
{
foreach (var property in properties)
{
GenerateProperty(entityTypeBuilderName, property, stringBuilder);
GenerateProperty(entityTypeBuilderName, property, stringBuilder, isInComplexCollection);
}
}

Expand All @@ -438,6 +452,20 @@ protected virtual void GenerateProperty(
string entityTypeBuilderName,
IProperty property,
IndentedStringBuilder stringBuilder)
=> GenerateProperty(entityTypeBuilderName, property, stringBuilder, isInComplexCollection: false);

/// <summary>
/// Generates code for an <see cref="IProperty" />.
/// </summary>
/// <param name="entityTypeBuilderName">The name of the builder variable.</param>
/// <param name="property">The property.</param>
/// <param name="stringBuilder">The builder code is added to.</param>
/// <param name="isInComplexCollection">Whether the property is in a complex collection.</param>
protected virtual void GenerateProperty(
string entityTypeBuilderName,
IProperty property,
IndentedStringBuilder stringBuilder,
bool isInComplexCollection)
{
var clrType = (FindValueConverter(property)?.ProviderClrType ?? property.ClrType)
.MakeNullable(property.IsNullable);
Expand All @@ -452,7 +480,8 @@ protected virtual void GenerateProperty(
// Note that GenerateAnnotations below does the corresponding decrement
stringBuilder.IncrementIndent();

if (property.IsConcurrencyToken)
// ComplexCollectionTypePropertyBuilder doesn't have IsConcurrencyToken method
if (!isInComplexCollection && property.IsConcurrencyToken)
{
stringBuilder
.AppendLine()
Expand All @@ -466,7 +495,8 @@ protected virtual void GenerateProperty(
.Append(".IsRequired()");
}

if (property.ValueGenerated != ValueGenerated.Never)
// ComplexCollectionTypePropertyBuilder doesn't have ValueGenerated* methods
if (!isInComplexCollection && property.ValueGenerated != ValueGenerated.Never)
{
stringBuilder
.AppendLine()
Expand All @@ -480,7 +510,7 @@ protected virtual void GenerateProperty(
: ".ValueGeneratedOnAddOrUpdate()");
}

GeneratePropertyAnnotations(propertyBuilderName, property, stringBuilder);
GeneratePropertyAnnotations(propertyBuilderName, property, stringBuilder, isInComplexCollection);
}

/// <summary>
Expand All @@ -493,13 +523,32 @@ protected virtual void GeneratePropertyAnnotations(
string propertyBuilderName,
IProperty property,
IndentedStringBuilder stringBuilder)
=> GeneratePropertyAnnotations(propertyBuilderName, property, stringBuilder, isInComplexCollection: false);

/// <summary>
/// Generates code for the annotations on an <see cref="IProperty" />.
/// </summary>
/// <param name="propertyBuilderName">The name of the builder variable.</param>
/// <param name="property">The property.</param>
/// <param name="stringBuilder">The builder code is added to.</param>
/// <param name="isInComplexCollection">Whether the property is in a complex collection.</param>
protected virtual void GeneratePropertyAnnotations(
string propertyBuilderName,
IProperty property,
IndentedStringBuilder stringBuilder,
bool isInComplexCollection)
{
var annotations = Dependencies.AnnotationCodeGenerator
.FilterIgnoredAnnotations(property.GetAnnotations())
.ToDictionary(a => a.Name, a => a);

GenerateFluentApiForMaxLength(property, stringBuilder);
GenerateFluentApiForPrecisionAndScale(property, stringBuilder);
// ComplexCollectionTypePropertyBuilder doesn't have HasMaxLength or HasPrecision methods
if (!isInComplexCollection)
{
GenerateFluentApiForMaxLength(property, stringBuilder);
GenerateFluentApiForPrecisionAndScale(property, stringBuilder);
}

GenerateFluentApiForIsUnicode(property, stringBuilder);

if (!annotations.ContainsKey(RelationalAnnotationNames.ColumnType)
Expand Down Expand Up @@ -594,7 +643,7 @@ protected virtual void GenerateComplexProperty(
.AppendLine(".IsRequired();");
}

GenerateProperties(complexTypeBuilderName, complexType.GetDeclaredProperties(), stringBuilder);
GenerateProperties(complexTypeBuilderName, complexType.GetDeclaredProperties(), stringBuilder, complexProperty.IsCollection);

GenerateComplexProperties(complexTypeBuilderName, complexType.GetDeclaredComplexProperties(), stringBuilder);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6259,6 +6259,95 @@ public virtual void Complex_types_mapped_to_json_are_stored_in_snapshot()
Assert.Equal(typeof(List<Dictionary<string, object>>), propertiesComplexCollection.ClrType);
});

[ConditionalFact]
public virtual void Complex_collection_property_annotations_not_supported_by_builder_are_ignored_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").HasColumnType("json");
bb.ComplexProperty(
x => x.EntityWithStringKey, bbb =>
{
bbb.ComplexCollection(x => x.Properties, bbbb =>
{
bbbb.HasJsonPropertyName("JsonProps");
// Set MaxLength directly on the model to simulate convention behavior
// This should NOT appear in snapshot because ComplexCollectionTypePropertyBuilder
// doesn't support HasMaxLength
bbbb.Metadata.ComplexType.FindProperty("Name")!.SetMaxLength(100);
});
});
});
});
},
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");

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

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("json");
});

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

b.ToTable("EntityWithOneProperty", "DefaultSchema");
});
""", usingSystem: false, usingCollections: true),
o =>
{
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
var complexProperty1 = entityWithOneProperty.FindComplexProperty(nameof(EntityWithOneProperty.EntityWithTwoProperties));
var complexType1 = complexProperty1.ComplexType;
var entityWithStringKeyComplexProperty =
complexType1.FindComplexProperty(nameof(EntityWithTwoProperties.EntityWithStringKey));
var entityWithStringKeyComplexType = entityWithStringKeyComplexProperty.ComplexType;

var propertiesComplexCollection =
entityWithStringKeyComplexType.FindComplexProperty(nameof(EntityWithStringKey.Properties));
Assert.True(propertiesComplexCollection.IsCollection);

// MaxLength is NOT in the snapshot, so it won't be set on the model created from snapshot
// This verifies that the snapshot doesn't contain HasMaxLength which would cause a compile error
var nameProperty = propertiesComplexCollection.ComplexType.FindProperty("Name");
Assert.Null(nameProperty.GetMaxLength());
});

#endregion

#region HasKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9922,6 +9922,46 @@ public virtual void Noop_on_complex_properties()
target => { },
Assert.Empty);

[ConditionalFact]
public void Noop_on_complex_collection_property_annotations_not_in_snapshot()
=> Execute(
builder =>
{
builder.Entity(
"Entity", e =>
{
e.Property<int>("Id").ValueGeneratedOnAdd();
e.HasKey("Id");

e.ComplexCollection<List<MyJsonComplex>, MyJsonComplex>(
"ComplexCollection", cp =>
{
cp.ToJson();
cp.Property(x => x.Value);
cp.Property(x => x.Date);
});
});
},
source =>
{
// Simulate convention setting MaxLength on string property in complex collection
// This annotation is NOT emitted in the snapshot because ComplexCollectionTypePropertyBuilder
// doesn't support HasMaxLength
var entity = source.Model.FindEntityType("Entity");
var complexProperty = entity!.FindComplexProperty("ComplexCollection")!;
var valueProperty = complexProperty.ComplexType.FindProperty("Value")!;
((IMutableProperty)valueProperty).SetMaxLength(255);
},
target =>
{
// Target model also has MaxLength set (same as source)
var entity = target.Model.FindEntityType("Entity");
var complexProperty = entity!.FindComplexProperty("ComplexCollection")!;
var valueProperty = complexProperty.ComplexType.FindProperty("Value")!;
((IMutableProperty)valueProperty).SetMaxLength(255);
},
Assert.Empty);

protected class MyJsonComplex
{
public string Value { get; set; }
Expand Down
Loading