Skip to content

Commit

Permalink
Model level eager loading configuration (#19355)
Browse files Browse the repository at this point in the history
IsEagerLoaded API on NavigationBuilder to mark a navigation as eagar loaded
IgnoreEagerLoadedNavigations API on query to ignore model based config

Resolves #21540
  • Loading branch information
smitpatel authored Jul 7, 2020
1 parent 52d53b8 commit da5ed2e
Show file tree
Hide file tree
Showing 18 changed files with 436 additions and 136 deletions.
33 changes: 33 additions & 0 deletions src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2655,6 +2655,39 @@ source.Provider is EntityQueryProvider

#endregion

#region Eager loaded navigations

internal static readonly MethodInfo IgnoreEagerLoadedNavigationsMethodInfo
= typeof(EntityFrameworkQueryableExtensions)
.GetTypeInfo().GetDeclaredMethod(nameof(IgnoreEagerLoadedNavigations));

/// <summary>
/// Specifies that the current Entity Framework LINQ query should not have any
/// model-level eager loaded navigations applied.
/// </summary>
/// <typeparam name="TEntity"> The type of entity being queried. </typeparam>
/// <param name="source"> The source query. </param>
/// <returns>
/// A new query that will not apply any model-level eager loaded navigations.
/// </returns>
public static IQueryable<TEntity> IgnoreEagerLoadedNavigations<TEntity>(
[NotNull] this IQueryable<TEntity> source)
where TEntity : class
{
Check.NotNull(source, nameof(source));

return
source.Provider is EntityQueryProvider
? source.Provider.CreateQuery<TEntity>(
Expression.Call(
instance: null,
method: IgnoreEagerLoadedNavigationsMethodInfo.MakeGenericMethod(typeof(TEntity)),
arguments: source.Expression))
: source;
}

#endregion

#region Query Filters

internal static readonly MethodInfo IgnoreQueryFiltersMethodInfo
Expand Down
27 changes: 0 additions & 27 deletions src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,33 +389,6 @@ IConventionForeignKeyBuilder UsePropertyAccessMode(
bool CanSetPropertyAccessMode(
PropertyAccessMode? propertyAccessMode, bool pointsToPrincipal, bool fromDataAnnotation = false);

/// <summary>
/// Configures whether this navigation should be eager loaded by default.
/// </summary>
/// <param name="eagerLoaded"> A value indicating whether this navigation should be eager loaded by default. </param>
/// <param name="pointsToPrincipal">
/// A value indicating whether the navigation is on the dependent type pointing to the principal type.
/// </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
IConventionForeignKeyBuilder IsEagerLoaded(
bool? eagerLoaded, bool pointsToPrincipal, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether this navigation can be configured as should be eager loaded by default
/// from the current configuration source.
/// </summary>
/// <param name="eagerLoaded"> A value indicating whether this navigation should be eager loaded by default. </param>
/// <param name="pointsToPrincipal">
/// A value indicating whether the navigation is on the dependent type pointing to the principal type.
/// </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if this navigation can be configured as should be eager loaded by default. </returns>
bool CanSetIsEagerLoaded(bool? eagerLoaded, bool pointsToPrincipal, bool fromDataAnnotation = false);

/// <summary>
/// Configures whether this is a required relationship (i.e. whether none the foreign key properties can
/// be assigned <see langword="null" />).
Expand Down
20 changes: 20 additions & 0 deletions src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,25 @@ public interface IConventionNavigationBuilder : IConventionAnnotatableBuilder
/// <see langword="null" /> otherwise.
/// </returns>
IConventionNavigationBuilder UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether this navigation can be configured to be eager loaded in a query
/// from the current configuration source.
/// </summary>
/// <param name="eagerLoaded"> A value indicating whether the navigation should be eager loaded. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if eager loaded can be set for this navigation. </returns>
bool CanSetIsEagerLoaded(bool? eagerLoaded, bool fromDataAnnotation = false);

/// <summary>
/// Configures this navigation to be eager loaded in a query
/// </summary>
/// <param name="eagerLoaded"> A value indicating whether the navigation should be eager loaded. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
IConventionNavigationBuilder IsEagerLoaded(bool? eagerLoaded, bool fromDataAnnotation = false);
}
}
20 changes: 20 additions & 0 deletions src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,5 +129,25 @@ public interface IConventionSkipNavigationBuilder : IConventionAnnotatableBuilde
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the <see cref="IConventionSkipNavigation.Inverse" /> can be set for this property. </returns>
bool CanSetInverse([CanBeNull] IConventionSkipNavigation inverse, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether this navigation can be configured to be eager loaded in a query
/// from the current configuration source.
/// </summary>
/// <param name="eagerLoaded"> A value indicating whether the navigation should be eager loaded. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if eager loaded can be set for this navigation. </returns>
bool CanSetIsEagerLoaded(bool? eagerLoaded, bool fromDataAnnotation = false);

/// <summary>
/// Configures this navigation to be eager loaded in a query
/// </summary>
/// <param name="eagerLoaded"> A value indicating whether the navigation should be eager loaded. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
IConventionSkipNavigationBuilder IsEagerLoaded(bool? eagerLoaded, bool fromDataAnnotation = false);
}
}
23 changes: 20 additions & 3 deletions src/EFCore/Metadata/Builders/NavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ public virtual NavigationBuilder UsePropertyAccessMode(PropertyAccessMode proper
}

