Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't throw on referenced shadow primary key. #7553

Merged
merged 1 commit into from
Feb 6, 2017
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
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ protected virtual void EnsureNoShadowKeys([NotNull] IModel model)
{
var referencingFk = key.GetReferencingForeignKeys().FirstOrDefault();
var conventionalKey = key as Key;
if (referencingFk != null
if (!key.IsPrimaryKey()
&& referencingFk != null
&& conventionalKey != null
&& ConfigurationSource.Convention.Overrides(conventionalKey.GetConfigurationSource()))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public virtual ConventionSet CreateConventionSet()

conventionSet.KeyRemovedConventions.Add(foreignKeyPropertyDiscoveryConvention);
conventionSet.KeyRemovedConventions.Add(foreignKeyIndexConvention);
conventionSet.KeyRemovedConventions.Add(keyDiscoveryConvention);

var cascadeDeleteConvention = new CascadeDeleteConvention();
conventionSet.ForeignKeyAddedConventions.Add(new ForeignKeyAttributeConvention());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
Expand All @@ -16,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class KeyDiscoveryConvention
: IEntityTypeConvention, IPropertyConvention, IBaseTypeConvention, IPropertyFieldChangedConvention
: IEntityTypeConvention, IPropertyConvention, IKeyRemovedConvention, IBaseTypeConvention, IPropertyFieldChangedConvention
{
private const string KeySuffix = "Id";

Expand All @@ -30,15 +31,21 @@ public virtual InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityT
var entityType = entityTypeBuilder.Metadata;

if (entityType.BaseType == null
&& entityType.FindPrimaryKey() == null)
&& ConfigurationSource.Convention.Overrides(entityType.GetPrimaryKeyConfigurationSource()))
{
var candidateProperties = entityType.GetProperties().Where(p =>
!p.IsShadowProperty
|| !ConfigurationSource.Convention.Overrides(p.GetConfigurationSource())).ToList();
var keyProperties = DiscoverKeyProperties(entityType, candidateProperties);
if (keyProperties.Count != 0)
var keyProperties = DiscoverKeyProperties(entityType, candidateProperties).Select(p => p.Name).ToList();
if (keyProperties.Count > 1)
{
entityTypeBuilder.PrimaryKey(keyProperties.Select(p => p.Name).ToList(), ConfigurationSource.Convention);
//TODO - log using Strings.MultiplePropertiesMatchedAsKeys()
return entityTypeBuilder;
}

if (keyProperties.Count > 0)
{
entityTypeBuilder.PrimaryKey(keyProperties, ConfigurationSource.Convention);
}
}

Expand All @@ -49,24 +56,18 @@ public virtual InternalEntityTypeBuilder Apply(InternalEntityTypeBuilder entityT
/// 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 IReadOnlyList<Property> DiscoverKeyProperties([NotNull] EntityType entityType, [NotNull] IReadOnlyList<Property> candidateProperties)
public virtual IEnumerable<Property> DiscoverKeyProperties([NotNull] EntityType entityType, [NotNull] IReadOnlyList<Property> candidateProperties)
{
Check.NotNull(entityType, nameof(entityType));

var keyProperties = candidateProperties.Where(p => string.Equals(p.Name, KeySuffix, StringComparison.OrdinalIgnoreCase))
.ToList();

if (keyProperties.Count == 0)
var keyProperties = candidateProperties.Where(p => string.Equals(p.Name, KeySuffix, StringComparison.OrdinalIgnoreCase));
if (!keyProperties.Any())
{
var entityTypeName = entityType.DisplayName();
keyProperties = candidateProperties.Where(
p => string.Equals(p.Name, entityType.DisplayName() + KeySuffix, StringComparison.OrdinalIgnoreCase))
.ToList();
}

if (keyProperties.Count > 1)
{
//TODO - add in logging using resource Strings.MultiplePropertiesMatchedAsKeys()
return new Property[0];
p => p.Name.Length == entityTypeName.Length + KeySuffix.Length
&& p.Name.StartsWith(entityTypeName, StringComparison.OrdinalIgnoreCase)
&& p.Name.EndsWith(KeySuffix, StringComparison.OrdinalIgnoreCase));
}

return keyProperties;
Expand Down Expand Up @@ -101,5 +102,17 @@ public virtual bool Apply(InternalPropertyBuilder propertyBuilder, FieldInfo old
Apply(propertyBuilder);
return true;
}

/// <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 Apply(InternalEntityTypeBuilder entityTypeBuilder, Key key)
{
if (entityTypeBuilder.Metadata.FindPrimaryKey() == null)
{
Apply(entityTypeBuilder);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,6 @@ public void Primary_key_is_not_set_when_zero_key_properties()
Assert.Null(key);
}

[Fact]
public void Composite_primary_key_is_set_when_multiple_key_properties()
{
var entityBuilder = CreateInternalEntityBuilder<EntityWithNoId>();
var convention = new Mock<KeyDiscoveryConvention> { CallBase = true };
convention.Setup(c => c.DiscoverKeyProperties(It.IsAny<EntityType>(), It.IsAny<IReadOnlyList<Property>>()))
.Returns<EntityType, IReadOnlyList<Property>>((t, properties) => t.GetProperties().ToList());

Assert.Same(entityBuilder, convention.Object.Apply(entityBuilder));

var key = entityBuilder.Metadata.FindPrimaryKey();
Assert.NotNull(key);
Assert.Equal(new[] { "ModifiedDate", "Name" }, key.Properties.Select(p => p.Name));
}

[Fact]
public void Primary_key_is_set_when_shadow_property_not_defined_by_convention_matches()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,33 @@ public virtual void Can_set_entity_key_from_clr_property()
Assert.Equal(Customer.IdProperty.Name, entity.FindPrimaryKey().Properties.First().Name);
}

[Fact]
public virtual void Entity_key_on_shadow_property_is_discovered_by_convention()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity<Order>().Property<int>("Id");

var entity = modelBuilder.Model.FindEntityType(typeof(Order));

modelBuilder.Validate();
Assert.Equal("Id", entity.FindPrimaryKey().Properties.Single().Name);
}

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

modelBuilder.Entity<SelfRef>()
.Ignore(s => s.SelfRef1)
.Ignore(s => s.SelfRef2)
.Ignore(s => s.Id);

modelBuilder.Validate();
var entity = modelBuilder.Model.FindEntityType(typeof(SelfRef));
Assert.Equal(nameof(SelfRef.SelfRefId), entity.FindPrimaryKey().Properties.Single().Name);
}

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