Skip to content

Commit

Permalink
Add pre-convention model configuration.
Browse files Browse the repository at this point in the history
Allow to Ignore entity types and properties by base type or interface
Allow to specify a value conversion and other facets for all properties of a given type

Fixes #12229
Fixes #3867
Fixes #10784
  • Loading branch information
AndriySvyryd committed May 27, 2021
1 parent 6449203 commit 6f0602a
Show file tree
Hide file tree
Showing 76 changed files with 2,570 additions and 1,218 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
6 changes: 4 additions & 2 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,10 @@ protected virtual void ValidatePropertyMapping(
var targetSequenceType = propertyType.TryGetSequenceType();

if (conventionModel.FindIgnoredConfigurationSource(propertyType) != null
|| targetSequenceType != null
&& conventionModel.FindIgnoredConfigurationSource(targetSequenceType) != null)
|| conventionModel.IsIgnoredType(propertyType)
|| (targetSequenceType != null
&& (conventionModel.FindIgnoredConfigurationSource(targetSequenceType) != null
|| conventionModel.IsIgnoredType(targetSequenceType))))
{
continue;
}
Expand Down
62 changes: 62 additions & 0 deletions src/EFCore/Internal/PropertyListComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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;
using System.Collections.Generic;

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>
// Sealed for perf
public sealed class TypeComparer : IComparer<Type>
{
/// <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 static readonly TypeComparer Instance = new();

private TypeComparer()
{
}

/// <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 int Compare(Type? x, Type? y)
{
if (ReferenceEquals(x, y))
{
return 0;
}

if (x is null)
{
return -1;
}

if (y is null)
{
return 1;
}

var result = StringComparer.Ordinal.Compare(x.FullName, y.FullName);
if (result != 0)
{
return result;
}

return StringComparer.Ordinal.Compare(x.AssemblyQualifiedName, y.AssemblyQualifiedName);
}
}
}
48 changes: 47 additions & 1 deletion src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ IConventionEntityTypeBuilder RemoveUnusedShadowProperties(
MemberInfo memberInfo,
bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given service property can be added to this entity type.
/// </summary>
/// <param name="memberInfo"> The <see cref="PropertyInfo" /> or <see cref="FieldInfo" /> of the property. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the service property can be added. </returns>
bool CanHaveServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation = false);

/// <summary>
/// Indicates whether the given member name is ignored for the given configuration source.
/// </summary>
Expand Down Expand Up @@ -624,16 +632,54 @@ bool CanAddNavigation(string navigationName, bool fromDataAnnotation = false)
/// <param name="navigationName"> The name of the navigation. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the configuration can be applied. </returns>
[Obsolete("Use CanHaveNavigation with Type parameter")]
bool CanHaveNavigation(string navigationName, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given navigation can be added to this entity type.
/// </summary>
/// <param name="navigationName"> The name of the navigation. </param>
/// <param name="type"> The type of the navigation target. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the skip navigation can be added. </returns>
bool CanHaveNavigation(string navigationName, Type? type, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given navigation can be added to this entity type.
/// </summary>
/// <param name="navigation"> The navigation member. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the configuration can be applied. </returns>
bool CanHaveNavigation(MemberInfo navigation, bool fromDataAnnotation = false)
=> CanHaveNavigation(navigation.Name, navigation.GetMemberType(), fromDataAnnotation);

/// <summary>
/// Returns a value indicating whether the given skip navigation can be added to this entity type.
/// </summary>
/// <param name="skipNavigationName"> The name of the skip navigation. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the configuration can be applied. </returns>
/// <returns> <see langword="true" /> if the skip navigation can be added. </returns>
[Obsolete("Use CanHaveNavigation with Type parameter")]
bool CanHaveSkipNavigation(string skipNavigationName, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given skip navigation can be added to this entity type.
/// </summary>
/// <param name="skipNavigationName"> The name of the skip navigation. </param>
/// <param name="type"> The type of the navigation target. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the skip navigation can be added. </returns>
bool CanHaveSkipNavigation(string skipNavigationName, Type? type, bool fromDataAnnotation = false);

/// <summary>
/// Returns a value indicating whether the given skip navigation can be added to this entity type.
/// </summary>
/// <param name="navigation"> The navigation member. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the skip navigation can be added. </returns>
bool CanHaveSkipNavigation(MemberInfo navigation, bool fromDataAnnotation = false)
=> CanHaveSkipNavigation(navigation.Name, navigation.GetMemberType(), fromDataAnnotation);

/// <summary>
/// Configures a skip navigation and the inverse between this and the target entity type.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Metadata/Builders/IConventionModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder
/// <returns> <see langword="true" /> if the given entity type name is ignored. </returns>
bool IsIgnored(string typeName, bool fromDataAnnotation = false);

/// <summary>
/// Indicates whether entity types and properties with the given type should be ignored for the current configuration source.
/// </summary>
/// <param name="type"> The name of the entity type that might be ignored. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> <see langword="true" /> if the given entity type name is ignored. </returns>
bool IsIgnoredType(Type type, bool fromDataAnnotation = false);

/// <summary>
/// Excludes the given entity type from the model and prevents it from being added by convention.
/// </summary>
Expand Down
Loading

0 comments on commit 6f0602a

Please sign in to comment.