Skip to content
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
5 changes: 4 additions & 1 deletion src/EFCore.SqlServer/EFCore.SqlServer.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -1461,11 +1461,14 @@
]
},
{
"Type": "class Microsoft.EntityFrameworkCore.Metadata.Conventions.SqlServerIndexConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.IEntityTypeBaseTypeChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IIndexAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IIndexUniquenessChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IIndexAnnotationChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyNullabilityChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyAnnotationChangedConvention",
"Type": "class Microsoft.EntityFrameworkCore.Metadata.Conventions.SqlServerIndexConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.IEntityTypeBaseTypeChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IDiscriminatorPropertySetConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IIndexAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IIndexUniquenessChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IIndexAnnotationChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyNullabilityChangedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IPropertyAnnotationChangedConvention",
"Methods": [
{
"Member": "SqlServerIndexConvention(Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure.ProviderConventionSetBuilderDependencies dependencies, Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure.RelationalConventionSetBuilderDependencies relationalDependencies, Microsoft.EntityFrameworkCore.Storage.ISqlGenerationHelper sqlGenerationHelper);"
},
{
"Member": "virtual void ProcessDiscriminatorPropertySet(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionTypeBaseBuilder structuralTypeBuilder, string? name, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext<string?> context);"
},
{
"Member": "virtual void ProcessEntityTypeBaseTypeChanged(Microsoft.EntityFrameworkCore.Metadata.Builders.IConventionEntityTypeBuilder entityTypeBuilder, Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType? newBaseType, Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType? oldBaseType, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConventionContext<Microsoft.EntityFrameworkCore.Metadata.IConventionEntityType> context);"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
/// </remarks>
public class SqlServerIndexConvention :
IEntityTypeBaseTypeChangedConvention,
IDiscriminatorPropertySetConvention,
IIndexAddedConvention,
IIndexUniquenessChangedConvention,
IIndexAnnotationChangedConvention,
Expand Down Expand Up @@ -76,6 +77,34 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
}
}

/// <summary>
/// Called after a discriminator property is set.
/// </summary>
/// <param name="structuralTypeBuilder">The builder for the type.</param>
/// <param name="name">The name of the discriminator property.</param>
/// <param name="context">Additional information associated with convention execution.</param>
public virtual void ProcessDiscriminatorPropertySet(
IConventionTypeBaseBuilder structuralTypeBuilder,
string? name,
IConventionContext<string?> context)
{
if (structuralTypeBuilder is not IConventionEntityTypeBuilder entityTypeBuilder)
{
return;
}

// Whether the columns of derived types are nullable depends on the mapping strategy, which is only known
// for sure once the discriminator is removed for TPT and TPC hierarchies. Regenerate existing filters too, since
// the set of nullable columns may have shrunk; a filter configured explicitly is still not overridden.
foreach (var entityType in entityTypeBuilder.Metadata.GetDerivedTypesInclusive())
{
foreach (var index in entityType.GetDeclaredIndexes())
{
SetIndexFilter(index.Builder, regenerate: true);
}
}
}

/// <summary>
/// Called after an index is added to the entity type.
/// </summary>
Expand Down Expand Up @@ -151,19 +180,19 @@ public virtual void ProcessPropertyAnnotationChanged(
{
foreach (var index in propertyBuilder.Metadata.GetContainingIndexes())
{
SetIndexFilter(index.Builder, columnNameChanged: true);
SetIndexFilter(index.Builder, regenerate: true);
}
}
}

private void SetIndexFilter(IConventionIndexBuilder indexBuilder, bool columnNameChanged = false)
private void SetIndexFilter(IConventionIndexBuilder indexBuilder, bool regenerate = false)
{
var index = indexBuilder.Metadata;
if (index.IsUnique
&& index.IsClustered() != true
&& GetNullableColumns(index) is { Count: > 0 } nullableColumns)
{
if (columnNameChanged
if (regenerate
|| index.GetFilter() == null)
{
indexBuilder.HasFilter(CreateIndexFilter(nullableColumns));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,77 @@ public void Index_convention_sets_filter_for_unique_index_when_base_type_changed
Assert.Null(index.GetFilter());
}

[Fact] // #32689
public void Index_convention_does_not_set_filter_for_unique_index_on_required_property_in_TPT()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Ignore<Customer>();
modelBuilder.Entity<DetailsBase>().ToTable("DetailsBase");
modelBuilder.Entity<CustomerDetails>(b =>
{
b.ToTable("CustomerDetails");
b.HasIndex(e => e.CustomerId).IsUnique();
});

var model = modelBuilder.FinalizeModel();

var index = model.FindEntityType(typeof(CustomerDetails))!.GetDeclaredIndexes().Single();
Assert.Null(index.GetFilter());
}

[Fact] // #32689
public void Index_convention_does_not_set_filter_for_unique_index_on_required_property_in_TPC()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Ignore<Customer>();
modelBuilder.Entity<CustomerDetails>()
.HasIndex(e => e.CustomerId)
.IsUnique();
modelBuilder.Entity<DetailsBase>().UseTpcMappingStrategy();

var model = modelBuilder.FinalizeModel();

var index = model.FindEntityType(typeof(CustomerDetails))!.GetDeclaredIndexes().Single();
Assert.Null(index.GetFilter());
}

[Fact] // #32689
public void Index_convention_keeps_only_nullable_columns_in_filter_for_composite_unique_index_in_TPT()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Ignore<Customer>();
modelBuilder.Entity<DetailsBase>().ToTable("DetailsBase");
modelBuilder.Entity<CustomerDetails>(b =>
{
b.ToTable("CustomerDetails");
b.Property<string?>("Note");
b.HasIndex(nameof(CustomerDetails.CustomerId), "Note").IsUnique();
});

var model = modelBuilder.FinalizeModel();

var index = model.FindEntityType(typeof(CustomerDetails))!.GetDeclaredIndexes().Single();
Assert.Equal("[Note] IS NOT NULL", index.GetFilter());
}

[Fact] // #32689
public void Index_convention_keeps_only_nullable_columns_in_filter_for_composite_unique_index_in_TPC()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Ignore<Customer>();
modelBuilder.Entity<CustomerDetails>(b =>
{
b.Property<string?>("Note");
b.HasIndex(nameof(CustomerDetails.CustomerId), "Note").IsUnique();
});
modelBuilder.Entity<DetailsBase>().UseTpcMappingStrategy();

var model = modelBuilder.FinalizeModel();

var index = model.FindEntityType(typeof(CustomerDetails))!.GetDeclaredIndexes().Single();
Assert.Equal("[Note] IS NOT NULL", index.GetFilter());
}

[Fact]
public virtual void Can_override_TPC_with_TPH()
{
Expand Down
Loading