Skip to content

Commit

Permalink
Don't discover FK properties for an identifying FK created by convention
Browse files Browse the repository at this point in the history
Silently ignore inherited properties in TPT in SqlServerIndexConvention

Fixes #23092
  • Loading branch information
AndriySvyryd committed Oct 28, 2020
1 parent 33570d0 commit 0e4e261
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
Expand Down Expand Up @@ -154,7 +155,7 @@ private IConventionIndexBuilder SetIndexFilter(IConventionIndexBuilder indexBuil
var index = indexBuilder.Metadata;
if (index.IsUnique
&& index.IsClustered() != true
&& index.Properties.Any(property => property.IsColumnNullable()))
&& GetNullableColumns(index)?.Any() == true)
{
if (columnNameChanged
|| index.GetFilter() == null)
Expand All @@ -175,18 +176,7 @@ private IConventionIndexBuilder SetIndexFilter(IConventionIndexBuilder indexBuil

private string CreateIndexFilter(IIndex index)
{
var tableName = index.DeclaringEntityType.GetTableName();
if (tableName == null)
{
return null;
}

var table = StoreObjectIdentifier.Table(tableName, index.DeclaringEntityType.GetSchema());
var nullableColumns = index.Properties
.Where(property => property.IsColumnNullable(table))
.Select(property => property.GetColumnName(table))
.ToList();

var nullableColumns = GetNullableColumns(index);
var builder = new StringBuilder();
for (var i = 0; i < nullableColumns.Count; i++)
{
Expand All @@ -202,5 +192,34 @@ private string CreateIndexFilter(IIndex index)

return builder.ToString();
}

private List<string> GetNullableColumns(IIndex index)
{
var tableName = index.DeclaringEntityType.GetTableName();
if (tableName == null)
{
return null;
}

var nullableColumns = new List<string>();
var table = StoreObjectIdentifier.Table(tableName, index.DeclaringEntityType.GetSchema());
foreach (var property in index.Properties)
{
var columnName = property.GetColumnName(table);
if (columnName == null)
{
return null;
}

if (!property.IsColumnNullable(table))
{
continue;
}

nullableColumns.Add(columnName);
}

return nullableColumns;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,13 @@ private IConventionForeignKeyBuilder DiscoverProperties(
IConventionContext context)
{
var foreignKey = relationshipBuilder.Metadata;
if (!ConfigurationSource.Convention.Overrides(foreignKey.GetPropertiesConfigurationSource()))
var foreignKeyProperties = FindCandidateForeignKeyProperties(relationshipBuilder.Metadata, onDependent: true);
var propertiesConfigurationSource = foreignKey.GetPropertiesConfigurationSource();
if (!ConfigurationSource.Convention.OverridesStrictly(propertiesConfigurationSource)
&& (propertiesConfigurationSource != ConfigurationSource.Convention
|| (foreignKey.Properties.All(p => !p.IsImplicitlyCreated())
&& (foreignKeyProperties == null
|| !foreignKey.Properties.SequenceEqual(foreignKeyProperties)))))
{
var batch = context.DelayConventions();
using var foreignKeyReference = batch.Track(foreignKey);
Expand Down Expand Up @@ -184,7 +190,6 @@ private IConventionForeignKeyBuilder DiscoverProperties(
}
}

var foreignKeyProperties = FindCandidateForeignKeyProperties(relationshipBuilder.Metadata, onDependent: true);
if (foreignKeyProperties == null)
{
if (invertible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,33 @@ public virtual void TPT_identifying_FK_are_created_only_on_declaring_type()
Assert.Single(sesameBunFk.GetMappedConstraints());
}

[ConditionalFact]
public virtual void TPT_index_can_use_inherited_properties()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity<BigMak>()
.Ignore(b => b.Bun)
.Ignore(b => b.Pickles);
modelBuilder.Entity<Ingredient>(b =>
{
b.ToTable("Ingredients");
b.Property<int?>("NullableProp");
b.Ignore(i => i.BigMak);
});
modelBuilder.Entity<Bun>(b =>
{
b.ToTable("Buns");
b.HasIndex(bun => bun.BurgerId);
b.HasIndex("NullableProp");
b.HasOne(i => i.BigMak).WithOne().HasForeignKey<Bun>(i => i.Id);
});

var model = modelBuilder.FinalizeModel();

var bunType = model.FindEntityType(typeof(Bun));
Assert.All(bunType.GetIndexes(), i => Assert.Null(i.GetFilter()));
}

public class Parent
{
public int Id { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,29 @@ public void Does_not_match_dependent_PK_for_self_ref()
ValidateModel();
}

[ConditionalFact]
public void Does_not_match_for_convention_identifying_FK()
{
var derivedType = PrincipalType.Builder.ModelBuilder.Entity(typeof(DerivedPrincipalEntity), ConfigurationSource.Convention);
derivedType.HasBaseType(PrincipalType, ConfigurationSource.Convention);

PrincipalType.Builder.Property(typeof(int), nameof(PrincipalEntity.PrincipalEntityId), ConfigurationSource.Convention);
var relationshipBuilder = derivedType.HasRelationship(
PrincipalType, PrincipalType.FindPrimaryKey().Properties, ConfigurationSource.Convention)
.IsUnique(true, ConfigurationSource.DataAnnotation);

var newRelationshipBuilder = RunConvention(relationshipBuilder);
Assert.Same(relationshipBuilder, newRelationshipBuilder);

var fk = (IForeignKey)relationshipBuilder.Metadata;
Assert.Equal(nameof(PrincipalEntity.PeeKay), fk.Properties.Single().Name);
Assert.Same(fk, derivedType.Metadata.GetForeignKeys().Single());
Assert.True(fk.IsUnique);
Assert.True(fk.IsRequired);

ValidateModel();
}

[ConditionalFact]
public void Matches_composite_dependent_PK_for_unique_FK()
{
Expand Down Expand Up @@ -1192,13 +1215,18 @@ private class PrincipalEntity
public static readonly PropertyInfo DependentEntityKayPeeProperty =
typeof(PrincipalEntity).GetProperty("DependentEntityKayPee");

public int PrincipalEntityId { get; set; }
public int PeeKay { get; set; }
public int? DependentEntityKayPee { get; set; }
public IEnumerable<DependentEntity> InverseNav { get; set; }
public DependentEntity InverseReferenceNav { get; set; }
public PrincipalEntity SelfRef { get; set; }
}

private class DerivedPrincipalEntity : PrincipalEntity
{
}

private class DependentEntity
{
public static readonly PropertyInfo SomeNavIDProperty = typeof(DependentEntity).GetProperty("SomeNavID");
Expand Down

0 comments on commit 0e4e261

Please sign in to comment.