Skip to content

Commit

Permalink
Throw when an inherited property or navigation is ignored
Browse files Browse the repository at this point in the history
Fixes #5864
  • Loading branch information
AndriySvyryd committed Jul 31, 2016
1 parent a8c05dd commit d972f6d
Show file tree
Hide file tree
Showing 17 changed files with 612 additions and 226 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public virtual ConventionSet AddConventions(ConventionSet conventionSet)
ReplaceConvention(conventionSet.BaseEntityTypeSetConventions, inversePropertyAttributeConvention);
ReplaceConvention(conventionSet.BaseEntityTypeSetConventions, relationshipDiscoveryConvention);

ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, inversePropertyAttributeConvention);
ReplaceConvention(conventionSet.EntityTypeMemberIgnoredConventions, relationshipDiscoveryConvention);

ReplaceConvention(conventionSet.ForeignKeyAddedConventions, (ForeignKeyAttributeConvention)new RelationalForeignKeyAttributeConvention(_typeMapper));
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
Expand Down Expand Up @@ -130,6 +131,7 @@ public virtual InternalRelationshipBuilder OnForeignKeyAdded([NotNull] InternalR
{
break;
}
Debug.Assert(relationshipBuilder.Metadata.Builder == relationshipBuilder);
}

return relationshipBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public virtual ConventionSet CreateConventionSet()
conventionSet.BaseEntityTypeSetConventions.Add(relationshipDiscoveryConvention);

// An ambiguity might have been resolved
conventionSet.EntityTypeMemberIgnoredConventions.Add(inversePropertyAttributeConvention);
conventionSet.EntityTypeMemberIgnoredConventions.Add(relationshipDiscoveryConvention);

