diff --git a/src/EFCore/Metadata/Internal/ModelConfiguration.cs b/src/EFCore/Metadata/Internal/ModelConfiguration.cs
index 879dd0eca3b..f587f35895b 100644
--- a/src/EFCore/Metadata/Internal/ModelConfiguration.cs
+++ b/src/EFCore/Metadata/Internal/ModelConfiguration.cs
@@ -41,6 +41,56 @@ public ModelConfiguration()
public virtual bool IsEmpty()
=> _properties.Count == 0 && _ignoredTypes.Count == 0 && _typeMappings.Count == 0;
+ ///
+ /// 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.
+ ///
+ public virtual ModelConfiguration Validate()
+ {
+ Type? configuredType = null;
+ var stringType = GetConfigurationType(typeof(string), null, ref configuredType);
+ if (stringType != null
+ && stringType != TypeConfigurationType.Property)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.UnconfigurableType(
+ typeof(string).DisplayName(fullName: false),
+ stringType,
+ TypeConfigurationType.Property,
+ configuredType!.DisplayName(fullName: false)));
+ }
+
+ configuredType = null;
+ var intType = GetConfigurationType(typeof(int?), null, ref configuredType);
+ if (intType != null
+ && intType != TypeConfigurationType.Property)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.UnconfigurableType(
+ typeof(int?).DisplayName(fullName: false),
+ intType,
+ TypeConfigurationType.Property,
+ configuredType!.DisplayName(fullName: false)));
+ }
+
+ configuredType = null;
+ var propertyBagType = GetConfigurationType(Model.DefaultPropertyBagType, null, ref configuredType);
+ if (propertyBagType != null
+ && !propertyBagType.Value.IsEntityType())
+ {
+ throw new InvalidOperationException(
+ CoreStrings.UnconfigurableType(
+ Model.DefaultPropertyBagType.DisplayName(fullName: false),
+ propertyBagType,
+ TypeConfigurationType.SharedTypeEntityType,
+ configuredType!.DisplayName(fullName: false)));
+ }
+
+ return this;
+ }
+
///
/// 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
@@ -69,6 +119,12 @@ public virtual bool IsEmpty()
Type? configuredType = null;
+ if (type.IsNullableValueType())
+ {
+ configurationType = GetConfigurationType(
+ Nullable.GetUnderlyingType(type)!, configurationType, ref configuredType, getBaseTypes: false);
+ }
+
if (type.IsConstructedGenericType)
{
configurationType = GetConfigurationType(
@@ -178,23 +234,6 @@ public virtual PropertyConfiguration GetOrAddProperty(Type type)
var property = FindProperty(type);
if (property == null)
{
- if (type == typeof(object)
- || type == typeof(ExpandoObject)
- || type == typeof(SortedDictionary)
- || type == typeof(Dictionary)
- || type == typeof(IDictionary)
- || type == typeof(IReadOnlyDictionary)
- || type == typeof(IDictionary)
- || type == typeof(ICollection>)
- || type == typeof(IReadOnlyCollection>)
- || type == typeof(ICollection)
- || type == typeof(IEnumerable>)
- || type == typeof(IEnumerable))
- {
- throw new InvalidOperationException(
- CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), TypeConfigurationType.Property));
- }
-
RemoveIgnored(type);
property = new PropertyConfiguration(type);
@@ -232,8 +271,8 @@ public virtual bool RemoveProperty(Type type)
///
public virtual PropertyConfiguration GetOrAddTypeMapping(Type type)
{
- var scalar = FindTypeMapping(type);
- if (scalar == null)
+ var typeMappingConfiguration = FindTypeMapping(type);
+ if (typeMappingConfiguration == null)
{
if (type == typeof(object)
|| type == typeof(ExpandoObject)
@@ -243,14 +282,14 @@ public virtual PropertyConfiguration GetOrAddTypeMapping(Type type)
|| !type.IsInstantiable())
{
throw new InvalidOperationException(
- CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), "DefaultTypeMapping"));
+ CoreStrings.UnconfigurableTypeMapping(type.DisplayName(fullName: false)));
}
- scalar = new PropertyConfiguration(type);
- _typeMappings.Add(type, scalar);
+ typeMappingConfiguration = new PropertyConfiguration(type);
+ _typeMappings.Add(type, typeMappingConfiguration);
}
- return scalar;
+ return typeMappingConfiguration;
}
///
@@ -272,25 +311,6 @@ public virtual PropertyConfiguration GetOrAddTypeMapping(Type type)
///
public virtual void AddIgnored(Type type)
{
- if (type.UnwrapNullableType() == typeof(int)
- || type == typeof(string)
- || type == typeof(object)
- || type == typeof(ExpandoObject)
- || type == typeof(SortedDictionary)
- || type == typeof(Dictionary)
- || type == typeof(IDictionary)
- || type == typeof(IReadOnlyDictionary)
- || type == typeof(IDictionary)
- || type == typeof(ICollection>)
- || type == typeof(IReadOnlyCollection>)
- || type == typeof(ICollection)
- || type == typeof(IEnumerable>)
- || type == typeof(IEnumerable))
- {
- throw new InvalidOperationException(
- CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), TypeConfigurationType.Ignored));
- }
-
RemoveProperty(type);
_ignoredTypes.Add(type);
}
diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs
index 95572e5ce29..40c468d2099 100644
--- a/src/EFCore/ModelConfigurationBuilder.cs
+++ b/src/EFCore/ModelConfigurationBuilder.cs
@@ -318,7 +318,7 @@ public virtual ModelConfigurationBuilder DefaultTypeMapping(
/// The dependencies object used during model building.
/// The configured .
public virtual ModelBuilder CreateModelBuilder(ModelDependencies? modelDependencies)
- => new(_conventions, modelDependencies, _modelConfiguration.IsEmpty() ? null : _modelConfiguration);
+ => new(_conventions, modelDependencies, _modelConfiguration.IsEmpty() ? null : _modelConfiguration.Validate());
#region Hidden System.Object members
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index f74efdb1b76..24e63224664 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -1,7 +1,9 @@
//
using System;
+using System.Reflection;
using System.Resources;
+using System.Threading;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
@@ -2770,12 +2772,20 @@ public static string UnableToSetIsUnique(object? isUnique, object? navigationNam
isUnique, navigationName, entityType);
///
- /// The type '{type}' cannot be configured as '{configuration}'. The current model building logic is unable to honor this configuration.
+ /// The type '{type}' cannot be configured as '{configuration}' since model building assumes that it is configured as '{expectedConfiguration}'. Remove the unsupported configuration for '{configurationType}'.
///
- public static string UnconfigurableType(object? type, object? configuration)
+ public static string UnconfigurableType(object? type, object? configuration, object? expectedConfiguration, object? configurationType)
=> string.Format(
- GetString("UnconfigurableType", nameof(type), nameof(configuration)),
- type, configuration);
+ GetString("UnconfigurableType", nameof(type), nameof(configuration), nameof(expectedConfiguration), nameof(configurationType)),
+ type, configuration, expectedConfiguration, configurationType);
+
+ ///
+ /// Default type mapping cannot be configured for the type '{type}' since it's not a valid scalar type. Remove the unsupported configuration.
+ ///
+ public static string UnconfigurableTypeMapping(object? type)
+ => string.Format(
+ GetString("UnconfigurableTypeMapping", nameof(type)),
+ type);
///
/// Unhandled expression node type '{nodeType}'.
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 4511a3a4dc0..3bf20ef9fbd 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1501,7 +1501,10 @@
Unable to set 'IsUnique' to '{isUnique}' on the relationship associated with the navigation '{2_entityType}.{1_navigationName}' because the navigation has the opposite multiplicity.
- The type '{type}' cannot be configured as '{configuration}'. The current model building logic is unable to honor this configuration.
+ The type '{type}' cannot be configured as '{configuration}' since model building assumes that it is configured as '{expectedConfiguration}'. Remove the unsupported configuration for '{configurationType}'.
+
+
+ Default type mapping cannot be configured for the type '{type}' since it's not a valid scalar type. Remove the unsupported configuration.
Unhandled expression node type '{nodeType}'.
diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs
index 5fbb9058378..2a35a50f7ee 100644
--- a/src/Shared/SharedTypeExtensions.cs
+++ b/src/Shared/SharedTypeExtensions.cs
@@ -41,7 +41,7 @@ public static Type UnwrapNullableType(this Type type)
=> Nullable.GetUnderlyingType(type) ?? type;
public static bool IsNullableValueType(this Type type)
- => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ => type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
public static bool IsNullableType(this Type type)
=> !type.IsValueType || type.IsNullableValueType();
@@ -312,6 +312,11 @@ public static List GetBaseTypesAndInterfacesInclusive(this Type type)
type = typesToProcess.Dequeue();
baseTypes.Add(type);
+ if (type.IsNullableValueType())
+ {
+ typesToProcess.Enqueue(Nullable.GetUnderlyingType(type)!);
+ }
+
if (type.IsConstructedGenericType)
{
typesToProcess.Enqueue(type.GetGenericTypeDefinition());
diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs
index 4401f2c0b1d..401f883c9d5 100644
--- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs
+++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs
@@ -378,7 +378,7 @@ public TestModelBuilder CreateModelBuilder(
IDiagnosticsLogger validationLogger)
=> new(Conventions,
modelDependencies,
- ModelConfiguration.IsEmpty() ? null : ModelConfiguration,
+ ModelConfiguration.IsEmpty() ? null : ModelConfiguration.Validate(),
modelRuntimeInitializer,
validationLogger);
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
index 80f717586d3..947dd045a2a 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
@@ -84,6 +84,7 @@ public virtual void Can_set_store_type_for_property_type()
{
c.Properties().HaveColumnType("smallint");
c.Properties().HaveColumnType("nchar(max)");
+ c.Properties(typeof(Nullable<>)).HavePrecision(2);
});
modelBuilder.Entity(
@@ -91,7 +92,7 @@ public virtual void Can_set_store_type_for_property_type()
{
b.Property("Charm");
b.Property("Strange");
- b.Property("Top");
+ b.Property("Top");
b.Property("Bottom");
});
@@ -101,9 +102,13 @@ public virtual void Can_set_store_type_for_property_type()
Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name).GetColumnType());
Assert.Equal("smallint", entityType.FindProperty("Up").GetColumnType());
Assert.Equal("nchar(max)", entityType.FindProperty("Down").GetColumnType());
- Assert.Equal("smallint", entityType.FindProperty("Charm").GetColumnType());
+ var charm = entityType.FindProperty("Charm");
+ Assert.Equal("smallint", charm.GetColumnType());
+ Assert.Null(charm.GetPrecision());
Assert.Equal("nchar(max)", entityType.FindProperty("Strange").GetColumnType());
- Assert.Equal("smallint", entityType.FindProperty("Top").GetColumnType());
+ var top = entityType.FindProperty("Top");
+ Assert.Equal("smallint", top.GetColumnType());
+ Assert.Equal(2, top.GetPrecision());
Assert.Equal("nchar(max)", entityType.FindProperty("Bottom").GetColumnType());
}
diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs
index 6090006075e..57942f18502 100644
--- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs
+++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs
@@ -419,14 +419,14 @@ public virtual void Properties_can_be_ignored_by_type()
[ConditionalFact]
public virtual void Int32_cannot_be_ignored()
{
- Assert.Equal(CoreStrings.UnconfigurableType("int", "Ignored"),
+ Assert.Equal(CoreStrings.UnconfigurableType("int?", "Ignored", "Property", "int"),
Assert.Throws(() => CreateModelBuilder(c => c.IgnoreAny())).Message);
}
[ConditionalFact]
public virtual void Object_cannot_be_ignored()
{
- Assert.Equal(CoreStrings.UnconfigurableType("object", "Ignored"),
+ Assert.Equal(CoreStrings.UnconfigurableType("string", "Ignored", "Property", "object"),
Assert.Throws(() => CreateModelBuilder(c => c.IgnoreAny