Skip to content

Commit

Permalink
Make skip navigation foreign key nullable and mutable
Browse files Browse the repository at this point in the history
Part of #19003
  • Loading branch information
AndriySvyryd committed Jan 4, 2020
1 parent 6594428 commit 20e2e1c
Show file tree
Hide file tree
Showing 21 changed files with 624 additions and 80 deletions.
10 changes: 10 additions & 0 deletions src/EFCore/Extensions/ConventionNavigationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ namespace Microsoft.EntityFrameworkCore
/// </summary>
public static class ConventionNavigationExtensions
{
/// <summary>
/// Returns the configuration source for the navigation.
/// </summary>
/// <param name="navigation"> The navigation property to find configuration source for. </param>
/// <returns> The configuration source for the navigation. </returns>
public static ConfigurationSource? GetConfigurationSource([NotNull] this IConventionNavigation navigation)
=> navigation.IsDependentToPrincipal()
? navigation.ForeignKey.GetDependentToPrincipalConfigurationSource()
: navigation.ForeignKey.GetPrincipalToDependentConfigurationSource();

/// <summary>
/// Gets the navigation property on the other end of the relationship. Returns null if
/// there is no navigation property defined on the other end of the relationship.
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ protected virtual void ValidateRelationships(
navigation.Name, navigation.DeclaringEntityType.DisplayName()));
}

if (navigation.ForeignKey == null)
{
throw new InvalidOperationException(CoreStrings.SkipNavigationNoForeignKey(
navigation.Name, navigation.DeclaringEntityType.DisplayName()));
}

