Skip to content

Commit

Permalink
Allow to specify the shadow FK before configuring the primary key on …
Browse files Browse the repository at this point in the history
…the principal type.

Fixes #6823
  • Loading branch information
AndriySvyryd committed Nov 18, 2016
1 parent 92921dc commit 1caba2c
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 77 deletions.
14 changes: 11 additions & 3 deletions src/Microsoft.EntityFrameworkCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1357,13 +1357,20 @@ public virtual Property AddProperty(
[NotNull] string name,
[CanBeNull] Type propertyType = null,
ConfigurationSource configurationSource = ConfigurationSource.Explicit,
ConfigurationSource? typeConfigurationSource = ConfigurationSource.Explicit,
bool runConventions = true)
{
Check.NotNull(name, nameof(name));

ValidateCanAddProperty(name);

return AddProperty(name, propertyType, ClrType?.GetMembersInHierarchy(name).FirstOrDefault(), configurationSource, runConventions);
return AddProperty(
name,
propertyType,
ClrType?.GetMembersInHierarchy(name).FirstOrDefault(),
configurationSource,
typeConfigurationSource,
runConventions);
}

/// <summary>
Expand Down Expand Up @@ -1391,7 +1398,7 @@ public virtual Property AddProperty(
memberInfo.Name, this.DisplayName(), memberInfo.DeclaringType?.ShortDisplayName()));
}

return AddProperty(memberInfo.Name, memberInfo.GetMemberType(), memberInfo, configurationSource, runConventions);
return AddProperty(memberInfo.Name, memberInfo.GetMemberType(), memberInfo, configurationSource, configurationSource, runConventions);
}

private void ValidateCanAddProperty(string name)
Expand All @@ -1416,6 +1423,7 @@ private Property AddProperty(
Type propertyType,
MemberInfo memberInfo,
ConfigurationSource configurationSource,
ConfigurationSource? typeConfigurationSource,
bool runConventions)
{
Check.NotNull(name, nameof(name));
Expand All @@ -1442,7 +1450,7 @@ private Property AddProperty(
}
}

var property = new Property(name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, configurationSource);
var property = new Property(name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, configurationSource, typeConfigurationSource);

