Skip to content

Commit

Permalink
Add pre-convention configuration for conventions
Browse files Browse the repository at this point in the history
Part of #214
  • Loading branch information
AndriySvyryd committed Aug 6, 2022
1 parent 71b7f6d commit 5e00822
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 99 deletions.
4 changes: 3 additions & 1 deletion src/EFCore/Infrastructure/ModelSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ protected virtual IModel CreateModel(
{
Check.DebugAssert(context != null, "context == null");

var modelConfigurationBuilder = new ModelConfigurationBuilder(conventionSetBuilder.CreateConventionSet());
var modelConfigurationBuilder = new ModelConfigurationBuilder(
conventionSetBuilder.CreateConventionSet(),
context.GetInfrastructure());

context.ConfigureConventions(modelConfigurationBuilder);

Expand Down
100 changes: 100 additions & 0 deletions src/EFCore/Metadata/Builders/ConventionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders;

/// <summary>
/// Provides a simple API surface for configuring conventions.
/// </summary>
/// <remarks>
/// Instances of this class are returned from methods when using the <see cref="ModelConfigurationBuilder" /> API
/// and it is not designed to be directly constructed in your application code.
/// </remarks>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
/// </remarks>
public class ConventionsBuilder
{
private readonly IServiceProvider _serviceProvider;
private readonly ConventionSet _conventionSet;

/// <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 ConventionsBuilder(ConventionSet conventionSet, IServiceProvider serviceProvider)
{
Check.NotNull(conventionSet, nameof(conventionSet));

_conventionSet = conventionSet;
_serviceProvider = serviceProvider;
}

/// <summary>
/// Replaces an existing convention with a derived convention. Also registers the new convention for any
/// convention types not implemented by the existing convention.
/// </summary>
/// <typeparam name="TImplementation">The type of the old convention.</typeparam>
/// <param name="conventionFactory">The factory that creates the new convention.</param>
public virtual void Replace<TImplementation>(Func<IServiceProvider, TImplementation> conventionFactory)
where TImplementation : IConvention
{
var convention = conventionFactory(_serviceProvider);
_conventionSet.Replace(convention);
}

/// <summary>
/// Adds a convention to the set.
/// </summary>
/// <param name="conventionFactory">The factory that creates the convention.</param>
public virtual void Add(Func<IServiceProvider, IConvention> conventionFactory)
{
var convention = conventionFactory(_serviceProvider);
_conventionSet.Add(convention);
}

/// <summary>
/// Removes the convention of the given type.
/// </summary>
/// <param name="conventionType">The convention type to remove.</param>
public virtual void Remove(Type conventionType)
{
_conventionSet.Remove(conventionType);
}

#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)]
// ReSharper disable once BaseObjectEqualsIsObjectEquals
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)]
// ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
public override int GetHashCode()
=> base.GetHashCode();

#endregion
}
23 changes: 15 additions & 8 deletions src/EFCore/ModelConfigurationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ public class ModelConfigurationBuilder
{
private readonly ModelConfiguration _modelConfiguration = new();
private readonly ConventionSet _conventions;

private readonly ConventionsBuilder _conventionsBuilder;

/// <summary>
/// Initializes a new instance of the <see cref="ModelConfigurationBuilder" />.
/// 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>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-pre-convention">Pre-convention model building in EF Core</see> for more information and
/// examples.
/// </remarks>
/// <param name="conventions">The conventions to be applied during model building.</param>
public ModelConfigurationBuilder(ConventionSet conventions)
[EntityFrameworkInternal]
public ModelConfigurationBuilder(ConventionSet conventions, IServiceProvider serviceProvider)
{
Check.NotNull(conventions, nameof(conventions));

_conventions = conventions;
_conventionsBuilder = new ConventionsBuilder(conventions, serviceProvider);
}

/// <summary>
Expand All @@ -51,6 +52,12 @@ public ModelConfigurationBuilder(ConventionSet conventions)
protected virtual ModelConfiguration ModelConfiguration
=> _modelConfiguration;

/// <summary>
/// Gets the builder for the conventions that will be used in the model.
/// </summary>
public virtual ConventionsBuilder Conventions
=> _conventionsBuilder;

/// <summary>
/// Prevents the conventions from the given type from discovering properties of the given or derived types.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3432,7 +3432,7 @@ protected virtual TestHelpers.TestModelBuilder CreateModelBuilderWithoutConventi
=> TestHelpers.CreateConventionBuilder(
CreateModelLogger(sensitiveDataLoggingEnabled), CreateValidationLogger(sensitiveDataLoggingEnabled),
modelConfigurationBuilder => ConventionSet.Remove(
modelConfigurationBuilder.Conventions.ModelFinalizingConventions,
modelConfigurationBuilder.ConventionSet.ModelFinalizingConventions,
typeof(T)));

protected override TestHelpers TestHelpers
Expand Down
97 changes: 48 additions & 49 deletions test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions;

Expand Down Expand Up @@ -152,7 +150,8 @@ public TestModelBuilder CreateConventionBuilder(
var modelCreationDependencies = contextServices.GetRequiredService<ModelCreationDependencies>();

var modelConfigurationBuilder = new TestModelConfigurationBuilder(
modelCreationDependencies.ConventionSetBuilder.CreateConventionSet());
modelCreationDependencies.ConventionSetBuilder.CreateConventionSet(),
contextServices);

configure?.Invoke(modelConfigurationBuilder);

Expand Down Expand Up @@ -412,68 +411,68 @@ public IModel FinalizeModel(bool designTime = false, bool skipValidation = false

public class TestModelConfigurationBuilder : ModelConfigurationBuilder
{
public TestModelConfigurationBuilder(ConventionSet conventions)
: base(conventions)
public TestModelConfigurationBuilder(ConventionSet conventionSet, IServiceProvider serviceProvider)
: base(conventionSet, serviceProvider)
{
Conventions = conventions;
ConventionSet = conventionSet;
}

public ConventionSet Conventions { get; }
public ConventionSet ConventionSet { get; }

public TestModelBuilder CreateModelBuilder(
ModelDependencies modelDependencies,
IModelRuntimeInitializer modelRuntimeInitializer,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> validationLogger)
=> new(
Conventions,
ConventionSet,
modelDependencies,
ModelConfiguration.IsEmpty() ? null : ModelConfiguration.Validate(),
modelRuntimeInitializer,
validationLogger);

public void RemoveAllConventions()
{
Conventions.EntityTypeAddedConventions.Clear();
Conventions.EntityTypeAnnotationChangedConventions.Clear();
Conventions.EntityTypeBaseTypeChangedConventions.Clear();
Conventions.EntityTypeIgnoredConventions.Clear();
Conventions.EntityTypeMemberIgnoredConventions.Clear();
Conventions.EntityTypePrimaryKeyChangedConventions.Clear();
Conventions.EntityTypeRemovedConventions.Clear();
Conventions.ForeignKeyAddedConventions.Clear();
Conventions.ForeignKeyAnnotationChangedConventions.Clear();
Conventions.ForeignKeyDependentRequirednessChangedConventions.Clear();
Conventions.ForeignKeyOwnershipChangedConventions.Clear();
Conventions.ForeignKeyPrincipalEndChangedConventions.Clear();
Conventions.ForeignKeyPropertiesChangedConventions.Clear();
Conventions.ForeignKeyRemovedConventions.Clear();
Conventions.ForeignKeyRequirednessChangedConventions.Clear();
Conventions.ForeignKeyUniquenessChangedConventions.Clear();
Conventions.IndexAddedConventions.Clear();
Conventions.IndexAnnotationChangedConventions.Clear();
Conventions.IndexRemovedConventions.Clear();
Conventions.IndexUniquenessChangedConventions.Clear();
Conventions.IndexSortOrderChangedConventions.Clear();
Conventions.KeyAddedConventions.Clear();
Conventions.KeyAnnotationChangedConventions.Clear();
Conventions.KeyRemovedConventions.Clear();
Conventions.ModelAnnotationChangedConventions.Clear();
Conventions.ModelFinalizedConventions.Clear();
Conventions.ModelFinalizingConventions.Clear();
Conventions.ModelInitializedConventions.Clear();
Conventions.NavigationAddedConventions.Clear();
Conventions.NavigationAnnotationChangedConventions.Clear();
Conventions.NavigationRemovedConventions.Clear();
Conventions.PropertyAddedConventions.Clear();
Conventions.PropertyAnnotationChangedConventions.Clear();
Conventions.PropertyFieldChangedConventions.Clear();
Conventions.PropertyNullabilityChangedConventions.Clear();
Conventions.PropertyRemovedConventions.Clear();
Conventions.SkipNavigationAddedConventions.Clear();
Conventions.SkipNavigationAnnotationChangedConventions.Clear();
Conventions.SkipNavigationForeignKeyChangedConventions.Clear();
Conventions.SkipNavigationInverseChangedConventions.Clear();
Conventions.SkipNavigationRemovedConventions.Clear();
ConventionSet.EntityTypeAddedConventions.Clear();
ConventionSet.EntityTypeAnnotationChangedConventions.Clear();
ConventionSet.EntityTypeBaseTypeChangedConventions.Clear();
ConventionSet.EntityTypeIgnoredConventions.Clear();
ConventionSet.EntityTypeMemberIgnoredConventions.Clear();
ConventionSet.EntityTypePrimaryKeyChangedConventions.Clear();
ConventionSet.EntityTypeRemovedConventions.Clear();
ConventionSet.ForeignKeyAddedConventions.Clear();
ConventionSet.ForeignKeyAnnotationChangedConventions.Clear();
ConventionSet.ForeignKeyDependentRequirednessChangedConventions.Clear();
ConventionSet.ForeignKeyOwnershipChangedConventions.Clear();
ConventionSet.ForeignKeyPrincipalEndChangedConventions.Clear();
ConventionSet.ForeignKeyPropertiesChangedConventions.Clear();
ConventionSet.ForeignKeyRemovedConventions.Clear();
ConventionSet.ForeignKeyRequirednessChangedConventions.Clear();
ConventionSet.ForeignKeyUniquenessChangedConventions.Clear();
ConventionSet.IndexAddedConventions.Clear();
ConventionSet.IndexAnnotationChangedConventions.Clear();
ConventionSet.IndexRemovedConventions.Clear();
ConventionSet.IndexUniquenessChangedConventions.Clear();
ConventionSet.IndexSortOrderChangedConventions.Clear();
ConventionSet.KeyAddedConventions.Clear();
ConventionSet.KeyAnnotationChangedConventions.Clear();
ConventionSet.KeyRemovedConventions.Clear();
ConventionSet.ModelAnnotationChangedConventions.Clear();
ConventionSet.ModelFinalizedConventions.Clear();
ConventionSet.ModelFinalizingConventions.Clear();
ConventionSet.ModelInitializedConventions.Clear();
ConventionSet.NavigationAddedConventions.Clear();
ConventionSet.NavigationAnnotationChangedConventions.Clear();
ConventionSet.NavigationRemovedConventions.Clear();
ConventionSet.PropertyAddedConventions.Clear();
ConventionSet.PropertyAnnotationChangedConventions.Clear();
ConventionSet.PropertyFieldChangedConventions.Clear();
ConventionSet.PropertyNullabilityChangedConventions.Clear();
ConventionSet.PropertyRemovedConventions.Clear();
ConventionSet.SkipNavigationAddedConventions.Clear();
ConventionSet.SkipNavigationAnnotationChangedConventions.Clear();
ConventionSet.SkipNavigationForeignKeyChangedConventions.Clear();
ConventionSet.SkipNavigationInverseChangedConventions.Clear();
ConventionSet.SkipNavigationRemovedConventions.Clear();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ protected override IModel CreateModel(
IConventionSetBuilder conventionSetBuilder,
ModelDependencies modelDependencies)
{
var modelConfigurationBuilder = new ModelConfigurationBuilder(conventionSetBuilder.CreateConventionSet());
var modelConfigurationBuilder = new ModelConfigurationBuilder(
conventionSetBuilder.CreateConventionSet(),
context.GetInfrastructure());
_configureConventions?.Invoke(modelConfigurationBuilder);
var modelBuilder = modelConfigurationBuilder.CreateModelBuilder(modelDependencies);

Expand Down
61 changes: 61 additions & 0 deletions test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,67 @@ public virtual void Properties_can_be_ignored_by_type()
Assert.Null(entityType.FindProperty(nameof(Customer.AlternateKey)));
}

[ConditionalFact]
public virtual void Conventions_can_be_added()
{
var modelBuilder = CreateModelBuilder(c => c.Conventions.Add(s => new TestConvention()));

var model = modelBuilder.FinalizeModel();

Assert.Equal("bar", model["foo"]);
}

[ConditionalFact]
public virtual void Conventions_can_be_removed()
{
var modelBuilder = CreateModelBuilder(c =>
{
c.Conventions.Add(s => new TestConvention());
c.Conventions.Remove(typeof(TestConvention));
});

var model = modelBuilder.FinalizeModel();

Assert.Null(model["foo"]);
}

[ConditionalFact]
public virtual void Conventions_can_be_replaced()
{
var modelBuilder = CreateModelBuilder(c =>
c.Conventions.Replace<DbSetFindingConvention>(s =>
new TestDbSetFindingConvention(s.GetService<ProviderConventionSetBuilderDependencies>())));

var model = modelBuilder.FinalizeModel();

Assert.Equal("bar", model["foo"]);
}

protected class TestConvention : IModelInitializedConvention
{
public void ProcessModelInitialized(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
modelBuilder.HasAnnotation("foo", "bar");
}
}

protected class TestDbSetFindingConvention : DbSetFindingConvention
{
public TestDbSetFindingConvention(ProviderConventionSetBuilderDependencies dependencies)
: base(dependencies)
{
}

public override void ProcessModelInitialized(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
modelBuilder.HasAnnotation("foo", "bar");
}
}

[ConditionalFact]
public virtual void Int32_cannot_be_ignored()
=> Assert.Equal(
Expand Down
Loading

0 comments on commit 5e00822

Please sign in to comment.