Skip to content

Commit

Permalink
Add pre-convention model configuration infrastructure
Browse files Browse the repository at this point in the history
Fixes #12229
  • Loading branch information
AndriySvyryd authored May 28, 2021
1 parent 6449203 commit dd315f6
Show file tree
Hide file tree
Showing 51 changed files with 1,373 additions and 827 deletions.
15 changes: 15 additions & 0 deletions src/EFCore/DbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,21 @@ protected internal virtual void OnConfiguring(DbContextOptionsBuilder optionsBui
{
}

/// <summary>
/// Override this method to set defaults and configure conventions before they run. This method is invoked before
/// <see cref="OnModelCreating"/>.
/// </summary>
/// <remarks>
/// If a model is explicitly set on the options for this context (via <see cref="DbContextOptionsBuilder.UseModel(IModel)" />)
/// then this method will not be run.
/// </remarks>
/// <param name="configurationBuilder">
/// The builder being used to set defaults and configure conventions that will be used to build the model for this context.
/// </param>
protected internal virtual void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
}

/// <summary>
/// Override this method to further configure the model that was discovered by convention from the entity types
/// exposed in <see cref="DbSet{TEntity}" /> properties on your derived context. The resulting model may be cached
Expand Down
6 changes: 5 additions & 1 deletion src/EFCore/Infrastructure/ModelSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ protected virtual IModel CreateModel(
{
Check.DebugAssert(context != null, "context == null");

var modelBuilder = new ModelBuilder(conventionSetBuilder.CreateConventionSet(), modelDependencies);
var modelConfigurationBuilder = new ModelConfigurationBuilder(conventionSetBuilder.CreateConventionSet());

context.ConfigureConventions(modelConfigurationBuilder);

var modelBuilder = modelConfigurationBuilder.CreateModelBuilder(modelDependencies);

Dependencies.ModelCustomizer.Customize(modelBuilder, context);

Expand Down
4 changes: 3 additions & 1 deletion src/EFCore/Metadata/Internal/ClrAccessorFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;

namespace Microsoft.EntityFrameworkCore.Metadata.Internal
{
Expand Down Expand Up @@ -61,7 +62,8 @@ protected virtual TAccessor Create(MemberInfo memberInfo, IPropertyBase? propert
}
catch (TargetInvocationException e) when (e.InnerException != null)
{
throw e.InnerException;
ExceptionDispatchInfo.Capture(e.InnerException).Throw();
throw;
}
}

Expand Down
21 changes: 17 additions & 4 deletions src/EFCore/Metadata/Internal/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
public class Model : ConventionAnnotatable, IMutableModel, IConventionModel, IRuntimeModel
{
/// <summary>
/// The CLR type that is used for property bag entity types when no other type is specified.
/// 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 static readonly Type DefaultPropertyBagType = typeof(Dictionary<string, object>);

Expand All @@ -49,11 +52,11 @@ public class Model : ConventionAnnotatable, IMutableModel, IConventionModel, IRu

private ConventionDispatcher? _conventionDispatcher;
private IList<IModelFinalizedConvention>? _modelFinalizedConventions;
private ModelDependencies? _scopedModelDependencies;
private bool? _skipDetectChanges;
private ChangeTrackingStrategy? _changeTrackingStrategy;

private ConfigurationSource? _changeTrackingStrategyConfigurationSource;
private ModelDependencies? _scopedModelDependencies;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -72,7 +75,7 @@ public Model()
/// 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 Model(ConventionSet conventions, ModelDependencies? modelDependencies = null)
public Model(ConventionSet conventions, ModelDependencies? modelDependencies = null, ModelConfiguration? modelConfiguration = null)
{
if (modelDependencies != null)
{
Expand All @@ -83,6 +86,7 @@ public Model(ConventionSet conventions, ModelDependencies? modelDependencies = n
_conventionDispatcher = dispatcher;
_modelFinalizedConventions = conventions.ModelFinalizedConventions;
Builder = builder;
Configuration = modelConfiguration;
dispatcher.OnModelInitialized(builder);
}

Expand Down Expand Up @@ -124,6 +128,14 @@ public virtual ModelDependencies? ScopedModelDependencies
/// </summary>
public virtual InternalModelBuilder Builder { [DebuggerStepThrough] get; }

/// <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 ModelConfiguration? Configuration { get; private set; }

/// <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 @@ -575,7 +587,7 @@ public virtual bool IsShared(Type type)
/// </summary>
public virtual ConfigurationSource? FindIgnoredConfigurationSource(string name)
=> _ignoredTypeNames.TryGetValue(Check.NotEmpty(name, nameof(name)), out var ignoredConfigurationSource)
? (ConfigurationSource?)ignoredConfigurationSource
? ignoredConfigurationSource
: null;

/// <summary>
Expand Down Expand Up @@ -859,6 +871,7 @@ public virtual IModel FinalizeModel()

if (finalizedModel is Model model)
{
model.Configuration = null;
finalizedModel = model.MakeReadonly();
}

Expand Down
33 changes: 33 additions & 0 deletions src/EFCore/Metadata/Internal/ModelConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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.

namespace Microsoft.EntityFrameworkCore.Metadata.Internal
{
/// <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 partial class ModelConfiguration
{
/// <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 ModelConfiguration()
{
}

/// <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 IsEmpty()
=> true;
}
}
21 changes: 12 additions & 9 deletions src/EFCore/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class ModelBuilder : IInfrastructure<IConventionModelBuilder>
/// </summary>
/// <param name="conventions"> The conventions to be applied to the model. </param>
public ModelBuilder(ConventionSet conventions)
: this(conventions, null, true)
: this(conventions, null, null)
{
}

Expand All @@ -46,16 +46,23 @@ public ModelBuilder(ConventionSet conventions)
/// <param name="conventions"> The conventions to be applied to the model. </param>
/// <param name="modelDependencies"> The dependencies object for the model. </param>
public ModelBuilder(ConventionSet conventions, ModelDependencies modelDependencies)
: this(conventions, modelDependencies, true)
: this(conventions, modelDependencies, null)
{
Check.NotNull(modelDependencies, nameof(modelDependencies));
}

private ModelBuilder(ConventionSet conventions, ModelDependencies? modelDependencies, bool _)
/// <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>
[EntityFrameworkInternal]
public ModelBuilder(ConventionSet conventions, ModelDependencies? modelDependencies, ModelConfiguration? modelConfiguration)
{
Check.NotNull(conventions, nameof(conventions));

_builder = new Model(conventions, modelDependencies).Builder;
_builder = new Model(conventions, modelDependencies, modelConfiguration).Builder;

_builder.Metadata.SetProductVersion(ProductInfo.GetVersion());
}
Expand All @@ -65,7 +72,7 @@ private ModelBuilder(ConventionSet conventions, ModelDependencies? modelDependen
/// Initializes a new instance of the <see cref="ModelBuilder" /> class with no conventions.
/// </para>
/// <para>
/// Warning: conventions are typically needed to build a correct model.
/// Warning: conventions are needed to build a correct model.
/// </para>
/// </summary>
public ModelBuilder()
Expand Down Expand Up @@ -262,7 +269,6 @@ public virtual ModelBuilder SharedTypeEntity<TEntity>(
Action<EntityTypeBuilder<TEntity>> buildAction)
where TEntity : class
{
Check.NotEmpty(name, nameof(name));
Check.NotNull(buildAction, nameof(buildAction));

buildAction(SharedTypeEntity<TEntity>(name));
Expand All @@ -288,7 +294,6 @@ public virtual ModelBuilder SharedTypeEntity<TEntity>(
/// </returns>
public virtual ModelBuilder Entity(Type type, Action<EntityTypeBuilder> buildAction)
{
Check.NotNull(type, nameof(type));
Check.NotNull(buildAction, nameof(buildAction));

buildAction(Entity(type));
Expand All @@ -315,7 +320,6 @@ public virtual ModelBuilder Entity(Type type, Action<EntityTypeBuilder> buildAct
/// </returns>
public virtual ModelBuilder Entity(string name, Action<EntityTypeBuilder> buildAction)
{
Check.NotEmpty(name, nameof(name));
Check.NotNull(buildAction, nameof(buildAction));

buildAction(Entity(name));
Expand Down Expand Up @@ -352,7 +356,6 @@ public virtual ModelBuilder SharedTypeEntity(
Type type,
Action<EntityTypeBuilder> buildAction)
{
Check.NotEmpty(name, nameof(name));
Check.NotNull(type, nameof(type));
Check.NotNull(buildAction, nameof(buildAction));

Expand Down
87 changes: 87 additions & 0 deletions src/EFCore/ModelConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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.ComponentModel;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore
{
/// <summary>
/// <para>
/// Provides a simple API surface for setting defaults and configuring conventions before they run.
/// </para>
/// <para>
/// You can use <see cref="ModelConfigurationBuilder" /> to configure the conventions for a context by overriding
/// <see cref="DbContext.ConfigureConventions(ModelConfigurationBuilder)" /> on your derived context.
/// Alternatively you can create the model externally and set it on a <see cref="DbContextOptions" /> instance
/// that is passed to the context constructor.
/// </para>
/// </summary>
public class ModelConfigurationBuilder
{
private readonly ModelConfiguration _modelConfiguration = new();
private readonly ConventionSet _conventions;

/// <summary>
/// Initializes a new instance of the <see cref="ModelConfigurationBuilder" />.
/// </summary>
/// <param name="conventions"> The conventions to be applied during model building. </param>
public ModelConfigurationBuilder(ConventionSet conventions)
{
Check.NotNull(conventions, nameof(conventions));

_conventions = conventions;
}

/// <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>
[EntityFrameworkInternal]
protected virtual ModelConfiguration ModelConfiguration => _modelConfiguration;

/// <summary>
/// Creates the configured <see cref="ModelBuilder" /> used to create the model. This is done automatically when using
/// <see cref="DbContext.OnModelCreating" />; this method allows it to be run
/// explicitly in cases where the automatic execution is not possible.
/// </summary>
/// <param name="modelDependencies"> The dependencies object used during model building. </param>
/// <returns> The configured <see cref="ModelBuilder" />. </returns>
public virtual ModelBuilder CreateModelBuilder(ModelDependencies? modelDependencies)
=> new(_conventions, modelDependencies, _modelConfiguration.IsEmpty() ? null : _modelConfiguration);

#region Hidden System.Object members

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns> A string that represents the current object. </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override string? ToString()
=> base.ToString();

/// <summary>
/// Determines whether the specified object is equal to the current object.
/// </summary>
/// <param name="obj"> The object to compare with the current object. </param>
/// <returns> <see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />. </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool Equals(object? obj)
=> base.Equals(obj);

/// <summary>
/// Serves as the default hash function.
/// </summary>
/// <returns> A hash code for the current object. </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public override int GetHashCode()
=> base.GetHashCode();

#endregion
}
}
Loading

0 comments on commit dd315f6

Please sign in to comment.