Skip to content

Commit 6063aed

Browse files
authored
Add pre-convention configuration for conventions
Part of dotnet#214
1 parent 1cb83ca commit 6063aed

File tree

8 files changed

+268
-99
lines changed

8 files changed

+268
-99
lines changed

src/EFCore/Infrastructure/ModelSource.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ protected virtual IModel CreateModel(
9494
{
9595
Check.DebugAssert(context != null, "context == null");
9696

97-
var modelConfigurationBuilder = new ModelConfigurationBuilder(conventionSetBuilder.CreateConventionSet());
97+
var modelConfigurationBuilder = new ModelConfigurationBuilder(
98+
conventionSetBuilder.CreateConventionSet(),
99+
context.GetInfrastructure());
98100

99101
context.ConfigureConventions(modelConfigurationBuilder);
100102

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.ComponentModel;
5+
6+
namespace Microsoft.EntityFrameworkCore.Metadata.Builders;
7+
8+
/// <summary>
9+
/// Provides a simple API surface for configuring conventions.
10+
/// </summary>
11+
/// <remarks>
12+
/// Instances of this class are returned from methods when using the <see cref="ModelConfigurationBuilder" /> API
13+
/// and it is not designed to be directly constructed in your application code.
14+
/// </remarks>
15+
/// <remarks>
16+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see> for more information and examples.
17+
/// </remarks>
18+
public class ConventionsBuilder
19+
{
20+
private readonly IServiceProvider _serviceProvider;
21+
private readonly ConventionSet _conventionSet;
22+
23+
/// <summary>
24+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
25+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
26+
/// any release. You should only use it directly in your code with extreme caution and knowing that
27+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
28+
/// </summary>
29+
[EntityFrameworkInternal]
30+
public ConventionsBuilder(ConventionSet conventionSet, IServiceProvider serviceProvider)
31+
{
32+
Check.NotNull(conventionSet, nameof(conventionSet));
33+
34+
_conventionSet = conventionSet;
35+
_serviceProvider = serviceProvider;
36+
}
37+
38+
/// <summary>
39+
/// Replaces an existing convention with a derived convention. Also registers the new convention for any
40+
/// convention types not implemented by the existing convention.
41+
/// </summary>
42+
/// <typeparam name="TImplementation">The type of the old convention.</typeparam>
43+
/// <param name="conventionFactory">The factory that creates the new convention.</param>
44+
public virtual void Replace<TImplementation>(Func<IServiceProvider, TImplementation> conventionFactory)
45+
where TImplementation : IConvention
46+
{
47+
var convention = conventionFactory(_serviceProvider);
48+
_conventionSet.Replace(convention);
49+
}
50+
51+
/// <summary>
52+
/// Adds a convention to the set.
53+
/// </summary>
54+
/// <param name="conventionFactory">The factory that creates the convention.</param>
55+
public virtual void Add(Func<IServiceProvider, IConvention> conventionFactory)
56+
{
57+
var convention = conventionFactory(_serviceProvider);
58+
_conventionSet.Add(convention);
59+
}
60+
61+
/// <summary>
62+
/// Removes the convention of the given type.
63+
/// </summary>
64+
/// <param name="conventionType">The convention type to remove.</param>
65+
public virtual void Remove(Type conventionType)
66+
{
67+
_conventionSet.Remove(conventionType);
68+
}
69+
70+
#region Hidden System.Object members
71+
72+
/// <summary>
73+
/// Returns a string that represents the current object.
74+
/// </summary>
75+
/// <returns>A string that represents the current object.</returns>
76+
[EditorBrowsable(EditorBrowsableState.Never)]
77+
public override string? ToString()
78+
=> base.ToString();
79+
80+
/// <summary>
81+
/// Determines whether the specified object is equal to the current object.
82+
/// </summary>
83+
/// <param name="obj">The object to compare with the current object.</param>
84+
/// <returns><see langword="true" /> if the specified object is equal to the current object; otherwise, <see langword="false" />.</returns>
85+
[EditorBrowsable(EditorBrowsableState.Never)]
86+
// ReSharper disable once BaseObjectEqualsIsObjectEquals
87+
public override bool Equals(object? obj)
88+
=> base.Equals(obj);
89+
90+
/// <summary>
91+
/// Serves as the default hash function.
92+
/// </summary>
93+
/// <returns>A hash code for the current object.</returns>
94+
[EditorBrowsable(EditorBrowsableState.Never)]
95+
// ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
96+
public override int GetHashCode()
97+
=> base.GetHashCode();
98+
99+
#endregion
100+
}

src/EFCore/ModelConfigurationBuilder.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,21 @@ public class ModelConfigurationBuilder
2525
{
2626
private readonly ModelConfiguration _modelConfiguration = new();
2727
private readonly ConventionSet _conventions;
28-
28+
private readonly ConventionsBuilder _conventionsBuilder;
29+
2930
/// <summary>
30-
/// Initializes a new instance of the <see cref="ModelConfigurationBuilder" />.
31+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
32+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
33+
/// any release. You should only use it directly in your code with extreme caution and knowing that
34+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
3135
/// </summary>
32-
/// <remarks>
33-
/// See <see href="https://aka.ms/efcore-docs-pre-convention">Pre-convention model building in EF Core</see> for more information and
34-
/// examples.
35-
/// </remarks>
36-
/// <param name="conventions">The conventions to be applied during model building.</param>
37-
public ModelConfigurationBuilder(ConventionSet conventions)
36+
[EntityFrameworkInternal]
37+
public ModelConfigurationBuilder(ConventionSet conventions, IServiceProvider serviceProvider)
3838
{
3939
Check.NotNull(conventions, nameof(conventions));
4040

4141
_conventions = conventions;
42+
_conventionsBuilder = new ConventionsBuilder(conventions, serviceProvider);
4243
}
4344

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

55+
/// <summary>
56+
/// Gets the builder for the conventions that will be used in the model.
57+
/// </summary>
58+
public virtual ConventionsBuilder Conventions
59+
=> _conventionsBuilder;
60+
5461
/// <summary>
5562
/// Prevents the conventions from the given type from discovering properties of the given or derived types.
5663
/// </summary>

test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3432,7 +3432,7 @@ protected virtual TestHelpers.TestModelBuilder CreateModelBuilderWithoutConventi
34323432
=> TestHelpers.CreateConventionBuilder(
34333433
CreateModelLogger(sensitiveDataLoggingEnabled), CreateValidationLogger(sensitiveDataLoggingEnabled),
34343434
modelConfigurationBuilder => ConventionSet.Remove(
3435-
modelConfigurationBuilder.Conventions.ModelFinalizingConventions,
3435+
modelConfigurationBuilder.ConventionSet.ModelFinalizingConventions,
34363436
typeof(T)));
34373437

34383438
protected override TestHelpers TestHelpers

test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
5-
using Microsoft.EntityFrameworkCore.Design.Internal;
6-
using Microsoft.EntityFrameworkCore.Internal;
75
using Microsoft.EntityFrameworkCore.Metadata.Internal;
86
using Microsoft.Extensions.DependencyInjection.Extensions;
97

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

154152
var modelConfigurationBuilder = new TestModelConfigurationBuilder(
155-
modelCreationDependencies.ConventionSetBuilder.CreateConventionSet());
153+
modelCreationDependencies.ConventionSetBuilder.CreateConventionSet(),
154+
contextServices);
156155

157156
configure?.Invoke(modelConfigurationBuilder);
158157

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

413412
public class TestModelConfigurationBuilder : ModelConfigurationBuilder
414413
{
415-
public TestModelConfigurationBuilder(ConventionSet conventions)
416-
: base(conventions)
414+
public TestModelConfigurationBuilder(ConventionSet conventionSet, IServiceProvider serviceProvider)
415+
: base(conventionSet, serviceProvider)
417416
{
418-
Conventions = conventions;
417+
ConventionSet = conventionSet;
419418
}
420419

421-
public ConventionSet Conventions { get; }
420+
public ConventionSet ConventionSet { get; }
422421

423422
public TestModelBuilder CreateModelBuilder(
424423
ModelDependencies modelDependencies,
425424
IModelRuntimeInitializer modelRuntimeInitializer,
426425
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> validationLogger)
427426
=> new(
428-
Conventions,
427+
ConventionSet,
429428
modelDependencies,
430429
ModelConfiguration.IsEmpty() ? null : ModelConfiguration.Validate(),
431430
modelRuntimeInitializer,
432431
validationLogger);
433432

434433
public void RemoveAllConventions()
435434
{
436-
Conventions.EntityTypeAddedConventions.Clear();
437-
Conventions.EntityTypeAnnotationChangedConventions.Clear();
438-
Conventions.EntityTypeBaseTypeChangedConventions.Clear();
439-
Conventions.EntityTypeIgnoredConventions.Clear();
440-
Conventions.EntityTypeMemberIgnoredConventions.Clear();
441-
Conventions.EntityTypePrimaryKeyChangedConventions.Clear();
442-
Conventions.EntityTypeRemovedConventions.Clear();
443-
Conventions.ForeignKeyAddedConventions.Clear();
444-
Conventions.ForeignKeyAnnotationChangedConventions.Clear();
445-
Conventions.ForeignKeyDependentRequirednessChangedConventions.Clear();
446-
Conventions.ForeignKeyOwnershipChangedConventions.Clear();
447-
Conventions.ForeignKeyPrincipalEndChangedConventions.Clear();
448-
Conventions.ForeignKeyPropertiesChangedConventions.Clear();
449-
Conventions.ForeignKeyRemovedConventions.Clear();
450-
Conventions.ForeignKeyRequirednessChangedConventions.Clear();
451-
Conventions.ForeignKeyUniquenessChangedConventions.Clear();
452-
Conventions.IndexAddedConventions.Clear();
453-
Conventions.IndexAnnotationChangedConventions.Clear();
454-
Conventions.IndexRemovedConventions.Clear();
455-
Conventions.IndexUniquenessChangedConventions.Clear();
456-
Conventions.IndexSortOrderChangedConventions.Clear();
457-
Conventions.KeyAddedConventions.Clear();
458-
Conventions.KeyAnnotationChangedConventions.Clear();
459-
Conventions.KeyRemovedConventions.Clear();
460-
Conventions.ModelAnnotationChangedConventions.Clear();
461-
Conventions.ModelFinalizedConventions.Clear();
462-
Conventions.ModelFinalizingConventions.Clear();
463-
Conventions.ModelInitializedConventions.Clear();
464-
Conventions.NavigationAddedConventions.Clear();
465-
Conventions.NavigationAnnotationChangedConventions.Clear();
466-
Conventions.NavigationRemovedConventions.Clear();
467-
Conventions.PropertyAddedConventions.Clear();
468-
Conventions.PropertyAnnotationChangedConventions.Clear();
469-
Conventions.PropertyFieldChangedConventions.Clear();
470-
Conventions.PropertyNullabilityChangedConventions.Clear();
471-
Conventions.PropertyRemovedConventions.Clear();
472-
Conventions.SkipNavigationAddedConventions.Clear();
473-
Conventions.SkipNavigationAnnotationChangedConventions.Clear();
474-
Conventions.SkipNavigationForeignKeyChangedConventions.Clear();
475-
Conventions.SkipNavigationInverseChangedConventions.Clear();
476-
Conventions.SkipNavigationRemovedConventions.Clear();
435+
ConventionSet.EntityTypeAddedConventions.Clear();
436+
ConventionSet.EntityTypeAnnotationChangedConventions.Clear();
437+
ConventionSet.EntityTypeBaseTypeChangedConventions.Clear();
438+
ConventionSet.EntityTypeIgnoredConventions.Clear();
439+
ConventionSet.EntityTypeMemberIgnoredConventions.Clear();
440+
ConventionSet.EntityTypePrimaryKeyChangedConventions.Clear();
441+
ConventionSet.EntityTypeRemovedConventions.Clear();
442+
ConventionSet.ForeignKeyAddedConventions.Clear();
443+
ConventionSet.ForeignKeyAnnotationChangedConventions.Clear();
444+
ConventionSet.ForeignKeyDependentRequirednessChangedConventions.Clear();
445+
ConventionSet.ForeignKeyOwnershipChangedConventions.Clear();
446+
ConventionSet.ForeignKeyPrincipalEndChangedConventions.Clear();
447+
ConventionSet.ForeignKeyPropertiesChangedConventions.Clear();
448+
ConventionSet.ForeignKeyRemovedConventions.Clear();
449+
ConventionSet.ForeignKeyRequirednessChangedConventions.Clear();
450+
ConventionSet.ForeignKeyUniquenessChangedConventions.Clear();
451+
ConventionSet.IndexAddedConventions.Clear();
452+
ConventionSet.IndexAnnotationChangedConventions.Clear();
453+
ConventionSet.IndexRemovedConventions.Clear();
454+
ConventionSet.IndexUniquenessChangedConventions.Clear();
455+
ConventionSet.IndexSortOrderChangedConventions.Clear();
456+
ConventionSet.KeyAddedConventions.Clear();
457+
ConventionSet.KeyAnnotationChangedConventions.Clear();
458+
ConventionSet.KeyRemovedConventions.Clear();
459+
ConventionSet.ModelAnnotationChangedConventions.Clear();
460+
ConventionSet.ModelFinalizedConventions.Clear();
461+
ConventionSet.ModelFinalizingConventions.Clear();
462+
ConventionSet.ModelInitializedConventions.Clear();
463+
ConventionSet.NavigationAddedConventions.Clear();
464+
ConventionSet.NavigationAnnotationChangedConventions.Clear();
465+
ConventionSet.NavigationRemovedConventions.Clear();
466+
ConventionSet.PropertyAddedConventions.Clear();
467+
ConventionSet.PropertyAnnotationChangedConventions.Clear();
468+
ConventionSet.PropertyFieldChangedConventions.Clear();
469+
ConventionSet.PropertyNullabilityChangedConventions.Clear();
470+
ConventionSet.PropertyRemovedConventions.Clear();
471+
ConventionSet.SkipNavigationAddedConventions.Clear();
472+
ConventionSet.SkipNavigationAnnotationChangedConventions.Clear();
473+
ConventionSet.SkipNavigationForeignKeyChangedConventions.Clear();
474+
ConventionSet.SkipNavigationInverseChangedConventions.Clear();
475+
ConventionSet.SkipNavigationRemovedConventions.Clear();
477476
}
478477
}
479478
}

test/EFCore.Specification.Tests/TestUtilities/TestModelSource.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ protected override IModel CreateModel(
2323
IConventionSetBuilder conventionSetBuilder,
2424
ModelDependencies modelDependencies)
2525
{
26-
var modelConfigurationBuilder = new ModelConfigurationBuilder(conventionSetBuilder.CreateConventionSet());
26+
var modelConfigurationBuilder = new ModelConfigurationBuilder(
27+
conventionSetBuilder.CreateConventionSet(),
28+
context.GetInfrastructure());
2729
_configureConventions?.Invoke(modelConfigurationBuilder);
2830
var modelBuilder = modelConfigurationBuilder.CreateModelBuilder(modelDependencies);
2931

test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,67 @@ public virtual void Properties_can_be_ignored_by_type()
404404
Assert.Null(entityType.FindProperty(nameof(Customer.AlternateKey)));
405405
}
406406

407+
[ConditionalFact]
408+
public virtual void Conventions_can_be_added()
409+
{
410+
var modelBuilder = CreateModelBuilder(c => c.Conventions.Add(s => new TestConvention()));
411+
412+
var model = modelBuilder.FinalizeModel();
413+
414+
Assert.Equal("bar", model["foo"]);
415+
}
416+
417+
[ConditionalFact]
418+
public virtual void Conventions_can_be_removed()
419+
{
420+
var modelBuilder = CreateModelBuilder(c =>
421+
{
422+
c.Conventions.Add(s => new TestConvention());
423+
c.Conventions.Remove(typeof(TestConvention));
424+
});
425+
426+
var model = modelBuilder.FinalizeModel();
427+
428+
Assert.Null(model["foo"]);
429+
}
430+
431+
[ConditionalFact]
432+
public virtual void Conventions_can_be_replaced()
433+
{
434+
var modelBuilder = CreateModelBuilder(c =>
435+
c.Conventions.Replace<DbSetFindingConvention>(s =>
436+
new TestDbSetFindingConvention(s.GetService<ProviderConventionSetBuilderDependencies>())));
437+
438+
var model = modelBuilder.FinalizeModel();
439+
440+
Assert.Equal("bar", model["foo"]);
441+
}
442+
443+
protected class TestConvention : IModelInitializedConvention
444+
{
445+
public void ProcessModelInitialized(
446+
IConventionModelBuilder modelBuilder,
447+
IConventionContext<IConventionModelBuilder> context)
448+
{
449+
modelBuilder.HasAnnotation("foo", "bar");
450+
}
451+
}
452+
453+
protected class TestDbSetFindingConvention : DbSetFindingConvention
454+
{
455+
public TestDbSetFindingConvention(ProviderConventionSetBuilderDependencies dependencies)
456+
: base(dependencies)
457+
{
458+
}
459+
460+
public override void ProcessModelInitialized(
461+
IConventionModelBuilder modelBuilder,
462+
IConventionContext<IConventionModelBuilder> context)
463+
{
464+
modelBuilder.HasAnnotation("foo", "bar");
465+
}
466+
}
467+
407468
[ConditionalFact]
408469
public virtual void Int32_cannot_be_ignored()
409470
=> Assert.Equal(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy