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

Add pre-convention model configuration infrastructure #24993

Merged
merged 1 commit into from
May 28, 2021
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
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 @@ public ModelSource(ModelSourceDependencies dependencies)
{
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to the rest of the PR, I was just annoyed that we lost the stack trace

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>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

attribute?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already pubturnal


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 ConventionDispatcher ConventionDispatcher
/// </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 Entity<TEntity>(Action<EntityTypeBuilder<TEntity>> b
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 Entity<TEntity>(Action<EntityTypeBuilder<TEntity>> b
/// </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 Entity(string name, Action<EntityTypeBuilder> buildA
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