if (navigation.Inverse == null)
{
throw new InvalidOperationException(CoreStrings.SkipNavigationNoInverse(
Expand Down
6 changes: 6 additions & 0 deletions src/EFCore/Metadata/Conventions/ConventionSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ public class ConventionSet
public virtual IList<ISkipNavigationAnnotationChangedConvention> SkipNavigationAnnotationChangedConventions { get; }
= new List<ISkipNavigationAnnotationChangedConvention>();

/// <summary>
/// Conventions to run when a skip navigation foreign key is changed.
/// </summary>
public virtual IList<ISkipNavigationForeignKeyChangedConvention> SkipNavigationForeignKeyChangedConventions { get; }
= new List<ISkipNavigationForeignKeyChangedConvention>();

/// <summary>
/// Conventions to run when a skip navigation inverse is changed.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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 JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
/// <summary>
/// Represents an operation that should be performed when a skip navigation foreign key is changed.
/// </summary>
public interface ISkipNavigationForeignKeyChangedConvention : IConvention
{
/// <summary>
/// Called after a skip navigation inverse is changed.
/// </summary>
/// <param name="skipNavigationBuilder"> The builder for the skip navigation. </param>
/// <param name="foreignKey"> The current skip navigation foreign key. </param>
/// <param name="oldForeignKey"> The old skip navigation foreign key. </param>
/// <param name="context"> Additional information associated with convention execution. </param>
void ProcessSkipNavigationForeignKeyChanged(
[NotNull] IConventionSkipNavigationBuilder skipNavigationBuilder,
[CanBeNull] IConventionForeignKey foreignKey,
[CanBeNull] IConventionForeignKey oldForeignKey,
[NotNull] IConventionContext<IConventionForeignKey> context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ public abstract IConventionAnnotation OnNavigationAnnotationChanged(
public abstract IConventionSkipNavigationBuilder OnSkipNavigationAdded(
[NotNull] IConventionSkipNavigationBuilder navigationBuilder);

public abstract IConventionForeignKey OnSkipNavigationForeignKeyChanged(
[NotNull] IConventionSkipNavigationBuilder navigationBuilder,
[NotNull] IConventionForeignKey foreignKey,
[NotNull] IConventionForeignKey oldForeignKey);

public abstract IConventionAnnotation OnSkipNavigationAnnotationChanged(
[NotNull] IConventionSkipNavigationBuilder navigationBuilder,
[NotNull] string name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ public override IConventionAnnotation OnSkipNavigationAnnotationChanged(
return annotation;
}

public override IConventionForeignKey OnSkipNavigationForeignKeyChanged(
IConventionSkipNavigationBuilder navigationBuilder,
IConventionForeignKey foreignKey,
IConventionForeignKey oldForeignKey)
{
Add(new OnSkipNavigationForeignKeyChangedNode(navigationBuilder, foreignKey, oldForeignKey));
return foreignKey;
}

public override IConventionSkipNavigation OnSkipNavigationInverseChanged(
IConventionSkipNavigationBuilder navigationBuilder,
IConventionSkipNavigation inverse,
Expand Down Expand Up @@ -686,6 +695,26 @@ public override void Run(ConventionDispatcher dispatcher)
NavigationBuilder, Name, Annotation, OldAnnotation);
}

private sealed class OnSkipNavigationForeignKeyChangedNode : ConventionNode
{
public OnSkipNavigationForeignKeyChangedNode(
IConventionSkipNavigationBuilder navigationBuilder,
IConventionForeignKey foreignKey,
IConventionForeignKey oldForeignKey)
{
NavigationBuilder = navigationBuilder;
ForeignKey = foreignKey;
OldForeignKey = oldForeignKey;
}

public IConventionSkipNavigationBuilder NavigationBuilder { get; }
public IConventionForeignKey ForeignKey { get; }
public IConventionForeignKey OldForeignKey { get; }

public override void Run(ConventionDispatcher dispatcher)
=> dispatcher._immediateConventionScope.OnSkipNavigationForeignKeyChanged(NavigationBuilder, ForeignKey, OldForeignKey);
}

private sealed class OnSkipNavigationInverseChangedNode : ConventionNode
{
public OnSkipNavigationInverseChangedNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,45 @@ public override IConventionAnnotation OnSkipNavigationAnnotationChanged(
return annotation;
}

public override IConventionForeignKey OnSkipNavigationForeignKeyChanged(
IConventionSkipNavigationBuilder navigationBuilder,
IConventionForeignKey foreignKey,
IConventionForeignKey oldForeignKey)
{
if (navigationBuilder.Metadata.DeclaringEntityType.Builder == null)
{
return null;
}

using (_dispatcher.DelayConventions())
{
_foreignKeyConventionContext.ResetState(foreignKey);
foreach (var skipNavigationConvention in _conventionSet.SkipNavigationForeignKeyChangedConventions)
{
if (navigationBuilder.Metadata.Builder == null
|| navigationBuilder.Metadata.ForeignKey != foreignKey)
{
Check.DebugAssert(false, "Foreign key changed");
return null;
}

skipNavigationConvention.ProcessSkipNavigationForeignKeyChanged(
navigationBuilder, foreignKey, oldForeignKey, _foreignKeyConventionContext);
if (_foreignKeyConventionContext.ShouldStopProcessing())
{
return _foreignKeyConventionContext.Result;
}
}
}

if (navigationBuilder.Metadata.Builder == null)
{
return null;
}

return foreignKey;
}

public override IConventionSkipNavigation OnSkipNavigationInverseChanged(
IConventionSkipNavigationBuilder navigationBuilder,
IConventionSkipNavigation inverse,
Expand Down
12 changes: 12 additions & 0 deletions src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,18 @@ public virtual IConventionSkipNavigationBuilder OnSkipNavigationAdded(
[NotNull] IConventionSkipNavigationBuilder navigationBuilder)
=> _scope.OnSkipNavigationAdded(navigationBuilder);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IConventionForeignKey OnSkipNavigationForeignKeyChanged(
[NotNull] IConventionSkipNavigationBuilder navigationBuilder,
[NotNull] IConventionForeignKey foreignKey,
[NotNull] IConventionForeignKey oldForeignKey)
=> _scope.OnSkipNavigationForeignKeyChanged(navigationBuilder, foreignKey, oldForeignKey);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/IConventionEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ IConventionSkipNavigation AddSkipNavigation(
[NotNull] string name,
[CanBeNull] MemberInfo memberInfo,
[NotNull] IConventionEntityType targetEntityType,
[NotNull] IConventionForeignKey foreignKey,
[CanBeNull] IConventionForeignKey foreignKey,
bool collection,
bool onPrincipal,
bool fromDataAnnotation = false);
Expand Down
39 changes: 27 additions & 12 deletions src/EFCore/Metadata/IConventionSkipNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,48 @@ public interface IConventionSkipNavigation : ISkipNavigation, IConventionPropert
/// <summary>
/// Gets the type that this navigation property belongs to.
/// </summary>
new IConventionEntityType DeclaringEntityType => IsOnPrincipal ? ForeignKey.PrincipalEntityType : ForeignKey.DeclaringEntityType;
new IConventionEntityType DeclaringEntityType => (IConventionEntityType)((ISkipNavigation)this).DeclaringEntityType;

/// <summary>
/// Gets the entity type that this navigation property will hold an instance(s) of.
/// </summary>
new IConventionEntityType TargetEntityType => (IConventionEntityType)((ISkipNavigation)this).TargetEntityType;

/// <summary>
/// Gets the association type used by the foreign key.
/// </summary>
new IConventionEntityType AssociationEntityType => IsOnPrincipal ? ForeignKey.DeclaringEntityType : ForeignKey.PrincipalEntityType;
new IConventionEntityType AssociationEntityType => (IConventionEntityType)((ISkipNavigation)this).AssociationEntityType;

/// <summary>
/// Gets the entity type that this navigation property will hold an instance(s) of.
/// Returns the configuration source for this property.
/// </summary>
new IConventionEntityType TargetEntityType { get; }
/// <returns> The configuration source. </returns>
ConfigurationSource GetConfigurationSource();

/// <summary>
/// Gets the foreign key to the association type.
/// </summary>
new IConventionForeignKey ForeignKey { get; }
new IConventionForeignKey ForeignKey => (IConventionForeignKey)((ISkipNavigation)this).ForeignKey;

/// <summary>
/// Gets the inverse skip navigation.
/// Sets the foreign key.
/// </summary>
new IConventionSkipNavigation Inverse { get; }
/// <param name="foreignKey">
/// The foreign key. Passing <c>null</c> will result in there being no foreign key associated.
/// </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
IConventionForeignKey SetForeignKey([CanBeNull] IConventionForeignKey foreignKey, bool fromDataAnnotation = false);

/// <summary>
/// Returns the configuration source for this property.
/// Returns the configuration source for <see cref="ForeignKey" />.
/// </summary>
/// <returns> The configuration source. </returns>
ConfigurationSource GetConfigurationSource();
/// <returns> The configuration source for <see cref="ForeignKey" />. </returns>
ConfigurationSource? GetForeignKeyConfigurationSource();

/// <summary>
/// Gets the inverse skip navigation.
/// </summary>
new IConventionSkipNavigation Inverse => (IConventionSkipNavigation)((ISkipNavigation)this).Inverse;

/// <summary>
/// Sets the inverse skip navigation.
Expand All @@ -64,9 +79,9 @@ public interface IConventionSkipNavigation : ISkipNavigation, IConventionPropert
IConventionSkipNavigation SetInverse([CanBeNull] IConventionSkipNavigation inverse, bool fromDataAnnotation = false);

/// <summary>
/// Returns the configuration source for <see cref="ISkipNavigation.Inverse" />.
/// Returns the configuration source for <see cref="Inverse" />.
/// </summary>
/// <returns> The configuration source for <see cref="ISkipNavigation.Inverse" />. </returns>
/// <returns> The configuration source for <see cref="Inverse" />. </returns>
ConfigurationSource? GetInverseConfigurationSource();
}
}
3 changes: 1 addition & 2 deletions src/EFCore/Metadata/IMutableEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata
{
Expand Down Expand Up @@ -150,7 +149,7 @@ IMutableSkipNavigation AddSkipNavigation(
[NotNull] string name,
[CanBeNull] MemberInfo memberInfo,
[NotNull] IMutableEntityType targetEntityType,
[NotNull] IMutableForeignKey foreignKey,
[CanBeNull] IMutableForeignKey foreignKey,
bool collection,
bool onPrincipal);

Expand Down
22 changes: 15 additions & 7 deletions src/EFCore/Metadata/IMutableSkipNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,35 @@ public interface IMutableSkipNavigation : ISkipNavigation, IMutablePropertyBase
/// <summary>
/// Gets the type that this navigation property belongs to.
/// </summary>
new IMutableEntityType DeclaringEntityType => IsOnPrincipal ? ForeignKey.PrincipalEntityType : ForeignKey.DeclaringEntityType;
new IMutableEntityType DeclaringEntityType => (IMutableEntityType)((ISkipNavigation)this).DeclaringEntityType;

/// <summary>
/// Gets the association type used by the foreign key.
/// Gets the entity type that this navigation property will hold an instance(s) of.
/// </summary>
new IMutableEntityType AssociationEntityType => IsOnPrincipal ? ForeignKey.DeclaringEntityType : ForeignKey.PrincipalEntityType;
new IMutableEntityType TargetEntityType => (IMutableEntityType)((ISkipNavigation)this).TargetEntityType;

/// <summary>
/// Gets the entity type that this navigation property will hold an instance(s) of.
/// Gets the association type used by the foreign key.
/// </summary>
new IMutableEntityType TargetEntityType { get; }
new IMutableEntityType AssociationEntityType => (IMutableEntityType)((ISkipNavigation)this).AssociationEntityType;

/// <summary>
/// Gets the foreign key to the association type.
/// </summary>
new IMutableForeignKey ForeignKey { get; }
new IMutableForeignKey ForeignKey => (IMutableForeignKey)((ISkipNavigation)this).ForeignKey;

/// <summary>
/// Sets the foreign key.
/// </summary>
/// <param name="foreignKey">
/// The foreign key. Passing <c>null</c> will result in there being no foreign key associated.
/// </param>
IMutableForeignKey SetForeignKey([CanBeNull] IMutableForeignKey foreignKey);

/// <summary>
/// Gets the inverse skip navigation.
/// </summary>
new IMutableSkipNavigation Inverse { get; }
new IMutableSkipNavigation Inverse => (IMutableSkipNavigation)((ISkipNavigation)this).Inverse;

/// <summary>
/// Sets the inverse skip navigation.
Expand Down
10 changes: 5 additions & 5 deletions src/EFCore/Metadata/ISkipNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ public interface ISkipNavigation : IPropertyBase
/// <summary>
/// Gets the entity type that this navigation belongs to.
/// </summary>
IEntityType DeclaringEntityType => IsOnPrincipal ? ForeignKey.PrincipalEntityType : ForeignKey.DeclaringEntityType;
IEntityType DeclaringEntityType { get; }

/// <summary>
/// Gets the association type used by the foreign key.
/// Gets the entity type that this navigation property will hold an instance(s) of.
/// </summary>
IEntityType AssociationEntityType => IsOnPrincipal ? ForeignKey.DeclaringEntityType : ForeignKey.PrincipalEntityType;
IEntityType TargetEntityType { get; }

/// <summary>
/// Gets the entity type that this navigation property will hold an instance(s) of.
/// Gets the association type used by the foreign key.
/// </summary>
IEntityType TargetEntityType { get; }
IEntityType AssociationEntityType => IsOnPrincipal ? ForeignKey?.DeclaringEntityType : ForeignKey?.PrincipalEntityType;

/// <summary>
/// Gets the foreign key to the association type.
Expand Down
Loading

0 comments on commit 20e2e1c

Please sign in to comment.