var keyAttributeConvention = new KeyAttributeConvention();
Expand Down Expand Up @@ -69,6 +70,7 @@ public virtual ConventionSet CreateConventionSet()
conventionSet.ModelBuiltConventions.Add(new ModelCleanupConvention());
conventionSet.ModelBuiltConventions.Add(keyAttributeConvention);
conventionSet.ModelBuiltConventions.Add(keyConvention);
conventionSet.ModelBuiltConventions.Add(new IgnoredMembersValidationConvention());
conventionSet.ModelBuiltConventions.Add(new PropertyMappingValidationConvention());
conventionSet.ModelBuiltConventions.Add(new RelationshipValidationConvention());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ public virtual InternalRelationshipBuilder Apply(InternalRelationshipBuilder rel
var invertedRelationshipBuilder = relationshipBuilder
.RelatedEntityTypes(foreignKey.DeclaringEntityType, foreignKey.PrincipalEntityType, ConfigurationSource.Convention);

return invertedRelationshipBuilder ?? relationshipBuilder;
if (invertedRelationshipBuilder != null)
{
return invertedRelationshipBuilder;
}

return foreignKey.Builder == null ? null : relationshipBuilder;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
{
/// <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 class IgnoredMembersValidationConvention : IModelConvention
{
/// <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 InternalModelBuilder Apply(InternalModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
{
foreach (var ignoredMember in entityType.GetIgnoredMembers())
{
var property = entityType.FindProperty(ignoredMember);
if (property != null)
{
if (property.DeclaringEntityType != entityType)
{
throw new InvalidOperationException(CoreStrings.InheritedPropertyCannotBeIgnored(
ignoredMember, entityType.DisplayName(), property.DeclaringEntityType.DisplayName()));
}
Debug.Assert(false);
}

var navigation = entityType.FindNavigation(ignoredMember);
if (navigation != null)
{
if (navigation.DeclaringEntityType != entityType)
{
throw new InvalidOperationException(CoreStrings.InheritedPropertyCannotBeIgnored(
ignoredMember, entityType.DisplayName(), navigation.DeclaringEntityType.DisplayName()));
}
Debug.Assert(false);
}
}
}

return modelBuilder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ public class InversePropertyAttributeConvention : NavigationAttributeEntityTypeC

// Check for InversePropertyAttribute on the inverseNavigation to verify that it matches.
var inverseAttribute = inverseNavigationPropertyInfo.GetCustomAttribute<InversePropertyAttribute>(true);
if ((inverseAttribute != null)
&& (inverseAttribute.Property != navigationPropertyInfo.Name))
if (inverseAttribute != null
&& inverseAttribute.Property != navigationPropertyInfo.Name)
{
throw new InvalidOperationException(
CoreStrings.InversePropertyMismatch(
Expand Down Expand Up @@ -120,7 +120,10 @@ public class InversePropertyAttributeConvention : NavigationAttributeEntityTypeC
inverseNavigationsList))
{
var fk = existingInverse.ForeignKey;
fk.DeclaringEntityType.Builder.RemoveForeignKey(fk, ConfigurationSource.DataAnnotation);
if (fk.GetConfigurationSource() == ConfigurationSource.DataAnnotation)
{
fk.DeclaringEntityType.Builder.RemoveForeignKey(fk, ConfigurationSource.DataAnnotation);
}
}

return null;
Expand Down Expand Up @@ -202,6 +205,67 @@ public class InversePropertyAttributeConvention : NavigationAttributeEntityTypeC
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 override bool ApplyIgnored(
InternalEntityTypeBuilder entityTypeBuilder,
PropertyInfo navigationPropertyInfo,
Type targetClrType,
InversePropertyAttribute attribute)
{
var entityType = entityTypeBuilder.Metadata;
var targetType = entityType.Model.FindEntityType(targetClrType);
var inverseNavigationPropertyInfo = targetClrType.GetRuntimeProperties()
.FirstOrDefault(p => string.Equals(p.Name, attribute.Property, StringComparison.OrdinalIgnoreCase));
if (targetType == null
|| inverseNavigationPropertyInfo == null)
{
return true;
}

List<Tuple<PropertyInfo, Type>> navigationTuples;
var inverseNavigations = GetInverseNavigations(targetType);
if (inverseNavigations == null
|| !inverseNavigations.TryGetValue(inverseNavigationPropertyInfo, out navigationTuples))
{
return true;
}

var inverseWasAmbiguous = false;
for (var index = 0; index < navigationTuples.Count; index++)
{
var inverseTuple = navigationTuples[index];
if (inverseTuple.Item1 == navigationPropertyInfo
&& inverseTuple.Item2 == entityType.ClrType)
{
navigationTuples.RemoveAt(index);
if (!navigationTuples.Any())
{
inverseNavigations.Remove(inverseNavigationPropertyInfo);
}
inverseWasAmbiguous = true;
break;
}
}

if (!inverseWasAmbiguous
|| navigationTuples.Count > 1)
{
return true;
}

var otherEntityTypeBuilder = entityTypeBuilder.ModelBuilder.Entity(navigationTuples[0].Item2, ConfigurationSource.DataAnnotation);
targetType.Builder.Relationship(
otherEntityTypeBuilder,
inverseNavigationPropertyInfo,
navigationTuples[0].Item1,
ConfigurationSource.DataAnnotation);

return true;
}

private static bool IsAmbiguousInverse(
PropertyInfo navigationPropertyInfo, EntityType entityType, List<Tuple<PropertyInfo, Type>> inverseNavigationsList)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
/// 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 abstract class NavigationAttributeEntityTypeConvention<TAttribute> : IEntityTypeConvention, IEntityTypeIgnoredConvention, INavigationConvention, IBaseTypeConvention
public abstract class NavigationAttributeEntityTypeConvention<TAttribute> : IEntityTypeConvention, IEntityTypeIgnoredConvention, INavigationConvention, IBaseTypeConvention, IEntityTypeMemberIgnoredConvention
where TAttribute : Attribute
{
/// <summary>
Expand Down Expand Up @@ -152,6 +152,39 @@ public virtual bool Apply(InternalEntityTypeBuilder entityTypeBuilder, EntityTyp
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 bool Apply(InternalEntityTypeBuilder entityTypeBuilder, string ignoredMemberName)
{
var navigationPropertyInfo =
entityTypeBuilder.Metadata.ClrType.GetRuntimeProperties().FirstOrDefault(p => p.Name == ignoredMemberName);
if (navigationPropertyInfo == null)
{
return true;
}

var targetClrType = FindCandidateNavigationPropertyType(navigationPropertyInfo);
if (targetClrType == null)
{
return true;
}

var attributes = navigationPropertyInfo.GetCustomAttributes<TAttribute>(true);
if (attributes != null)
{
foreach (var attribute in attributes)
{
if (!ApplyIgnored(entityTypeBuilder, navigationPropertyInfo, targetClrType, attribute))
{
return false;
}
}
}
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.
Expand Down Expand Up @@ -215,5 +248,18 @@ public virtual Type FindCandidateNavigationPropertyType([NotNull] PropertyInfo p
{
throw new NotImplementedException();
}

/// <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 bool ApplyIgnored(
[NotNull] InternalEntityTypeBuilder entityTypeBuilder,
[NotNull] PropertyInfo navigationPropertyInfo,
[NotNull] Type targetClrType,
[NotNull] TAttribute attribute)
{
throw new NotImplementedException();
}
}
}
28 changes: 25 additions & 3 deletions src/Microsoft.EntityFrameworkCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1656,7 +1656,8 @@ public virtual Func<ISnapshot> EmptyShadowValuesFactory
/// 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 Ignore([NotNull] string name, ConfigurationSource configurationSource = ConfigurationSource.Explicit)
public virtual void Ignore([NotNull] string name, ConfigurationSource configurationSource = ConfigurationSource.Explicit,
bool runConventions = true)
{
Check.NotNull(name, nameof(name));

Expand All @@ -1668,14 +1669,24 @@ public virtual void Ignore([NotNull] string name, ConfigurationSource configurat

_ignoredMembers[name] = configurationSource;

Model.ConventionDispatcher.OnEntityTypeMemberIgnored(Builder, name);
if (runConventions)
{
Model.ConventionDispatcher.OnEntityTypeMemberIgnored(Builder, name);
}
}

/// <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 IReadOnlyList<string> GetIgnoredMembers()
=> _ignoredMembers.Keys.ToList();

/// <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? FindIgnoredMemberConfigurationSource([NotNull] string name)
public virtual ConfigurationSource? FindDeclaredIgnoredMemberConfigurationSource([NotNull] string name)
{
Check.NotEmpty(name, nameof(name));

Expand All @@ -1688,6 +1699,17 @@ public virtual void Ignore([NotNull] string name, ConfigurationSource configurat
return null;
}

/// <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? FindIgnoredMemberConfigurationSource([NotNull] string name)
{
var ignoredSource = FindDeclaredIgnoredMemberConfigurationSource(name);

return BaseType == null ? ignoredSource : BaseType.FindIgnoredMemberConfigurationSource(name).Max(ignoredSource);
}

/// <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

0 comments on commit d972f6d

Please sign in to comment.