/// <summary>
/// <para>
/// Sets a backing field to use for this navigation property.
/// </para>
/// Sets a backing field to use for this navigation property.
/// </summary>
/// <param name="fieldName"> The name of the field to use for this navigation property. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
Expand All @@ -124,6 +122,25 @@ public virtual NavigationBuilder HasField([CanBeNull] string fieldName)
return this;
}

/// <summary>
/// Configures whether this navigation should be eager loaded in a query.
/// </summary>
/// <param name="eagerLoaded"> A value indicating if the navigation should be eager loaded. </param>
/// <returns> The same builder instance so that multiple configuration calls can be chained. </returns>
public virtual NavigationBuilder IsEagerLoaded(bool eagerLoaded = true)
{
if (InternalNavigationBuilder != null)
{
InternalNavigationBuilder.IsEagerLoaded(eagerLoaded, ConfigurationSource.Explicit);
}
else
{
InternalSkipNavigationBuilder.IsEagerLoaded(eagerLoaded, ConfigurationSource.Explicit);
}

return this;
}

/// <summary>
/// The internal builder being used to configure the skip navigation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public NavigationEagerLoadingConvention([NotNull] ProviderConventionSetBuilderDe
public virtual void ProcessForeignKeyOwnershipChanged(
IConventionForeignKeyBuilder relationshipBuilder, IConventionContext<bool?> context)
{
relationshipBuilder.Metadata.PrincipalToDependent?.SetIsEagerLoaded(relationshipBuilder.Metadata.IsOwnership);
relationshipBuilder.Metadata.PrincipalToDependent?.Builder.IsEagerLoaded(relationshipBuilder.Metadata.IsOwnership);
}
}
}
78 changes: 4 additions & 74 deletions src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,53 +1075,6 @@ public virtual bool CanSetPropertyAccessMode(
|| navigation.GetPropertyAccessMode() == propertyAccessMode);
}

/// <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 InternalForeignKeyBuilder IsEagerLoaded(
bool? eagerLoaded,
bool pointsToPrincipal,
ConfigurationSource configurationSource)
{
var navigation = pointsToPrincipal ? Metadata.DependentToPrincipal : Metadata.PrincipalToDependent;
if (navigation == null)
{
throw new InvalidOperationException(
CoreStrings.NoNavigation(
pointsToPrincipal ? Metadata.DeclaringEntityType.DisplayName() : Metadata.PrincipalEntityType.DisplayName(),
Metadata.Properties.Format()));
}

if (CanSetIsEagerLoaded(eagerLoaded, pointsToPrincipal, configurationSource))
{
navigation.SetIsEagerLoaded(eagerLoaded, configurationSource);

return this;
}

return null;
}

/// <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 bool CanSetIsEagerLoaded(
bool? eagerLoaded,
bool pointsToPrincipal,
ConfigurationSource? configurationSource)
{
IConventionNavigation navigation = pointsToPrincipal ? Metadata.DependentToPrincipal : Metadata.PrincipalToDependent;
return navigation != null
&& (configurationSource.Overrides(navigation.GetIsEagerLoadedConfigurationSource())
|| navigation.IsEagerLoaded == eagerLoaded);
}

/// <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 Expand Up @@ -2887,10 +2840,10 @@ private InternalForeignKeyBuilder GetOrCreateRelationshipBuilder(

foreach (var relationshipWithResolution in resolvableRelationships)
{
var resolvableRelationship = relationshipWithResolution.Item1;
var sameConfigurationSource = relationshipWithResolution.Item2;
var resolution = relationshipWithResolution.Item3;
var inverseNavigationRemoved = relationshipWithResolution.Item4;
var resolvableRelationship = relationshipWithResolution.Builder;
var sameConfigurationSource = relationshipWithResolution.SameConfigurationSource;
var resolution = relationshipWithResolution.Resolution;
var inverseNavigationRemoved = relationshipWithResolution.InverseNavigationShouldBeRemoved;
if (sameConfigurationSource
&& configurationSource == ConfigurationSource.Explicit
&& inverseNavigationRemoved)
Expand Down Expand Up @@ -4341,29 +4294,6 @@ bool IConventionForeignKeyBuilder.CanSetPropertyAccessMode(
pointsToPrincipal,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <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>
[DebuggerStepThrough]
IConventionForeignKeyBuilder IConventionForeignKeyBuilder.IsEagerLoaded(
bool? eagerLoaded, bool pointsToPrincipal, bool fromDataAnnotation)
=> IsEagerLoaded(
eagerLoaded, pointsToPrincipal, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <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>
[DebuggerStepThrough]
bool IConventionForeignKeyBuilder.CanSetIsEagerLoaded(bool? eagerLoaded, bool pointsToPrincipal, bool fromDataAnnotation)
=> CanSetIsEagerLoaded(
eagerLoaded, pointsToPrincipal, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

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

0 comments on commit da5ed2e

Please sign in to comment.