_properties.Add(property.Name, property);

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -289,17 +289,24 @@ public virtual InternalPropertyBuilder Attach(
{
var newProperty = entityTypeBuilder.Metadata.FindProperty(Metadata.Name);
InternalPropertyBuilder newPropertyBuilder;
var typeConfigurationSource = Metadata.GetTypeConfigurationSource();
if (newProperty != null
&& (newProperty.GetConfigurationSource().Overrides(configurationSource)
|| newProperty.GetTypeConfigurationSource().Overrides(typeConfigurationSource)
|| (Metadata.ClrType == newProperty.ClrType
&& Metadata.PropertyInfo?.Name == newProperty.PropertyInfo?.Name)))
{
newPropertyBuilder = newProperty.Builder;
newProperty.UpdateConfigurationSource(configurationSource);
if (typeConfigurationSource.HasValue)
{
newProperty.UpdateTypeConfigurationSource(typeConfigurationSource.Value);
}
}
else
{
newPropertyBuilder = Metadata.PropertyInfo == null
? entityTypeBuilder.Property(Metadata.Name, Metadata.ClrType, configurationSource)
? entityTypeBuilder.Property(Metadata.Name, Metadata.ClrType, configurationSource, Metadata.GetTypeConfigurationSource())
: entityTypeBuilder.Property(Metadata.PropertyInfo, configurationSource);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1266,7 +1266,7 @@ public virtual InternalRelationshipBuilder HasForeignKey(
public virtual InternalRelationshipBuilder HasForeignKey(
[NotNull] IReadOnlyList<string> propertyNames, [NotNull] EntityType dependentEntityType, ConfigurationSource configurationSource)
=> HasForeignKey(
dependentEntityType.Builder.GetOrCreateProperties(propertyNames, configurationSource, Metadata.PrincipalKey.Properties),
dependentEntityType.Builder.GetOrCreateProperties(propertyNames, configurationSource, Metadata.PrincipalKey.Properties, useDefaultType: true),
dependentEntityType,
configurationSource,
runConventions: true);
Expand Down Expand Up @@ -1815,6 +1815,7 @@ private InternalRelationshipBuilder ReplaceForeignKey(
if (dependentProperties != null
&& dependentProperties.Any())
{
dependentProperties = dependentEntityTypeBuilder.GetActualProperties(dependentProperties, configurationSource);
var foreignKeyPropertiesConfigurationSource = configurationSource;
if (PropertyListComparer.Instance.Equals(Metadata.Properties, dependentProperties)
&& !oldRelationshipInverted)
Expand All @@ -1835,8 +1836,9 @@ private InternalRelationshipBuilder ReplaceForeignKey(
if (principalProperties != null
&& principalProperties.Any())
{
principalProperties = principalEntityTypeBuilder.GetActualProperties(principalProperties, configurationSource);
var principalKeyConfigurationSource = configurationSource;
if (PropertyListComparer.Instance.Equals(Metadata.PrincipalKey.Properties, principalProperties)
if (PropertyListComparer.Instance.Equals(principalProperties, newRelationshipBuilder.Metadata.PrincipalKey.Properties)
&& !oldRelationshipInverted)
{
principalKeyConfigurationSource = principalKeyConfigurationSource.Max(Metadata.GetPrincipalKeyConfigurationSource());
Expand Down Expand Up @@ -1983,8 +1985,10 @@ private InternalRelationshipBuilder ReplaceForeignKey(
ModelBuilder.Metadata.ConventionDispatcher.OnForeignKeyRemoved(removedForeignKey.Item1, removedForeignKey.Item2);
}

dependentProperties = dependentProperties != null && dependentProperties.Any() ? dependentProperties : null;
principalProperties = principalProperties != null && principalProperties.Any() ? principalProperties : null;
dependentProperties = dependentProperties != null && dependentProperties.Any()
? newRelationshipBuilder.Metadata.Properties : null;
principalProperties = principalProperties != null && principalProperties.Any()
? newRelationshipBuilder.Metadata.PrincipalKey.Properties : null;
if (newRelationshipBuilder.Metadata.Builder == null)
{
newRelationshipBuilder = FindCurrentRelationshipBuilder(
Expand Down Expand Up @@ -2738,7 +2742,7 @@ public virtual InternalRelationshipBuilder Attach(ConfigurationSource configurat
IReadOnlyList<Property> dependentProperties = null;
if (Metadata.GetForeignKeyPropertiesConfigurationSource()?.Overrides(configurationSource) == true)
{
dependentProperties = Metadata.DeclaringEntityType.Builder.GetActualProperties(Metadata.Properties, null)
dependentProperties = Metadata.DeclaringEntityType.Builder.GetActualProperties(Metadata.Properties, configurationSource: null)
?? new List<Property>();
}

Expand Down
18 changes: 17 additions & 1 deletion src/Microsoft.EntityFrameworkCore/Metadata/Internal/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class Property : PropertyBase, IMutableProperty
private int _flags;

private ConfigurationSource _configurationSource;
private ConfigurationSource? _typeConfigurationSource;
private ConfigurationSource? _isReadOnlyAfterSaveConfigurationSource;
private ConfigurationSource? _isReadOnlyBeforeSaveConfigurationSource;
private ConfigurationSource? _isNullableConfigurationSource;
Expand All @@ -42,7 +43,8 @@ public Property(
[CanBeNull] PropertyInfo propertyInfo,
[CanBeNull] FieldInfo fieldInfo,
[NotNull] EntityType declaringEntityType,
ConfigurationSource configurationSource)
ConfigurationSource configurationSource,
ConfigurationSource? typeConfigurationSource)
: base(name, propertyInfo, fieldInfo)
{
Check.NotNull(clrType, nameof(clrType));
Expand All @@ -51,6 +53,7 @@ public Property(
DeclaringEntityType = declaringEntityType;
ClrType = clrType;
_configurationSource = configurationSource;
_typeConfigurationSource = typeConfigurationSource;

Builder = new InternalPropertyBuilder(this, declaringEntityType.Model.Builder);
}
Expand Down Expand Up @@ -107,6 +110,19 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS
public virtual void SetConfigurationSource(ConfigurationSource configurationSource)
=> _configurationSource = configurationSource;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual ConfigurationSource? GetTypeConfigurationSource() => _typeConfigurationSource;

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public virtual void UpdateTypeConfigurationSource(ConfigurationSource configurationSource)
=> _typeConfigurationSource = _typeConfigurationSource.Max(configurationSource);

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Internal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,19 +157,20 @@ public void ForeignKey_creates_shadow_properties_if_corresponding_principal_key_
}

[Fact]
public void ForeignKey_does_not_create_shadow_properties_if_principal_type_does_not_have_primary_key()
public void ForeignKey_creates_shadow_properties_if_principal_type_does_not_have_primary_key()
{
var modelBuilder = CreateModelBuilder();
var customerEntityBuilder = modelBuilder.Entity(typeof(Customer), ConfigurationSource.Explicit);
var orderEntityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit);

Assert.Equal(
CoreStrings.NoPropertyType("ShadowCustomerId", nameof(Order)),
Assert.Throws<InvalidOperationException>(
() => orderEntityBuilder.HasForeignKey(
customerEntityBuilder.Metadata.Name,
new[] { "ShadowCustomerId" },
ConfigurationSource.Convention)).Message);
var relationshipBuilder = orderEntityBuilder.HasForeignKey(customerEntityBuilder.Metadata.Name, new[] { "ShadowCustomerId" }, ConfigurationSource.Convention);

var shadowProperty = orderEntityBuilder.Metadata.FindProperty("ShadowCustomerId");
Assert.True(shadowProperty.IsShadowProperty);
Assert.Equal(shadowProperty, relationshipBuilder.Metadata.Properties.First());

Assert.Null(customerEntityBuilder.Metadata.FindPrimaryKey());
Assert.Equal(1, relationshipBuilder.Metadata.PrincipalKey.Properties.Count);
}

[Fact]
Expand Down Expand Up @@ -1198,11 +1199,13 @@ public void Changing_primary_key_removes_previously_referenced_key_of_lower_or_e
var modelBuilder = CreateModelBuilder();
var dependentEntityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit);
var principalEntityBuilder = modelBuilder.Entity(typeof(Customer), ConfigurationSource.Explicit);
var keyBuilder = principalEntityBuilder.HasKey(new[] { Customer.IdProperty, Customer.UniqueProperty }, ConfigurationSource.Convention);
dependentEntityBuilder.Relationship(principalEntityBuilder, ConfigurationSource.DataAnnotation)
.HasPrincipalKey(keyBuilder.Metadata.Properties, ConfigurationSource.DataAnnotation);
.HasForeignKey(new[] { Order.CustomerIdProperty, Order.CustomerUniqueProperty }, ConfigurationSource.DataAnnotation);

keyBuilder = principalEntityBuilder.PrimaryKey(new[] { Customer.IdProperty }, ConfigurationSource.DataAnnotation);
Assert.Null(principalEntityBuilder.Metadata.FindPrimaryKey());
Assert.NotEqual(nameof(Customer.Id), dependentEntityBuilder.Metadata.GetForeignKeys().Single().PrincipalKey.Properties.First().Name);

var keyBuilder = principalEntityBuilder.PrimaryKey(new[] { Customer.IdProperty, Customer.UniqueProperty }, ConfigurationSource.Convention);

Assert.Same(keyBuilder.Metadata, dependentEntityBuilder.Metadata.GetForeignKeys().Single().PrincipalKey);
Assert.Same(keyBuilder.Metadata, principalEntityBuilder.Metadata.GetKeys().Single());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,17 +228,25 @@ public void ForeignKey_creates_shadow_properties_if_corresponding_principal_key_
}

[Fact]
public void ForeignKey_does_not_create_shadow_properties_if_corresponding_principal_key_properties_count_mismatch()
public void ForeignKey_creates_shadow_properties_if_corresponding_principal_key_properties_count_mismatch()
{
var modelBuilder = CreateInternalModelBuilder();
var customerEntityBuilder = modelBuilder.Entity(typeof(Customer), ConfigurationSource.Explicit);
var orderEntityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit);

var relationshipBuilder = orderEntityBuilder.Relationship(customerEntityBuilder, ConfigurationSource.Convention);
var relationshipBuilder = orderEntityBuilder.Relationship(customerEntityBuilder, ConfigurationSource.Convention)
.HasForeignKey(new[] { "ShadowCustomerId", "ShadowCustomerUnique" }, ConfigurationSource.Convention);

Assert.Equal(
CoreStrings.NoPropertyType("ShadowCustomerId", nameof(Order)),
Assert.Throws<InvalidOperationException>(() => relationshipBuilder.HasForeignKey(new[] { "ShadowCustomerId", "ShadowCustomerUnique" }, ConfigurationSource.Convention)).Message);
var shadowProperty1 = relationshipBuilder.Metadata.Properties.First();
Assert.True(shadowProperty1.IsShadowProperty);
Assert.Equal("ShadowCustomerId", shadowProperty1.Name);

var shadowProperty2 = relationshipBuilder.Metadata.Properties.Last();
Assert.True(shadowProperty2.IsShadowProperty);
Assert.Equal("ShadowCustomerUnique", shadowProperty2.Name);

Assert.Null(customerEntityBuilder.Metadata.FindPrimaryKey());
Assert.Equal(2, relationshipBuilder.Metadata.PrincipalKey.Properties.Count);
}

[Fact]
Expand All @@ -252,8 +260,7 @@ public void ForeignKey_creates_shadow_properties_if_corresponding_principal_key_
.HasForeignKey(new[] { "ShadowCustomerId" }, ConfigurationSource.Convention);

var shadowProperty = orderEntityBuilder.Metadata.FindProperty("ShadowCustomerId");
Assert.NotNull(shadowProperty);
Assert.True(((IProperty)shadowProperty).IsShadowProperty);
Assert.True(shadowProperty.IsShadowProperty);
Assert.Equal(shadowProperty, relationshipBuilder.Metadata.Properties.First());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public virtual void Finds_existing_navigation_to_dependent_and_uses_associated_F
modelBuilder.Entity<Customer>().HasOne(e => e.Details).WithOne(e => e.Customer);

var fk = dependentType.GetForeignKeys().Single();
Assert.Equal("Customer", dependentType.GetNavigations().Single().Name);
Assert.Same(fk.DependentToPrincipal, dependentType.GetNavigations().Single());
Assert.Same(principalKey, principalType.GetKeys().Single());
Assert.Same(dependentKey, dependentType.GetKeys().Single());
Assert.Same(principalKey, principalType.FindPrimaryKey());
Expand Down Expand Up @@ -2517,6 +2517,52 @@ public virtual void Throws_on_duplicate_navigation_when_self_referencing()
modelBuilder.Entity<SelfRef>().HasOne(e => e.SelfRef1).WithOne(e => e.SelfRef1)).Message);
}

[Fact]
public virtual void Can_specify_shadow_fk_before_configuring_principal_PK()
{
var modelBuilder = CreateModelBuilder();

modelBuilder.Entity<Hob>()
.HasMany(c => c.Nobs)
.WithOne(d => d.Hob)
.HasForeignKey("Fk1");

modelBuilder.Entity<Hob>()
.HasKey(e => new { e.NobId1 });
modelBuilder.Entity<Nob>()
.HasKey(e => new { e.HobId1 });

modelBuilder.Validate();

Assert.Equal(typeof(int?), modelBuilder.Model.FindEntityType(typeof(Nob)).GetForeignKeys().Single().Properties.Single().ClrType);
Assert.Equal(typeof(string), modelBuilder.Model.FindEntityType(typeof(Hob)).GetForeignKeys().Single().Properties.Single().ClrType);
}

[Fact]
public virtual void Can_specify_shadow_fk_before_reconfiguring_principal_PK()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity<Hob>()
.HasKey(e => new { e.Id1 });
modelBuilder.Entity<Nob>()
.HasKey(e => new { e.Id1 });

modelBuilder.Entity<Hob>()
.HasMany(c => c.Nobs)
.WithOne(d => d.Hob)
.HasForeignKey("Fk1");

modelBuilder.Entity<Hob>()
.HasKey(e => new { e.NobId1 });
modelBuilder.Entity<Nob>()
.HasKey(e => new { e.HobId1 });

modelBuilder.Validate();

Assert.Equal(typeof(int?), modelBuilder.Model.FindEntityType(typeof(Nob)).GetForeignKeys().Single().Properties.Single().ClrType);
Assert.Equal(typeof(string), modelBuilder.Model.FindEntityType(typeof(Hob)).GetForeignKeys().Single().Properties.Single().ClrType);
}

[Fact]
public virtual void Throws_if_specified_FK_types_do_not_match()
{
Expand Down

0 comments on commit 1caba2c

Please sign in to comment.