diff --git a/src/EFCore.Relational/Extensions/PropertiesConfigurationBuilderExtensions.cs b/src/EFCore.Relational/Extensions/PropertiesConfigurationBuilderExtensions.cs
new file mode 100644
index 00000000000..49223aab30a
--- /dev/null
+++ b/src/EFCore.Relational/Extensions/PropertiesConfigurationBuilderExtensions.cs
@@ -0,0 +1,106 @@
+// 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 Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore
+{
+ ///
+ /// Relational database specific extension methods for .
+ ///
+ public static class PropertiesConfigurationBuilderExtensions
+ {
+ ///
+ /// Configures the data type of the column that the property maps to when targeting a relational database.
+ /// This should be the complete type name, including precision, scale, length, etc.
+ ///
+ /// The builder for the property being configured.
+ /// The name of the data type of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PropertiesConfigurationBuilder HaveColumnType(
+ this PropertiesConfigurationBuilder propertyBuilder,
+ string typeName)
+ {
+ Check.NotNull(propertyBuilder, nameof(propertyBuilder));
+ Check.NotEmpty(typeName, nameof(typeName));
+
+ propertyBuilder.HaveAnnotation(RelationalAnnotationNames.ColumnType, typeName);
+
+ return propertyBuilder;
+ }
+
+ ///
+ /// Configures the data type of the column that the property maps to when targeting a relational database.
+ /// This should be the complete type name, including precision, scale, length, etc.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The name of the data type of the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PropertiesConfigurationBuilder HaveColumnType(
+ this PropertiesConfigurationBuilder propertyBuilder,
+ string typeName)
+ => (PropertiesConfigurationBuilder)HaveColumnType((PropertiesConfigurationBuilder)propertyBuilder, typeName);
+
+ ///
+ /// Configures the property as capable of storing only fixed-length data, such as strings.
+ ///
+ /// The builder for the property being configured.
+ /// A value indicating whether the property is constrained to fixed length values.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public static PropertiesConfigurationBuilder AreFixedLength(
+ this PropertiesConfigurationBuilder propertyBuilder,
+ bool fixedLength = true)
+ {
+ Check.NotNull(propertyBuilder, nameof(propertyBuilder));
+
+ propertyBuilder.HaveAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength);
+
+ return propertyBuilder;
+ }
+
+ ///
+ /// Configures the property as capable of storing only fixed-length data, such as strings.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// A value indicating whether the property is constrained to fixed length values.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public static PropertiesConfigurationBuilder AreFixedLength(
+ this PropertiesConfigurationBuilder propertyBuilder,
+ bool fixedLength = true)
+ => (PropertiesConfigurationBuilder)AreFixedLength((PropertiesConfigurationBuilder)propertyBuilder, fixedLength);
+
+ ///
+ /// Configures the property to use the given collation. The database column will be created with the given
+ /// collation, and it will be used implicitly in all collation-sensitive operations.
+ ///
+ /// The builder for the property being configured.
+ /// The collation for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PropertiesConfigurationBuilder UseCollation(this PropertiesConfigurationBuilder propertyBuilder, string collation)
+ {
+ Check.NotNull(propertyBuilder, nameof(propertyBuilder));
+ Check.NotEmpty(collation, nameof(collation));
+
+ propertyBuilder.HaveAnnotation(RelationalAnnotationNames.Collation, collation);
+
+ return propertyBuilder;
+ }
+
+ ///
+ /// Configures the property to use the given collation. The database column will be created with the given
+ /// collation, and it will be used implicitly in all collation-sensitive operations.
+ ///
+ /// The builder for the property being configured.
+ /// The collation for the column.
+ /// The same builder instance so that multiple calls can be chained.
+ public static PropertiesConfigurationBuilder UseCollation(
+ this PropertiesConfigurationBuilder propertyBuilder,
+ string collation)
+ => (PropertiesConfigurationBuilder)UseCollation((PropertiesConfigurationBuilder)propertyBuilder, collation);
+ }
+}
diff --git a/src/EFCore/Infrastructure/Internal/MemberInfoNameComparer.cs b/src/EFCore/Infrastructure/Internal/MemberInfoNameComparer.cs
new file mode 100644
index 00000000000..7ff9c6301df
--- /dev/null
+++ b/src/EFCore/Infrastructure/Internal/MemberInfoNameComparer.cs
@@ -0,0 +1,57 @@
+// 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;
+using System.Reflection;
+
+namespace Microsoft.EntityFrameworkCore.Infrastructure.Internal
+{
+ ///
+ /// 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.
+ ///
+ // Sealed for perf
+ public sealed class MemberInfoNameComparer : IComparer
+ {
+ ///
+ /// 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 static readonly MemberInfoNameComparer Instance = new();
+
+ private MemberInfoNameComparer()
+ {
+ }
+
+ ///
+ /// 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 int Compare(MemberInfo? x, MemberInfo? y)
+ {
+ if (ReferenceEquals(x, y))
+ {
+ return 0;
+ }
+
+ if (x is null)
+ {
+ return -1;
+ }
+
+ if (y is null)
+ {
+ return 1;
+ }
+
+ return StringComparer.Ordinal.Compare(x.Name, y.Name);
+ }
+ }
+}
diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs
index f1da4b2ae74..8324b755e4e 100644
--- a/src/EFCore/Infrastructure/ModelValidator.cs
+++ b/src/EFCore/Infrastructure/ModelValidator.cs
@@ -158,7 +158,8 @@ protected virtual void ValidatePropertyMapping(
{
throw new InvalidOperationException(
CoreStrings.PropertyNotMapped(
- entityType.DisplayName(), unmappedProperty.Name, unmappedProperty.ClrType.ShortDisplayName()));
+ entityType.DisplayName(), unmappedProperty.Name,
+ (unmappedProperty.GetValueConverter()?.ProviderClrType ?? unmappedProperty.ClrType).ShortDisplayName()));
}
if (entityType.ClrType == Model.DefaultPropertyBagType)
@@ -175,45 +176,52 @@ protected virtual void ValidatePropertyMapping(
.Where(pi => pi.IsCandidateProperty(needsWrite: false))
.Select(pi => pi.GetSimpleMemberName()));
- clrProperties.ExceptWith(entityType.GetProperties().Select(p => p.Name));
- clrProperties.ExceptWith(entityType.GetNavigations().Select(p => p.Name));
- clrProperties.ExceptWith(entityType.GetSkipNavigations().Select(p => p.Name));
- clrProperties.ExceptWith(entityType.GetServiceProperties().Select(p => p.Name));
+ clrProperties.ExceptWith(
+ ((IEnumerable)entityType.GetProperties())
+ .Concat(entityType.GetNavigations())
+ .Concat(entityType.GetSkipNavigations())
+ .Concat(entityType.GetServiceProperties()).Select(p => p.Name));
if (entityType.IsPropertyBag)
{
clrProperties.ExceptWith(_dictionaryProperties);
}
- clrProperties.RemoveWhere(p => entityType.FindIgnoredConfigurationSource(p) != null);
-
if (clrProperties.Count <= 0)
{
continue;
}
- foreach (var clrProperty in clrProperties)
+ var configuration = ((Model)entityType.Model).Configuration;
+ foreach (var clrPropertyName in clrProperties)
{
- var actualProperty = runtimeProperties[clrProperty];
- var propertyType = actualProperty.PropertyType;
+ if (entityType.FindIgnoredConfigurationSource(clrPropertyName) != null)
+ {
+ continue;
+ }
+
+ var clrProperty = runtimeProperties[clrPropertyName];
+ var propertyType = clrProperty.PropertyType;
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;
}
- var targetType = FindCandidateNavigationPropertyType(actualProperty);
+ Dependencies.MemberClassifier.GetNavigationCandidates(entityType).TryGetValue(clrProperty, out var targetType);
if (targetType == null
- || targetSequenceType == null)
+ || targetSequenceType == null)
{
- if (actualProperty.FindSetterProperty() == null)
+ if (clrProperty.FindSetterProperty() == null)
{
continue;
}
- var sharedType = actualProperty.GetMemberType();
+ var sharedType = clrProperty.GetMemberType();
if (conventionModel.IsShared(sharedType))
{
targetType = sharedType;
@@ -234,7 +242,7 @@ protected virtual void ValidatePropertyMapping(
if ((!entityType.IsKeyless
|| targetSequenceType == null)
&& entityType.GetDerivedTypes().All(
- dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == actualProperty.GetSimpleMemberName())
+ dt => dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName())
== null)
&& (!isTargetSharedOrOwned
|| (!targetType.Equals(entityType.ClrType)
@@ -247,18 +255,18 @@ protected virtual void ValidatePropertyMapping(
{
throw new InvalidOperationException(
CoreStrings.AmbiguousOwnedNavigation(
- entityType.DisplayName() + "." + actualProperty.Name, targetType.ShortDisplayName()));
+ entityType.DisplayName() + "." + clrProperty.Name, targetType.ShortDisplayName()));
}
if (model.IsShared(targetType))
{
throw new InvalidOperationException(
- CoreStrings.NonConfiguredNavigationToSharedType(actualProperty.Name, entityType.DisplayName()));
+ CoreStrings.NonConfiguredNavigationToSharedType(clrProperty.Name, entityType.DisplayName()));
}
throw new InvalidOperationException(
CoreStrings.NavigationNotAdded(
- entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName()));
+ entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}
// ReSharper restore CheckForReferenceEqualityInstead.3
@@ -269,21 +277,18 @@ protected virtual void ValidatePropertyMapping(
{
throw new InvalidOperationException(
CoreStrings.InterfacePropertyNotAdded(
- entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName()));
+ entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}
else
{
throw new InvalidOperationException(
CoreStrings.PropertyNotAdded(
- entityType.DisplayName(), actualProperty.Name, propertyType.ShortDisplayName()));
+ entityType.DisplayName(), clrProperty.Name, propertyType.ShortDisplayName()));
}
}
}
}
- private Type? FindCandidateNavigationPropertyType(PropertyInfo propertyInfo)
- => Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(propertyInfo);
-
///
/// Validates that no attempt is made to ignore inherited properties.
///
diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs
index 6ed57fcc2cd..cf13faf79f4 100644
--- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs
@@ -75,6 +75,54 @@ public interface IConventionEntityTypeBuilder : IConventionAnnotatableBuilder
///
IConventionPropertyBuilder? Property(MemberInfo memberInfo, bool fromDataAnnotation = false);
+ ///
+ /// Returns a value indicating whether the given property can be added to this entity type.
+ ///
+ /// The type of value the property will hold.
+ /// The name of the property to be configured.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the property can be added.
+ bool CanHaveProperty(
+ Type? propertyType,
+ string propertyName,
+ bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given property can be added to this entity type.
+ ///
+ /// The or of the property.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the property can be added.
+ bool CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns an object that can be used to configure the indexer property with the given name.
+ /// If no matching property exists, then a new property will be added.
+ ///
+ /// The type of value the property will hold.
+ /// The name of the property to be configured.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// An object that can be used to configure the property if it exists on the entity type,
+ /// otherwise.
+ ///
+ IConventionPropertyBuilder? IndexerProperty(
+ Type propertyType,
+ string propertyName,
+ bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given indexer property can be added to this entity type.
+ ///
+ /// The type of value the property will hold.
+ /// The name of the property to be configured.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the property can be added.
+ bool CanHaveIndexerProperty(
+ Type propertyType,
+ string propertyName,
+ bool fromDataAnnotation = false);
+
///
/// Creates a property with a name that's different from any existing properties.
///
@@ -138,6 +186,14 @@ IConventionEntityTypeBuilder RemoveUnusedShadowProperties(
MemberInfo memberInfo,
bool fromDataAnnotation = false);
+ ///
+ /// Returns a value indicating whether the given service property can be added to this entity type.
+ ///
+ /// The or of the property.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the service property can be added.
+ bool CanHaveServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation = false);
+
///
/// Indicates whether the given member name is ignored for the given configuration source.
///
@@ -624,16 +680,54 @@ bool CanAddNavigation(string navigationName, bool fromDataAnnotation = false)
/// The name of the navigation.
/// Indicates whether the configuration was specified using a data annotation.
/// if the configuration can be applied.
+ [Obsolete("Use CanHaveNavigation with Type parameter")]
bool CanHaveNavigation(string navigationName, bool fromDataAnnotation = false);
+ ///
+ /// Returns a value indicating whether the given navigation can be added to this entity type.
+ ///
+ /// The name of the navigation.
+ /// The type of the navigation target.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the skip navigation can be added.
+ bool CanHaveNavigation(string navigationName, Type? type, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given navigation can be added to this entity type.
+ ///
+ /// The navigation member.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the configuration can be applied.
+ bool CanHaveNavigation(MemberInfo navigation, bool fromDataAnnotation = false)
+ => CanHaveNavigation(navigation.Name, navigation.GetMemberType(), fromDataAnnotation);
+
///
/// Returns a value indicating whether the given skip navigation can be added to this entity type.
///
/// The name of the skip navigation.
/// Indicates whether the configuration was specified using a data annotation.
- /// if the configuration can be applied.
+ /// if the skip navigation can be added.
+ [Obsolete("Use CanHaveSkipNavigation with Type parameter")]
bool CanHaveSkipNavigation(string skipNavigationName, bool fromDataAnnotation = false);
+ ///
+ /// Returns a value indicating whether the given skip navigation can be added to this entity type.
+ ///
+ /// The name of the skip navigation.
+ /// The type of the navigation target.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the skip navigation can be added.
+ bool CanHaveSkipNavigation(string skipNavigationName, Type? type, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given skip navigation can be added to this entity type.
+ ///
+ /// The navigation member.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the skip navigation can be added.
+ bool CanHaveSkipNavigation(MemberInfo navigation, bool fromDataAnnotation = false)
+ => CanHaveSkipNavigation(navigation.Name, navigation.GetMemberType(), fromDataAnnotation);
+
///
/// Configures a skip navigation and the inverse between this and the target entity type.
///
diff --git a/src/EFCore/Metadata/Builders/PropertiesConfigurationBuilder.cs b/src/EFCore/Metadata/Builders/PropertiesConfigurationBuilder.cs
new file mode 100644
index 00000000000..0c730609abc
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/PropertiesConfigurationBuilder.cs
@@ -0,0 +1,201 @@
+// 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.ComponentModel;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders
+{
+ ///
+ ///
+ /// Provides a simple API surface for setting property defaults before conventions run.
+ ///
+ ///
+ /// Instances of this class are returned from methods when using the API
+ /// and it is not designed to be directly constructed in your application code.
+ ///
+ ///
+ public class PropertiesConfigurationBuilder
+ {
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public PropertiesConfigurationBuilder(PropertyConfiguration property)
+ {
+ Check.NotNull(property, nameof(property));
+
+ Property = property;
+ }
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ protected virtual PropertyConfiguration Property { get; }
+
+ ///
+ /// Adds or updates an annotation on the property.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HaveAnnotation(string annotation, object value)
+ {
+ Check.NotEmpty(annotation, nameof(annotation));
+
+ Property[annotation] = value;
+
+ return this;
+ }
+
+ ///
+ /// Configures the maximum length of data that can be stored in this property.
+ /// Maximum length can only be set on array properties (including properties).
+ ///
+ /// The maximum length of data allowed in the property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HaveMaxLength(int maxLength)
+ {
+ Property.SetMaxLength(maxLength);
+
+ return this;
+ }
+
+ ///
+ /// Configures the precision and scale of the property.
+ ///
+ /// The precision of the property.
+ /// The scale of the property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HavePrecision(int precision, int scale)
+ {
+ Property.SetPrecision(precision);
+ Property.SetScale(scale);
+
+ return this;
+ }
+
+ ///
+ ///
+ /// Configures the precision of the property.
+ ///
+ ///
+ /// The precision of the property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HavePrecision(int precision)
+ {
+ Property.SetPrecision(precision);
+
+ return this;
+ }
+
+ ///
+ /// Configures whether the property as capable of persisting unicode characters.
+ /// Can only be set on properties.
+ ///
+ /// A value indicating whether the property can contain unicode characters.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder AreUnicode(bool unicode = true)
+ {
+ Property.SetIsUnicode(unicode);
+
+ return this;
+ }
+
+ ///
+ /// Configures the property so that the property value is converted to the given type before
+ /// writing to the database and converted back when reading from the database.
+ ///
+ /// The type to convert to and from.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HaveConversion()
+ => HaveConversion(typeof(TProvider));
+
+ ///
+ /// Configures the property so that the property value is converted to the given type before
+ /// writing to the database and converted back when reading from the database.
+ ///
+ /// The type to convert to and from.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HaveConversion(Type providerClrType)
+ {
+ Check.NotNull(providerClrType, nameof(providerClrType));
+
+ Property.SetProviderClrType(providerClrType);
+
+ return this;
+ }
+
+ ///
+ /// Configures the property so that the property value is converted to and from the database
+ /// using the given .
+ ///
+ /// A type that derives from .
+ /// A type that derives from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HaveConversion()
+ where TConverter : ValueConverter
+ where TComparer : ValueComparer
+ => HaveConversion(typeof(TConverter), typeof(TComparer));
+
+ ///
+ /// Configures the property so that the property value is converted to and from the database
+ /// using the given .
+ ///
+ /// A type that derives from .
+ /// A type that derives from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public virtual PropertiesConfigurationBuilder HaveConversion(Type converterType, Type? comparerType)
+ {
+ Check.NotNull(converterType, nameof(converterType));
+
+ Property.SetValueConverter(converterType);
+ Property.SetValueComparer(comparerType);
+
+ return this;
+ }
+
+ #region Hidden System.Object members
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string? ToString()
+ => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// if the specified object is equal to the current object; otherwise, .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectEqualsIsObjectEquals
+ public override bool Equals(object? obj)
+ => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
+ public override int GetHashCode()
+ => base.GetHashCode();
+
+ #endregion
+ }
+}
diff --git a/src/EFCore/Metadata/Builders/PropertiesConfigurationBuilder`.cs b/src/EFCore/Metadata/Builders/PropertiesConfigurationBuilder`.cs
new file mode 100644
index 00000000000..9c448fa83a6
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/PropertiesConfigurationBuilder`.cs
@@ -0,0 +1,122 @@
+// 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 Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders
+{
+ ///
+ ///
+ /// Provides a simple API surface for setting property defaults before conventions run.
+ ///
+ ///
+ /// Instances of this class are returned from methods when using the API
+ /// and it is not designed to be directly constructed in your application code.
+ ///
+ ///
+ public class PropertiesConfigurationBuilder : PropertiesConfigurationBuilder
+ {
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public PropertiesConfigurationBuilder(PropertyConfiguration property)
+ : base(property)
+ {
+ }
+
+ ///
+ /// Adds or updates an annotation on the property. If an annotation with the key specified in
+ /// already exists its value will be updated.
+ ///
+ /// The key of the annotation to be added or updated.
+ /// The value to be stored in the annotation.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HaveAnnotation(string annotation, object value)
+ => (PropertiesConfigurationBuilder)base.HaveAnnotation(annotation, value);
+
+ ///
+ /// Configures the maximum length of data that can be stored in this property.
+ /// Maximum length can only be set on array properties (including properties).
+ ///
+ /// The maximum length of data allowed in the property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HaveMaxLength(int maxLength)
+ => (PropertiesConfigurationBuilder)base.HaveMaxLength(maxLength);
+
+ ///
+ /// Configures the precision and scale of the property.
+ ///
+ /// The precision of the property.
+ /// The scale of the property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HavePrecision(int precision, int scale)
+ => (PropertiesConfigurationBuilder)base.HavePrecision(precision, scale);
+
+ ///
+ ///
+ /// Configures the precision of the property.
+ ///
+ ///
+ /// The precision of the property.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HavePrecision(int precision)
+ => (PropertiesConfigurationBuilder)base.HavePrecision(precision);
+
+ ///
+ /// Configures the property as capable of persisting unicode characters.
+ /// Can only be set on properties.
+ ///
+ /// A value indicating whether the property can contain unicode characters.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder AreUnicode(bool unicode = true)
+ => (PropertiesConfigurationBuilder)base.AreUnicode(unicode);
+
+ ///
+ /// Configures the property so that the property value is converted to the given type before
+ /// writing to the database and converted back when reading from the database.
+ ///
+ /// The type to convert to and from.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HaveConversion()
+ => (PropertiesConfigurationBuilder)base.HaveConversion();
+
+ ///
+ /// Configures the property so that the property value is converted to the given type before
+ /// writing to the database and converted back when reading from the database.
+ ///
+ /// The type to convert to and from.
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HaveConversion(Type providerClrType)
+ => (PropertiesConfigurationBuilder)base.HaveConversion(providerClrType);
+
+ ///
+ /// Configures the property so that the property value is converted to and from the database
+ /// using the given .
+ ///
+ /// A type that derives from .
+ /// A type that derives from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HaveConversion()
+ where TConverter : ValueConverter
+ where TComparer : ValueComparer
+ => (PropertiesConfigurationBuilder)base.HaveConversion();
+
+ ///
+ /// Configures the property so that the property value is converted to and from the database
+ /// using the given .
+ ///
+ /// A type that derives from .
+ /// A type that derives from .
+ /// The same builder instance so that multiple configuration calls can be chained.
+ public new virtual PropertiesConfigurationBuilder HaveConversion(Type converterType, Type? comparerType)
+ => (PropertiesConfigurationBuilder)base.HaveConversion(converterType, comparerType);
+ }
+}
diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs
index d6815d84100..1d1571f8fbc 100644
--- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs
+++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs
@@ -491,8 +491,10 @@ public virtual PropertyBuilder HasConversion(ValueComparer? valueComp
/// The type to convert to and from.
/// The comparer to use for values before conversion.
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual PropertyBuilder HasConversion(Type? providerClrType, ValueComparer? valueComparer)
+ public virtual PropertyBuilder HasConversion(Type providerClrType, ValueComparer? valueComparer)
{
+ Check.NotNull(providerClrType, nameof(providerClrType));
+
Builder.HasConversion(providerClrType, ConfigurationSource.Explicit);
Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit);
@@ -533,8 +535,10 @@ public virtual PropertyBuilder HasConversion()
/// A type that derives from .
/// A type that derives from .
/// The same builder instance so that multiple configuration calls can be chained.
- public virtual PropertyBuilder HasConversion(Type? converterType, Type? comparerType)
+ public virtual PropertyBuilder HasConversion(Type converterType, Type? comparerType)
{
+ Check.NotNull(converterType, nameof(converterType));
+
Builder.HasConverter(converterType, ConfigurationSource.Explicit);
Builder.HasValueComparer(comparerType, ConfigurationSource.Explicit);
diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs
index 7f4557dbf09..9e5b97e14bf 100644
--- a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs
@@ -410,7 +410,7 @@ public virtual PropertyBuilder HasConversion(ValueConverte
/// The comparer to use for values before conversion.
/// The same builder instance so that multiple configuration calls can be chained.
public new virtual PropertyBuilder HasConversion(
- Type? providerClrType,
+ Type providerClrType,
ValueComparer? valueComparer)
=> (PropertyBuilder)base.HasConversion(providerClrType, valueComparer);
@@ -477,7 +477,7 @@ public virtual PropertyBuilder HasConversion(
/// A type that derives from .
/// A type that derives from .
/// The same builder instance so that multiple configuration calls can be chained.
- public new virtual PropertyBuilder HasConversion(Type? converterType, Type? comparerType)
+ public new virtual PropertyBuilder HasConversion(Type converterType, Type? comparerType)
=> (PropertyBuilder)base.HasConversion(converterType, comparerType);
}
}
diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs
index a3008b1675c..541c8258b82 100644
--- a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs
+++ b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs
@@ -332,18 +332,16 @@ var fkPropertiesOnDependentToPrincipal
foreach (var memberInfo in entityType.GetRuntimeProperties().Values.Cast()
.Concat(entityType.GetRuntimeFields().Values))
{
- if (entityType.Builder.IsIgnored(memberInfo.GetSimpleMemberName())
- || !Attribute.IsDefined(memberInfo, typeof(ForeignKeyAttribute), inherit: true))
+ if (!Attribute.IsDefined(memberInfo, typeof(ForeignKeyAttribute), inherit: true)
+ || !entityType.Builder.CanHaveProperty(memberInfo, fromDataAnnotation: true))
{
continue;
}
var attribute = memberInfo.GetCustomAttribute(inherit: true)!;
-
if (attribute.Name != navigationName
|| (memberInfo is PropertyInfo propertyInfo
- && (FindCandidateNavigationPropertyType(propertyInfo) != null
- || IsNavigationToSharedType(entityType.Model, propertyInfo))))
+ && IsNavigationCandidate(propertyInfo, entityType)))
{
continue;
}
@@ -372,13 +370,8 @@ var fkPropertiesOnDependentToPrincipal
return candidateProperty;
}
- private Type? FindCandidateNavigationPropertyType(PropertyInfo propertyInfo)
- => Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(propertyInfo);
-
- private bool IsNavigationToSharedType(IConventionModel model, PropertyInfo propertyInfo)
- => model.IsShared(propertyInfo.PropertyType)
- || (propertyInfo.PropertyType.TryGetSequenceType() is Type elementType
- && model.IsShared(elementType));
+ private bool IsNavigationCandidate(PropertyInfo propertyInfo, IConventionEntityType entityType)
+ => Dependencies.MemberClassifier.GetNavigationCandidates(entityType).TryGetValue(propertyInfo, out var _);
private static IReadOnlyList? FindCandidateDependentPropertiesThroughNavigation(
IConventionForeignKeyBuilder relationshipBuilder,
diff --git a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs
index d66bc3cd80c..78a5be4ae80 100644
--- a/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs
+++ b/src/EFCore/Metadata/Conventions/InversePropertyAttributeConvention.cs
@@ -54,12 +54,7 @@ private void Process(
Type targetClrType,
InversePropertyAttribute attribute)
{
- var entityType = (EntityType)entityTypeBuilder.Metadata;
- var navigationName = navigationMemberInfo.GetSimpleMemberName();
- if (entityTypeBuilder.IsIgnored(navigationName, fromDataAnnotation: true)
- || entityType.FindPropertiesInHierarchy(navigationName).Cast()
- .Concat(entityType.FindServicePropertiesInHierarchy(navigationName))
- .Any(m => !ConfigurationSource.DataAnnotation.Overrides(m.GetConfigurationSource())))
+ if (!entityTypeBuilder.CanHaveNavigation(navigationMemberInfo, fromDataAnnotation: true))
{
return;
}
@@ -87,7 +82,7 @@ private void Process(
.FirstOrDefault(p => string.Equals(p.GetSimpleMemberName(), attribute.Property, StringComparison.OrdinalIgnoreCase));
if (inverseNavigationPropertyInfo == null
- || !Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(inverseNavigationPropertyInfo)!
+ || !Dependencies.MemberClassifier.GetNavigationCandidates(targetEntityTypeBuilder.Metadata)[inverseNavigationPropertyInfo]
.IsAssignableFrom(entityType.ClrType))
{
throw new InvalidOperationException(
@@ -306,10 +301,11 @@ public override void ProcessEntityTypeRemoved(
return;
}
+ var navigationName = navigationMemberInfo.GetSimpleMemberName();
var leastDerivedEntityTypes = modelBuilder.Metadata.FindLeastDerivedEntityTypes(declaringType);
foreach (var leastDerivedEntityType in leastDerivedEntityTypes)
{
- if (leastDerivedEntityType.Builder.IsIgnored(navigationMemberInfo.GetSimpleMemberName(), fromDataAnnotation: true))
+ if (leastDerivedEntityType.Builder.IsIgnored(navigationName, fromDataAnnotation: true))
{
continue;
}
diff --git a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs
index 7c9e92a262a..d8105873114 100644
--- a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs
+++ b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs
@@ -83,7 +83,7 @@ public virtual void ProcessEntityTypeIgnored(
var navigations = new List<(PropertyInfo, Type)>();
foreach (var navigationPropertyInfo in type.GetRuntimeProperties())
{
- var targetClrType = FindCandidateNavigationWithAttributePropertyType(navigationPropertyInfo);
+ var targetClrType = FindCandidateNavigationWithAttributePropertyType(navigationPropertyInfo, modelBuilder.Metadata);
if (targetClrType == null)
{
continue;
@@ -183,7 +183,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
var navigations = new List<(PropertyInfo, Type)>();
foreach (var navigationPropertyInfo in entityType.GetRuntimeProperties().Values)
{
- var targetClrType = FindCandidateNavigationWithAttributePropertyType(navigationPropertyInfo);
+ var targetClrType = FindCandidateNavigationWithAttributePropertyType(navigationPropertyInfo, entityType);
if (targetClrType == null)
{
continue;
@@ -267,7 +267,7 @@ public virtual void ProcessEntityTypeMemberIgnored(
return;
}
- var targetClrType = FindCandidateNavigationWithAttributePropertyType(navigationPropertyInfo);
+ var targetClrType = FindCandidateNavigationWithAttributePropertyType(navigationPropertyInfo, entityTypeBuilder.Metadata);
if (targetClrType == null)
{
return;
@@ -284,15 +284,23 @@ public virtual void ProcessEntityTypeMemberIgnored(
}
}
- private Type? FindCandidateNavigationWithAttributePropertyType(PropertyInfo propertyInfo)
+ private Type? FindCandidateNavigationWithAttributePropertyType(PropertyInfo propertyInfo, IConventionModel model)
{
- var targetClrType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(propertyInfo);
- return targetClrType == null
- || !Attribute.IsDefined(propertyInfo, typeof(TAttribute), inherit: true)
- ? null
- : targetClrType;
+ var targetClrType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(
+ propertyInfo, ((Model)model).Configuration);
+ return targetClrType != null
+ && Attribute.IsDefined(propertyInfo, typeof(TAttribute), inherit: true)
+ ? targetClrType
+ : null;
}
+ private Type? FindCandidateNavigationWithAttributePropertyType(PropertyInfo propertyInfo, IConventionEntityType entityType)
+ => Dependencies.MemberClassifier.GetNavigationCandidates(entityType)
+ .TryGetValue(propertyInfo, out var targetClrType)
+ && Attribute.IsDefined(propertyInfo, typeof(TAttribute), inherit: true)
+ ? targetClrType
+ : null;
+
///
/// Returns the attributes applied to the given navigation.
///
diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs
index 0b516a55b43..43ed2505483 100644
--- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs
+++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs
@@ -11,7 +11,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
///
/// A convention that adds properties to entity types corresponding to scalar public properties on the CLR type.
///
- public class PropertyDiscoveryConvention : IEntityTypeAddedConvention, IEntityTypeBaseTypeChangedConvention
+ public class PropertyDiscoveryConvention :
+ IEntityTypeAddedConvention,
+ IEntityTypeBaseTypeChangedConvention
{
///
/// Creates a new instance of .
@@ -62,22 +64,17 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
{
- foreach (var propertyInfo in entityTypeBuilder.Metadata.GetRuntimeProperties().Values)
+ var entityType = entityTypeBuilder.Metadata;
+ var configuration = ((Model)entityType.Model).Configuration;
+ foreach (var propertyInfo in entityType.GetRuntimeProperties().Values)
{
- if (IsCandidatePrimitiveProperty(propertyInfo))
+ if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, configuration))
{
- entityTypeBuilder.Property(propertyInfo);
+ continue;
}
+
+ entityTypeBuilder.Property(propertyInfo);
}
}
-
- ///
- /// Returns a value indicating whether the given CLR property should be mapped as an entity type property.
- ///
- /// The property.
- /// if the property should be mapped.
- protected virtual bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo)
- => propertyInfo.IsCandidateProperty()
- && Dependencies.TypeMappingSource.FindMapping(propertyInfo) != null;
}
}
diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs
index d514f0444da..94a531e34ab 100644
--- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs
+++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs
@@ -10,6 +10,7 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -46,11 +47,6 @@ public RelationshipDiscoveryConvention(ProviderConventionSetBuilderDependencies
private void DiscoverRelationships(IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context)
{
- if (entityTypeBuilder.ModelBuilder.IsIgnored(entityTypeBuilder.Metadata.ClrType))
- {
- return;
- }
-
var relationshipCandidates = FindRelationshipCandidates(entityTypeBuilder);
relationshipCandidates = RemoveIncompatibleWithExistingRelationships(relationshipCandidates, entityTypeBuilder);
relationshipCandidates = RemoveInheritedInverseNavigations(relationshipCandidates);
@@ -74,7 +70,7 @@ private IReadOnlyList FindRelationshipCandidates(IConvent
return relationshipCandidates.Values.ToList();
}
- foreach (var candidateTuple in GetNavigationCandidates(entityType))
+ foreach (var candidateTuple in Dependencies.MemberClassifier.GetNavigationCandidates(entityType))
{
var navigationPropertyInfo = candidateTuple.Key;
var targetClrType = candidateTuple.Value;
@@ -147,7 +143,7 @@ private IReadOnlyList FindRelationshipCandidates(IConvent
if (!entityType.IsKeyless)
{
- var inverseCandidates = GetNavigationCandidates(candidateTargetEntityType);
+ var inverseCandidates = Dependencies.MemberClassifier.GetNavigationCandidates(candidateTargetEntityType);
foreach (var inverseCandidateTuple in inverseCandidates)
{
var inversePropertyInfo = inverseCandidateTuple.Key;
@@ -618,10 +614,10 @@ private void CreateRelationships(
continue;
}
- var targetOwned = (!entityType.IsInOwnershipPath(targetEntityType)
+ var targetOwned = !entityType.IsInOwnershipPath(targetEntityType)
&& (targetEntityType.Model.IsOwned(targetEntityType.ClrType)
|| (targetEntityType.HasSharedClrType
- && targetEntityType.Model.FindEntityTypes(targetEntityType.ClrType).Any(e => e.IsOwned()))));
+ && targetEntityType.Model.FindEntityTypes(targetEntityType.ClrType).Any(e => e.IsOwned())));
var inverse = relationshipCandidate.InverseProperties.SingleOrDefault();
if (inverse == null)
@@ -967,37 +963,6 @@ public virtual void ProcessForeignKeyOwnershipChanged(
IConventionContext context)
=> DiscoverRelationships(relationshipBuilder.Metadata.DeclaringEntityType.Builder, context);
- private Type? FindCandidateNavigationPropertyType(PropertyInfo propertyInfo)
- => Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(propertyInfo);
-
- private ImmutableSortedDictionary GetNavigationCandidates(IConventionEntityType entityType)
- {
- if (entityType.FindAnnotation(CoreAnnotationNames.NavigationCandidates)?.Value
- is ImmutableSortedDictionary navigationCandidates)
- {
- return navigationCandidates;
- }
-
- var dictionaryBuilder = ImmutableSortedDictionary.CreateBuilder(MemberInfoNameComparer.Instance);
- foreach (var propertyInfo in entityType.GetRuntimeProperties().Values.OrderBy(p => p.Name))
- {
- var targetType = FindCandidateNavigationPropertyType(propertyInfo);
- if (targetType != null)
- {
- dictionaryBuilder[propertyInfo] = targetType;
- }
- }
-
- navigationCandidates = dictionaryBuilder.ToImmutable();
- SetNavigationCandidates(entityType.Builder, navigationCandidates);
- return navigationCandidates;
- }
-
- private static void SetNavigationCandidates(
- IConventionEntityTypeBuilder entityTypeBuilder,
- ImmutableSortedDictionary navigationCandidates)
- => entityTypeBuilder.HasAnnotation(CoreAnnotationNames.NavigationCandidates, navigationCandidates);
-
private static bool IsImplicitlyCreatedUnusedSharedType(IConventionEntityType entityType)
=> entityType.HasSharedClrType
&& entityType.GetConfigurationSource() == ConfigurationSource.Convention
@@ -1097,35 +1062,6 @@ private static void SetAmbiguousNavigations(
ImmutableSortedDictionary ambiguousNavigations)
=> entityTypeBuilder.HasAnnotation(CoreAnnotationNames.AmbiguousNavigations, ambiguousNavigations);
- private sealed class MemberInfoNameComparer : IComparer
- {
- public static readonly MemberInfoNameComparer Instance = new();
-
- private MemberInfoNameComparer()
- {
- }
-
- public int Compare(MemberInfo? x, MemberInfo? y)
- {
- if (ReferenceEquals(x, y))
- {
- return 0;
- }
-
- if (x is null)
- {
- return -1;
- }
-
- if (y is null)
- {
- return 1;
- }
-
- return StringComparer.Ordinal.Compare(x.Name, y.Name);
- }
- }
-
[DebuggerDisplay("{DebuggerDisplay(),nq}")]
private sealed class RelationshipCandidate
{
diff --git a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs
index a2593a01b3e..690fbc3840c 100644
--- a/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs
+++ b/src/EFCore/Metadata/Conventions/ServicePropertyDiscoveryConvention.cs
@@ -61,29 +61,22 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
{
var entityType = entityTypeBuilder.Metadata;
- var candidates = entityType.GetRuntimeProperties().Values;
-
- foreach (var propertyInfo in candidates)
+ var configuration = ((Model)entityType.Model).Configuration;
+ foreach (var propertyInfo in entityType.GetRuntimeProperties().Values)
{
- var name = propertyInfo.GetSimpleMemberName();
- if (entityTypeBuilder.IsIgnored(name)
- || entityType.FindProperty(propertyInfo) != null
- || entityType.FindNavigation(propertyInfo) != null
- || !propertyInfo.IsCandidateProperty(publicOnly: false)
- || (propertyInfo.IsCandidateProperty()
- && Dependencies.TypeMappingSource.FindMapping(propertyInfo) != null))
+ if (!entityTypeBuilder.CanHaveServiceProperty(propertyInfo))
{
continue;
}
- var factory = Dependencies.ParameterBindingFactories.FindFactory(propertyInfo.PropertyType, name);
+ var factory = Dependencies.MemberClassifier.FindServicePropertyCandidateBindingFactory(propertyInfo, configuration);
if (factory == null)
{
continue;
}
entityTypeBuilder.ServiceProperty(propertyInfo)?.HasParameterBinding(
- (ServiceParameterBinding)factory.Bind(entityType, propertyInfo.PropertyType, name));
+ (ServiceParameterBinding)factory.Bind(entityType, propertyInfo.PropertyType, propertyInfo.GetSimpleMemberName()));
}
}
}
diff --git a/src/EFCore/Metadata/IConventionModel.cs b/src/EFCore/Metadata/IConventionModel.cs
index 1bdd7e817e4..1314cb6e158 100644
--- a/src/EFCore/Metadata/IConventionModel.cs
+++ b/src/EFCore/Metadata/IConventionModel.cs
@@ -333,8 +333,7 @@ public interface IConventionModel : IReadOnlyModel, IConventionAnnotatable
///
/// The name of the entity type that could be ignored.
/// if the given entity type name is ignored.
- bool IsIgnored(string typeName)
- => FindIgnoredConfigurationSource(typeName) != null;
+ bool IsIgnored(string typeName);
///
/// Indicates whether the given entity type is ignored.
@@ -343,6 +342,14 @@ bool IsIgnored(string typeName)
/// if the given entity type is ignored.
bool IsIgnored(Type type);
+ ///
+ /// Indicates whether entity types and properties with the given type should be ignored.
+ /// This configuration is independent from
+ ///
+ /// The entity type that might be ignored.
+ /// if the given entity type is ignored.
+ bool IsIgnoredType(Type type);
+
///
/// Indicates whether the given entity type name is ignored.
///
diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs
index ade139c5dec..04be8d4d8bc 100644
--- a/src/EFCore/Metadata/Internal/EntityType.cs
+++ b/src/EFCore/Metadata/Internal/EntityType.cs
@@ -2374,6 +2374,16 @@ public virtual IEnumerable GetIndexes()
_properties.Add(property.Name, property);
+ if (Model.Configuration != null)
+ {
+ using (Model.ConventionDispatcher.DelayConventions())
+ {
+ Model.ConventionDispatcher.OnPropertyAdded(property.Builder);
+ Model.Configuration.ConfigureProperty(property);
+ return property;
+ }
+ }
+
return (Property?)Model.ConventionDispatcher.OnPropertyAdded(property.Builder)?.Metadata;
}
diff --git a/src/EFCore/Metadata/Internal/IMemberClassifier.cs b/src/EFCore/Metadata/Internal/IMemberClassifier.cs
index b06f2827cdb..e52acba52ef 100644
--- a/src/EFCore/Metadata/Internal/IMemberClassifier.cs
+++ b/src/EFCore/Metadata/Internal/IMemberClassifier.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Immutable;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
@@ -28,6 +29,30 @@ public interface IMemberClassifier
/// 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.
///
- Type? FindCandidateNavigationPropertyType(MemberInfo memberInfo);
+ ImmutableSortedDictionary GetNavigationCandidates(IConventionEntityType entityType);
+
+ ///
+ /// 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.
+ ///
+ Type? FindCandidateNavigationPropertyType(MemberInfo memberInfo, ModelConfiguration? configuration);
+
+ ///
+ /// 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.
+ ///
+ bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, ModelConfiguration? configuration);
+
+ ///
+ /// 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.
+ ///
+ IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(PropertyInfo propertyInfo, ModelConfiguration? configuration);
}
}
diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
index ae6aef950af..d4fb2d720aa 100644
--- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
@@ -554,49 +554,14 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource)
}
else
{
- if (!configurationSource.HasValue
- || IsIgnored(propertyName, configurationSource))
+ if (configurationSource != ConfigurationSource.Explicit
+ && (!configurationSource.HasValue
+ || !CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value)))
{
return null;
}
- foreach (var conflictingServiceProperty in Metadata.FindServicePropertiesInHierarchy(propertyName))
- {
- if (!configurationSource.Overrides(conflictingServiceProperty.GetConfigurationSource()))
- {
- return null;
- }
- }
-
- foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName))
- {
- var foreignKey = conflictingNavigation.ForeignKey;
-
- var navigationConfigurationSource = conflictingNavigation.GetConfigurationSource();
- if (!configurationSource.Overrides(navigationConfigurationSource))
- {
- return null;
- }
-
- if (navigationConfigurationSource == ConfigurationSource.Explicit)
- {
- throw new InvalidOperationException(
- CoreStrings.PropertyCalledOnNavigation(propertyName, Metadata.DisplayName()));
- }
- }
-
- foreach (var conflictingSkipNavigation in Metadata.FindSkipNavigationsInHierarchy(propertyName))
- {
- if (!configurationSource.Overrides(conflictingSkipNavigation.GetConfigurationSource()))
- {
- return null;
- }
- }
-
- if (memberInfo == null)
- {
- memberInfo = Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault();
- }
+ memberInfo ??= Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault();
if (propertyType == null)
{
@@ -608,13 +573,6 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource)
propertyType = memberInfo.GetMemberType();
typeConfigurationSource = ConfigurationSource.Explicit;
}
- else if (memberInfo != null
- && propertyType != memberInfo.GetMemberType()
- && memberInfo != Metadata.FindIndexerPropertyInfo()
- && typeConfigurationSource != null)
- {
- return null;
- }
foreach (var derivedType in Metadata.GetDerivedTypes())
{
@@ -654,7 +612,8 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource)
{
if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit)
{
- continue;
+ throw new InvalidOperationException(
+ CoreStrings.PropertyCalledOnNavigation(propertyName, Metadata.DisplayName()));
}
var foreignKey = conflictingNavigation.ForeignKey;
@@ -702,6 +661,45 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource)
: Metadata.FindProperty(propertyName)?.Builder;
}
+ ///
+ /// 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 bool CanHaveProperty(
+ Type? propertyType,
+ string propertyName,
+ MemberInfo? memberInfo,
+ ConfigurationSource? typeConfigurationSource,
+ ConfigurationSource? configurationSource)
+ {
+ var existingProperty = Metadata.FindProperty(propertyName);
+ return existingProperty != null
+ ? (IsCompatible(memberInfo, existingProperty)
+ && (propertyType == null || propertyType == existingProperty.ClrType))
+ || ((memberInfo == null
+ || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty()))
+ && (existingProperty.GetTypeConfigurationSource() is not ConfigurationSource existingTypeConfigurationSource
+ || typeConfigurationSource.Overrides(existingTypeConfigurationSource)))
+ || configurationSource.Overrides(existingProperty.GetConfigurationSource())
+ : configurationSource.HasValue
+ && CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value);
+ }
+
+ private bool CanAddProperty(
+ Type? propertyType,
+ string propertyName,
+ ConfigurationSource configurationSource)
+ => !IsIgnored(propertyName, configurationSource)
+ && (propertyType == null
+ || Metadata.Model.Builder.CanBeConfigured(propertyType, TypeConfigurationType.Property, configurationSource))
+ && Metadata.FindServicePropertiesInHierarchy(propertyName).Cast()
+ .Concat(Metadata.FindNavigationsInHierarchy(propertyName))
+ .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName))
+ .All(m => configurationSource.Overrides(m.GetConfigurationSource())
+ && m.GetConfigurationSource() != ConfigurationSource.Explicit);
+
private bool IsCompatible(MemberInfo? newMemberInfo, Property existingProperty)
{
if (newMemberInfo == null)
@@ -868,7 +866,9 @@ public virtual IMutableNavigationBase Navigation(string navigationName)
propertiesToDetach = new List { existingProperty };
}
- else if (!CanAddServiceProperty(memberInfo, configurationSource))
+ else if (configurationSource != ConfigurationSource.Explicit
+ && (!configurationSource.HasValue
+ || !CanAddServiceProperty(memberInfo, configurationSource.Value)))
{
return null;
}
@@ -983,31 +983,25 @@ public virtual bool CanHaveServiceProperty(MemberInfo memberInfo, ConfigurationS
var existingProperty = Metadata.FindServiceProperty(memberInfo);
return existingProperty != null
? existingProperty.DeclaringEntityType == Metadata
- || (configurationSource.HasValue
- && configurationSource.Value.Overrides(existingProperty.GetConfigurationSource()))
- : CanAddServiceProperty(memberInfo, configurationSource);
+ || configurationSource.Overrides(existingProperty.GetConfigurationSource())
+ : configurationSource.HasValue
+ && CanAddServiceProperty(memberInfo, configurationSource.Value);
}
- private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource? configurationSource)
+ private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource configurationSource)
{
var propertyName = memberInfo.GetSimpleMemberName();
- if (!configurationSource.HasValue
- || IsIgnored(propertyName, configurationSource))
- {
- return false;
- }
-
- foreach (var conflictingProperty in Metadata.FindMembersInHierarchy(propertyName))
- {
- if (!configurationSource.Overrides(conflictingProperty.GetConfigurationSource())
- && (conflictingProperty is not ServiceProperty derivedServiceProperty
- || !memberInfo.IsOverridenBy(derivedServiceProperty.GetIdentifyingMemberInfo())))
- {
- return false;
- }
- }
-
- return true;
+ return !IsIgnored(propertyName, configurationSource)
+ && Metadata.Model.Builder.CanBeConfigured(memberInfo.GetMemberType(), TypeConfigurationType.ServiceProperty, configurationSource)
+ && Metadata.FindPropertiesInHierarchy(propertyName).Cast()
+ .Concat(Metadata.FindNavigationsInHierarchy(propertyName))
+ .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName))
+ .All(m => configurationSource.Overrides(m.GetConfigurationSource())
+ && m.GetConfigurationSource() != ConfigurationSource.Explicit)
+ && Metadata.FindServicePropertiesInHierarchy(propertyName)
+ .All(m => (configurationSource.Overrides(m.GetConfigurationSource())
+ && m.GetConfigurationSource() != ConfigurationSource.Explicit)
+ || memberInfo.IsOverridenBy(m.GetIdentifyingMemberInfo()));
}
private static InternalServicePropertyBuilder? DetachServiceProperty(ServiceProperty? serviceProperty)
@@ -1028,12 +1022,48 @@ private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource? c
/// 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 bool CanHaveNavigation(string navigationName, ConfigurationSource? configurationSource)
+ public virtual bool CanHaveNavigation(string navigationName, Type? type, ConfigurationSource? configurationSource)
+ {
+ var existingNavigation = Metadata.FindNavigation(navigationName);
+ return existingNavigation != null
+ ? type == null
+ || existingNavigation.ClrType == type
+ || configurationSource.Overrides(existingNavigation.GetConfigurationSource())
+ : configurationSource.HasValue
+ && CanAddNavigation(navigationName, type, configurationSource.Value);
+ }
+
+ ///
+ /// 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 bool CanAddNavigation(string navigationName, Type? type, ConfigurationSource configurationSource)
=> !IsIgnored(navigationName, configurationSource)
+ && (type == null || CanBeNavigation(type, configurationSource))
&& Metadata.FindPropertiesInHierarchy(navigationName).Cast()
.Concat(Metadata.FindServicePropertiesInHierarchy(navigationName))
.Concat(Metadata.FindSkipNavigationsInHierarchy(navigationName))
- .All(m => configurationSource.Overrides(m.GetConfigurationSource()));
+ .All(m => configurationSource.Overrides(m.GetConfigurationSource())
+ && m.GetConfigurationSource() != ConfigurationSource.Explicit);
+
+ private bool CanBeNavigation(Type type, ConfigurationSource configurationSource)
+ {
+ if (configurationSource == ConfigurationSource.Explicit)
+ {
+ return true;
+ }
+
+ if (ModelBuilder.Metadata.Configuration?.GetConfigurationType(type).IsEntityType() == false
+ || (type?.TryGetSequenceType() is Type sequenceType
+ && ModelBuilder.Metadata.Configuration?.GetConfigurationType(sequenceType).IsEntityType() == false))
+ {
+ return false;
+ }
+
+ return true;
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -1041,12 +1071,25 @@ public virtual bool CanHaveNavigation(string navigationName, ConfigurationSource
/// 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 bool CanHaveSkipNavigation(string skipNavigationName, ConfigurationSource? configurationSource)
+ public virtual bool CanHaveSkipNavigation(string skipNavigationName, Type? type, ConfigurationSource? configurationSource)
+ {
+ var existingNavigation = Metadata.FindSkipNavigation(skipNavigationName);
+ return existingNavigation != null
+ ? type == null
+ || existingNavigation.ClrType == type
+ || configurationSource.Overrides(existingNavigation.GetConfigurationSource())
+ : configurationSource.HasValue
+ && CanAddSkipNavigation(skipNavigationName, type, configurationSource.Value);
+ }
+
+ private bool CanAddSkipNavigation(string skipNavigationName, Type? type, ConfigurationSource configurationSource)
=> !IsIgnored(skipNavigationName, configurationSource)
+ && (type == null || CanBeNavigation(type, configurationSource))
&& Metadata.FindPropertiesInHierarchy(skipNavigationName).Cast()
.Concat(Metadata.FindServicePropertiesInHierarchy(skipNavigationName))
.Concat(Metadata.FindNavigationsInHierarchy(skipNavigationName))
- .All(m => configurationSource.Overrides(m.GetConfigurationSource()));
+ .All(m => configurationSource.Overrides(m.GetConfigurationSource())
+ && m.GetConfigurationSource() != ConfigurationSource.Explicit);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -3377,6 +3420,21 @@ private bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignKey deri
targetType = Model.DefaultPropertyBagType;
}
+ switch (ModelBuilder.Metadata.Configuration?.GetConfigurationType(targetType))
+ {
+ case null:
+ break;
+ case TypeConfigurationType.EntityType:
+ case TypeConfigurationType.SharedTypeEntityType:
+ targetShouldBeOwned ??= false;
+ break;
+ case TypeConfigurationType.OwnedEntityType:
+ targetShouldBeOwned ??= true;
+ break;
+ default:
+ return null;
+ }
+
if (targetShouldBeOwned != true)
{
var ownership = Metadata.FindOwnership();
@@ -3593,6 +3651,11 @@ private bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignKey deri
dependentProperties.Count, null, dependentProperties.Select(p => p.ClrType),
Enumerable.Repeat("", dependentProperties.Count), isRequired: true, baseName: "TempId").Item2;
+ if (principalKeyProperties == null)
+ {
+ return null;
+ }
+
principalKey = principalBaseEntityTypeBuilder.HasKeyInternal(principalKeyProperties, ConfigurationSource.Convention)!
.Metadata;
}
@@ -3611,6 +3674,11 @@ private bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignKey deri
var principalKeyProperties = principalBaseEntityTypeBuilder.TryCreateUniqueProperties(
1, null, new[] { typeof(int) }, new[] { "TempId" }, isRequired: true, baseName: "").Item2;
+ if (principalKeyProperties == null)
+ {
+ return null;
+ }
+
principalKey = principalBaseEntityTypeBuilder.HasKeyInternal(
principalKeyProperties, ConfigurationSource.Convention)?.Metadata;
@@ -3624,7 +3692,12 @@ private bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignKey deri
{
var oldProperties = foreignKey.Properties;
var oldKey = foreignKey.PrincipalKey;
- var temporaryProperties = CreateUniqueProperties(null, principalKey.Properties, isRequired ?? false, "TempFk")!;
+ var temporaryProperties = CreateUniqueProperties(principalKey.Properties, isRequired ?? false, "TempFk");
+ if (temporaryProperties == null)
+ {
+ return null;
+ }
+
foreignKey.SetProperties(temporaryProperties, principalKey, configurationSource);
foreignKey.DeclaringEntityType.Builder.RemoveUnusedImplicitProperties(oldProperties);
@@ -3639,7 +3712,11 @@ private bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignKey deri
var baseName = string.IsNullOrEmpty(propertyBaseName)
? principalType.ShortName()
: propertyBaseName;
- dependentProperties = CreateUniqueProperties(null, principalKey.Properties, isRequired ?? false, baseName)!;
+ dependentProperties = CreateUniqueProperties(principalKey.Properties, isRequired ?? false, baseName);
+ if (dependentProperties == null)
+ {
+ return null;
+ }
}
if (foreignKey == null)
@@ -3745,22 +3822,13 @@ private bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignKey deri
return existingNavigation.Builder;
}
- if (!configurationSource.HasValue
- || IsIgnored(navigationName, configurationSource))
+ if (configurationSource != ConfigurationSource.Explicit
+ && (!configurationSource.HasValue
+ || !CanAddSkipNavigation(navigationName, memberInfo?.GetMemberType(), configurationSource.Value)))
{
return null;
}
- foreach (var conflictingMember in Metadata.FindPropertiesInHierarchy(navigationName).Cast()
- .Concat(Metadata.FindNavigationsInHierarchy(navigationName))
- .Concat(Metadata.FindServicePropertiesInHierarchy(navigationName)))
- {
- if (!configurationSource.Overrides(conflictingMember.GetConfigurationSource()))
- {
- return null;
- }
- }
-
foreach (var derivedType in Metadata.GetDerivedTypes())
{
var conflictingNavigation = derivedType.FindDeclaredSkipNavigation(navigationName);
@@ -3936,7 +4004,7 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey)
=> CreateUniqueProperties(
new[] { propertyType },
new[] { propertyName },
- required).First().Builder;
+ required)?.First().Builder;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -3944,7 +4012,7 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey)
/// 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 IReadOnlyList CreateUniqueProperties(
+ public virtual IReadOnlyList? CreateUniqueProperties(
IReadOnlyList propertyTypes,
IReadOnlyList propertyNames,
bool isRequired)
@@ -3954,16 +4022,15 @@ public virtual IReadOnlyList CreateUniqueProperties(
propertyTypes,
propertyNames,
isRequired,
- "").Item2!;
+ "").Item2;
private IReadOnlyList? CreateUniqueProperties(
- IReadOnlyList? currentProperties,
IReadOnlyList principalProperties,
bool isRequired,
string baseName)
=> TryCreateUniqueProperties(
principalProperties.Count,
- currentProperties,
+ null,
principalProperties.Select(p => p.ClrType),
principalProperties.Select(p => p.Name),
isRequired,
@@ -4010,7 +4077,12 @@ public virtual IReadOnlyList CreateUniqueProperties(
{
var propertyBuilder = Property(
clrType, propertyName, typeConfigurationSource: null,
- configurationSource: ConfigurationSource.Convention)!;
+ configurationSource: ConfigurationSource.Convention);
+
+ if (propertyBuilder == null)
+ {
+ return (false, null);
+ }
if (clrType.IsNullableType())
{
@@ -4019,6 +4091,11 @@ public virtual IReadOnlyList CreateUniqueProperties(
newProperties![i] = propertyBuilder.Metadata;
}
+ else if (!Metadata.Model.Builder.CanBeConfigured(
+ clrType, TypeConfigurationType.Property, ConfigurationSource.Convention))
+ {
+ break;
+ }
else
{
canReuniquify = true;
@@ -4579,7 +4656,7 @@ bool IConventionEntityTypeBuilder.CanSetBaseType(IConventionEntityType? baseEnti
propertyType,
propertyName, setTypeConfigurationSource
? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention
- : (ConfigurationSource?)null, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ : null, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -4591,6 +4668,71 @@ bool IConventionEntityTypeBuilder.CanSetBaseType(IConventionEntityType? baseEnti
IConventionPropertyBuilder? IConventionEntityTypeBuilder.Property(MemberInfo memberInfo, bool fromDataAnnotation)
=> Property(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ bool IConventionEntityTypeBuilder.CanHaveProperty(
+ Type? propertyType, string propertyName, bool fromDataAnnotation)
+ => CanHaveProperty(
+ propertyType,
+ propertyName,
+ null,
+ propertyType != null ?
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention
+ : null,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ bool IConventionEntityTypeBuilder.CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation)
+ => CanHaveProperty(
+ memberInfo.GetMemberType(),
+ memberInfo.Name,
+ memberInfo,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ IConventionPropertyBuilder? IConventionEntityTypeBuilder.IndexerProperty(
+ Type propertyType, string propertyName, bool fromDataAnnotation)
+ => Property(
+ propertyType,
+ propertyName,
+ Metadata.FindIndexerPropertyInfo(),
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ bool IConventionEntityTypeBuilder.CanHaveIndexerProperty(
+ Type propertyType, string propertyName, bool fromDataAnnotation)
+ => CanHaveProperty(
+ propertyType,
+ propertyName,
+ Metadata.FindIndexerPropertyInfo(),
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
///
/// 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
@@ -4637,6 +4779,16 @@ IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitPr
IConventionServicePropertyBuilder? IConventionEntityTypeBuilder.ServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation)
=> ServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ bool IConventionEntityTypeBuilder.CanHaveServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation)
+ => CanHaveServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
///
/// 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
@@ -5136,6 +5288,20 @@ bool IConventionEntityTypeBuilder.CanRemoveRelationship(IConventionForeignKey fo
bool IConventionEntityTypeBuilder.CanHaveNavigation(string navigationName, bool fromDataAnnotation)
=> CanHaveNavigation(
navigationName,
+ null,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ bool IConventionEntityTypeBuilder.CanHaveNavigation(string navigationName, Type? type, bool fromDataAnnotation)
+ => CanHaveNavigation(
+ navigationName,
+ type,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
@@ -5148,6 +5314,20 @@ bool IConventionEntityTypeBuilder.CanHaveNavigation(string navigationName, bool
bool IConventionEntityTypeBuilder.CanHaveSkipNavigation(string skipNavigationName, bool fromDataAnnotation)
=> CanHaveSkipNavigation(
skipNavigationName,
+ null,
+ fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ bool IConventionEntityTypeBuilder.CanHaveSkipNavigation(string skipNavigationName, Type? type, bool fromDataAnnotation)
+ => CanHaveSkipNavigation(
+ skipNavigationName,
+ type,
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
///
diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs
index 1f8856b9aa4..1bdd97ec1c7 100644
--- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs
@@ -672,7 +672,10 @@ private bool CanSetNavigations(
removeOppositeNavigation = true;
}
- else if (!dependentEntityType.Builder.CanHaveNavigation(navigationToPrincipalName, configurationSource))
+ else if ((configurationSource != ConfigurationSource.Explicit || !shouldThrow)
+ && (!configurationSource.HasValue
+ || !dependentEntityType.Builder.CanAddNavigation(
+ navigationToPrincipalName, navigationToPrincipal.Value.MemberInfo?.GetMemberType(), configurationSource.Value)))
{
return false;
}
@@ -702,7 +705,10 @@ private bool CanSetNavigations(
removeOppositeNavigation = true;
}
- else if (!principalEntityType.Builder.CanHaveNavigation(navigationToDependentName, configurationSource))
+ else if ((configurationSource != ConfigurationSource.Explicit || !shouldThrow)
+ && (!configurationSource.HasValue
+ || !principalEntityType.Builder.CanAddNavigation(
+ navigationToDependentName, navigationToDependent.Value.MemberInfo?.GetMemberType(), configurationSource.Value)))
{
return false;
}
@@ -804,8 +810,7 @@ private bool CanSetNavigations(
conflictingNavigationsFound = compatibleRelationship != null
|| resolvableRelationships.Any(
- r =>
- (r.Resolution & (Resolution.ResetToDependent | Resolution.ResetToPrincipal)) != 0);
+ r => (r.Resolution & (Resolution.ResetToDependent | Resolution.ResetToPrincipal)) != 0);
if (shouldBeUnique == null
&& (Metadata.IsUnique || configurationSource.OverridesStrictly(Metadata.GetIsUniqueConfigurationSource()))
diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs
index 2735a25c682..41775199960 100644
--- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs
@@ -86,6 +86,31 @@ public override InternalModelBuilder ModelBuilder
return null;
}
+ if (type.Type != null)
+ {
+ if (shouldBeOwned == null)
+ {
+ if (configurationSource != ConfigurationSource.Explicit
+ && Metadata.Configuration?.GetConfigurationType(type.Type).IsEntityType() == false)
+ {
+ return null;
+ }
+ }
+ else
+ {
+ var configurationType = shouldBeOwned.Value
+ ? TypeConfigurationType.OwnedEntityType
+ : type.IsNamed
+ ? TypeConfigurationType.SharedTypeEntityType
+ : TypeConfigurationType.EntityType;
+
+ if (!CanBeConfigured(type.Type, configurationType, configurationSource))
+ {
+ return null;
+ }
+ }
+ }
+
using var batch = Metadata.DelayConventions();
var clrType = type.Type;
EntityType? entityType;
@@ -139,8 +164,7 @@ public override InternalModelBuilder ModelBuilder
{
// We always throw as configuring a type as owned always comes from user (through Explicit/DataAnnotation)
throw new InvalidOperationException(
- CoreStrings.ClashingOwnedEntityType(
- clrType == null ? type.Name : clrType.ShortDisplayName()));
+ CoreStrings.ClashingOwnedEntityType(clrType == null ? type.Name : clrType.ShortDisplayName()));
}
if (shouldBeOwned == true
@@ -271,7 +295,8 @@ public override InternalModelBuilder ModelBuilder
Type type,
ConfigurationSource configurationSource)
{
- if (IsIgnored(type, configurationSource))
+ if (IsIgnored(type, configurationSource)
+ || !CanBeConfigured(type, TypeConfigurationType.OwnedEntityType, configurationSource))
{
return null;
}
@@ -370,10 +395,42 @@ private bool IsIgnored(in TypeIdentity type, ConfigurationSource configurationSo
}
var ignoredConfigurationSource = Metadata.FindIgnoredConfigurationSource(type.Name);
+ if (type.Type != null
+ && Metadata.IsIgnoredType(type.Type))
+ {
+ ignoredConfigurationSource = ConfigurationSource.Explicit;
+ }
+
return ignoredConfigurationSource.HasValue
&& ignoredConfigurationSource.Value.Overrides(configurationSource);
}
+ ///
+ /// 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 bool CanBeConfigured(Type type, TypeConfigurationType configurationType, ConfigurationSource configurationSource)
+ {
+ if (configurationSource == ConfigurationSource.Explicit)
+ {
+ return true;
+ }
+
+ if (!configurationType.IsEntityType()
+ && (!configurationSource.Overrides(Metadata.FindEntityType(type)?.GetConfigurationSource())
+ || !configurationSource.Overrides(Metadata.FindIsOwnedConfigurationSource(type))
+ || Metadata.IsShared(type)))
+ {
+ return false;
+ }
+
+ var configuredType = ModelBuilder.Metadata.Configuration?.GetConfigurationType(type);
+ return configuredType == null
+ || configuredType == configurationType;
+ }
+
///
/// 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
diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
index 83476c710cf..fb27c385773 100644
--- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
@@ -459,6 +459,7 @@ public virtual bool CanSetValueGeneratorFactory(
{
if (CanSetConversion(converter, configurationSource))
{
+ Metadata.SetProviderClrType(null, configurationSource);
Metadata.SetValueConverter(converter, configurationSource);
return this;
@@ -476,11 +477,12 @@ public virtual bool CanSetValueGeneratorFactory(
public virtual bool CanSetConversion(
ValueConverter? converter,
ConfigurationSource? configurationSource)
- => configurationSource == ConfigurationSource.Explicit
+ => (configurationSource == ConfigurationSource.Explicit
|| (configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource())
&& Metadata.CheckValueConverter(converter) == null)
|| (Metadata[CoreAnnotationNames.ValueConverterType] == null
- && (ValueConverter?)Metadata[CoreAnnotationNames.ValueConverter] == converter);
+ && (ValueConverter?)Metadata[CoreAnnotationNames.ValueConverter] == converter))
+ && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource());
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -492,6 +494,7 @@ public virtual bool CanSetConversion(
{
if (CanSetConversion(providerClrType, configurationSource))
{
+ Metadata.SetValueConverter((ValueConverter?)null, configurationSource);
Metadata.SetProviderClrType(providerClrType, configurationSource);
return this;
@@ -507,8 +510,9 @@ public virtual bool CanSetConversion(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? configurationSource)
- => configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource())
- || Metadata.GetProviderClrType() == providerClrType;
+ => (configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource())
+ || Metadata.GetProviderClrType() == providerClrType)
+ && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource());
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -520,6 +524,7 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource?
{
if (CanSetConverter(converterType, configurationSource))
{
+ Metadata.SetProviderClrType(null, configurationSource);
Metadata.SetValueConverter(converterType, configurationSource);
return this;
diff --git a/src/EFCore/Metadata/Internal/MemberClassifier.cs b/src/EFCore/Metadata/Internal/MemberClassifier.cs
index d896a27407f..5f03e435d08 100644
--- a/src/EFCore/Metadata/Internal/MemberClassifier.cs
+++ b/src/EFCore/Metadata/Internal/MemberClassifier.cs
@@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Immutable;
using System.Reflection;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.DependencyInjection;
@@ -50,10 +53,44 @@ public MemberClassifier(
/// 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 Type? FindCandidateNavigationPropertyType(MemberInfo memberInfo)
+ public virtual ImmutableSortedDictionary GetNavigationCandidates(IConventionEntityType entityType)
{
- Check.NotNull(memberInfo, nameof(memberInfo));
+ if (entityType.FindAnnotation(CoreAnnotationNames.NavigationCandidates)?.Value
+ is ImmutableSortedDictionary navigationCandidates)
+ {
+ return navigationCandidates;
+ }
+
+ var dictionaryBuilder = ImmutableSortedDictionary.CreateBuilder(MemberInfoNameComparer.Instance);
+
+ var configuration = ((Model)entityType.Model).Configuration;
+ foreach (var propertyInfo in entityType.GetRuntimeProperties().Values)
+ {
+ var targetType = FindCandidateNavigationPropertyType(propertyInfo, configuration);
+ if (targetType != null)
+ {
+ dictionaryBuilder[propertyInfo] = targetType;
+ }
+ }
+ navigationCandidates = dictionaryBuilder.ToImmutable();
+
+ if (!((Annotatable)entityType).IsReadOnly
+ && entityType.IsInModel)
+ {
+ entityType.Builder.HasAnnotation(CoreAnnotationNames.NavigationCandidates, navigationCandidates);
+ }
+ return navigationCandidates;
+ }
+
+ ///
+ /// 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 Type? FindCandidateNavigationPropertyType(MemberInfo memberInfo, ModelConfiguration? configuration)
+ {
var targetType = memberInfo.GetMemberType();
var targetSequenceType = targetType.TryGetSequenceType();
if (!(memberInfo is PropertyInfo propertyInfo)
@@ -62,17 +99,85 @@ public MemberClassifier(
return null;
}
+ var isConfiguredAsEntityType = configuration?.GetConfigurationType(targetType).IsEntityType();
+ if (isConfiguredAsEntityType == false)
+ {
+ return null;
+ }
+
+ if (targetSequenceType != null)
+ {
+ isConfiguredAsEntityType ??= configuration?.GetConfigurationType(targetSequenceType).IsEntityType();
+ if (isConfiguredAsEntityType == false)
+ {
+ return null;
+ }
+ }
+
targetType = targetSequenceType ?? targetType;
- targetType = targetType.UnwrapNullableType();
+ if (!targetType.IsValidEntityType())
+ {
+ return null;
+ }
- return targetType.IsInterface
- || targetType.IsValueType
- || targetType == typeof(object)
- || _parameterBindingFactories.FindFactory(targetType, memberInfo.GetSimpleMemberName()) != null
- || _typeMappingSource.FindMapping(targetType) != null
- || targetType.IsArray
+ targetType = targetType.UnwrapNullableType();
+ return isConfiguredAsEntityType == null
+ && (targetType == typeof(object)
+ || _parameterBindingFactories.FindFactory(targetType, memberInfo.GetSimpleMemberName()) != null
+ || _typeMappingSource.FindMapping(targetType) != null)
? null
: targetType;
}
+
+ ///
+ /// 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 bool IsCandidatePrimitiveProperty(PropertyInfo propertyInfo, ModelConfiguration? configuration)
+ {
+ if (!propertyInfo.IsCandidateProperty())
+ {
+ return false;
+ }
+
+ var configurationType = configuration?.GetConfigurationType(propertyInfo.PropertyType);
+ return configurationType == TypeConfigurationType.Property
+ || (configurationType == null && _typeMappingSource.FindMapping(propertyInfo) != null);
+ }
+
+ ///
+ /// 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 IParameterBindingFactory? FindServicePropertyCandidateBindingFactory(
+ PropertyInfo propertyInfo, ModelConfiguration? configuration)
+ {
+ if (!propertyInfo.IsCandidateProperty(publicOnly: false))
+ {
+ return null;
+ }
+
+ var type = propertyInfo.PropertyType;
+ var configurationType = configuration?.GetConfigurationType(type);
+ if (configurationType != TypeConfigurationType.ServiceProperty)
+ {
+ if (configurationType != null)
+ {
+ return null;
+ }
+
+ if (propertyInfo.IsCandidateProperty()
+ && _typeMappingSource.FindMapping(propertyInfo) != null)
+ {
+ return null;
+ }
+ }
+
+ return _parameterBindingFactories.FindFactory(type, propertyInfo.GetSimpleMemberName());
+ }
}
}
diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs
index d0128457e0c..48343d0ee9b 100644
--- a/src/EFCore/Metadata/Internal/Model.cs
+++ b/src/EFCore/Metadata/Internal/Model.cs
@@ -608,6 +608,15 @@ public virtual bool IsIgnored(string name)
public virtual bool IsIgnored(Type type)
=> FindIgnoredConfigurationSource(GetDisplayName(type)) != null;
+ ///
+ /// 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 bool IsIgnoredType(Type type)
+ => Configuration?.GetConfigurationType(type) == TypeConfigurationType.Ignored;
+
///
/// 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
@@ -871,7 +880,6 @@ public virtual IModel FinalizeModel()
if (finalizedModel is Model model)
{
- model.Configuration = null;
finalizedModel = model.MakeReadonly();
}
diff --git a/src/EFCore/Metadata/Internal/ModelConfiguration.cs b/src/EFCore/Metadata/Internal/ModelConfiguration.cs
index 544acfe0838..8d44a11c77c 100644
--- a/src/EFCore/Metadata/Internal/ModelConfiguration.cs
+++ b/src/EFCore/Metadata/Internal/ModelConfiguration.cs
@@ -1,6 +1,11 @@
// 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;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+
namespace Microsoft.EntityFrameworkCore.Metadata.Internal
{
///
@@ -11,6 +16,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
///
public partial class ModelConfiguration
{
+ private readonly Dictionary _properties = new();
+ private readonly HashSet _ignoredTypes = new();
+ private readonly Dictionary _configurationTypes = new();
+
///
/// 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
@@ -28,6 +37,245 @@ public ModelConfiguration()
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual bool IsEmpty()
- => true;
+ => _properties.Count == 0 && _ignoredTypes.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 TypeConfigurationType? GetConfigurationType(Type type)
+ {
+ Type? configuredType = null;
+ return GetConfigurationType(type, null, ref configuredType);
+ }
+
+ private TypeConfigurationType? GetConfigurationType(
+ Type type, TypeConfigurationType? previousConfiguration, ref Type? previousType, bool getBaseTypes = true)
+ {
+ if (_configurationTypes.TryGetValue(type, out var configurationType))
+ {
+ if (configurationType.HasValue)
+ {
+ EnsureCompatible(configurationType.Value, type, previousConfiguration, previousType);
+ previousType = type;
+ }
+
+ return configurationType ?? previousConfiguration;
+ }
+
+ Type? configuredType = null;
+ if (getBaseTypes)
+ {
+ if (type.BaseType != null)
+ {
+ configurationType = GetConfigurationType(
+ type.BaseType, configurationType, ref configuredType);
+ }
+
+ if (type.IsConstructedGenericType)
+ {
+ configurationType = GetConfigurationType(
+ type.GetGenericTypeDefinition(), configurationType, ref configuredType, getBaseTypes: false);
+ }
+
+ foreach (var @interface in GetDeclaredInterfaces(type))
+ {
+ configurationType = GetConfigurationType(
+ @interface, configurationType, ref configuredType, getBaseTypes: false);
+ }
+ }
+
+ if (_ignoredTypes.Contains(type))
+ {
+ EnsureCompatible(TypeConfigurationType.Ignored, type, configurationType, configuredType);
+ configurationType = TypeConfigurationType.Ignored;
+ configuredType = type;
+ }
+
+ if (_properties.ContainsKey(type))
+ {
+ EnsureCompatible(TypeConfigurationType.Property, type, configurationType, configuredType);
+ configurationType = TypeConfigurationType.Property;
+ configuredType = type;
+ }
+
+ if (configurationType.HasValue)
+ {
+ EnsureCompatible(configurationType.Value, configuredType!, previousConfiguration, previousType);
+ previousType = configuredType;
+ }
+
+ _configurationTypes[type] = configurationType;
+ return configurationType ?? previousConfiguration;
+ }
+
+ private IEnumerable GetDeclaredInterfaces(Type type)
+ {
+ var interfaces = type.GetInterfaces();
+ if (type.BaseType == typeof(object)
+ || type.BaseType == null)
+ {
+ return interfaces;
+ }
+
+ return interfaces.Except(type.BaseType.GetInterfaces());
+ }
+
+ private static void EnsureCompatible(
+ TypeConfigurationType configurationType, Type type,
+ TypeConfigurationType? previousConfiguration, Type? previousType)
+ {
+ if (previousConfiguration != null
+ && previousConfiguration.Value != configurationType)
+ {
+ throw new InvalidOperationException(CoreStrings.TypeConfigurationConflict(
+ type.DisplayName(fullName: false), configurationType,
+ previousType?.DisplayName(fullName: false), previousConfiguration.Value));
+ }
+ }
+
+ private IList GetBaseTypesAndInterfacesInclusive(Type type)
+ {
+ var baseTypes = new List();
+ var typesToProcess = new Queue();
+ typesToProcess.Enqueue(type);
+
+ while (typesToProcess.Count > 0)
+ {
+ type = typesToProcess.Dequeue();
+ baseTypes.Add(type);
+
+ if (!type.IsGenericTypeDefinition
+ && !type.IsInterface)
+ {
+ if (type.BaseType != null)
+ {
+ typesToProcess.Enqueue(type.BaseType);
+ }
+
+ if (type.IsConstructedGenericType)
+ {
+ typesToProcess.Enqueue(type.GetGenericTypeDefinition());
+ }
+
+ foreach (var @interface in GetDeclaredInterfaces(type))
+ {
+ typesToProcess.Enqueue(@interface);
+ }
+ }
+ }
+
+ return baseTypes;
+ }
+
+ ///
+ /// 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 void ConfigureProperty(IMutableProperty property)
+ {
+ var types = GetBaseTypesAndInterfacesInclusive(property.ClrType);
+ for (var i = types.Count - 1; i >= 0; i--)
+ {
+ var type = types[i];
+
+ if (_properties.TryGetValue(type, out var configuration))
+ {
+ configuration.Apply(property);
+ }
+ }
+ }
+
+ ///
+ /// 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 PropertyConfiguration GetOrAddProperty(Type type)
+ {
+ if (type.UnwrapNullableType() == typeof(object)
+ || type == Model.DefaultPropertyBagType)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), TypeConfigurationType.Property));
+ }
+
+ var property = FindProperty(type);
+
+ if (property == null)
+ {
+ RemoveIgnored(type);
+
+ property = new PropertyConfiguration(type);
+ _properties.Add(type, property);
+ }
+
+ return property;
+ }
+
+ ///
+ /// 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 PropertyConfiguration? FindProperty(Type type)
+ => _properties.TryGetValue(type, out var property)
+ ? property
+ : null;
+
+ ///
+ /// 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 bool RemoveProperty(Type type)
+ => _properties.Remove(type);
+
+ ///
+ /// 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 void AddIgnored(Type type)
+ {
+ if (type.UnwrapNullableType() == typeof(int)
+ || type.UnwrapNullableType() == typeof(int?)
+ || type.UnwrapNullableType() == typeof(string)
+ || type.UnwrapNullableType() == typeof(object)
+ || type == Model.DefaultPropertyBagType)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.UnconfigurableType(type.DisplayName(fullName: false), TypeConfigurationType.Ignored));
+ }
+
+ RemoveProperty(type);
+ _ignoredTypes.Add(type);
+ }
+
+ ///
+ /// 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 bool IsIgnored(Type type)
+ => _ignoredTypes.Contains(type);
+
+ ///
+ /// 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 bool RemoveIgnored(Type type)
+ => _ignoredTypes.Remove(type);
}
}
diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs
index 13cea781f7e..e6b9861a4c1 100644
--- a/src/EFCore/Metadata/Internal/Property.cs
+++ b/src/EFCore/Metadata/Internal/Property.cs
@@ -978,9 +978,9 @@ public static bool AreCompatible(IReadOnlyList properties, EntityType
property =>
property.IsShadowProperty()
|| ((property.PropertyInfo != null
- && entityType.GetRuntimeProperties().ContainsKey(property.Name))
- || (property.FieldInfo != null
- && entityType.GetRuntimeFields().ContainsKey(property.Name))));
+ && entityType.GetRuntimeProperties().ContainsKey(property.Name))
+ || (property.FieldInfo != null
+ && entityType.GetRuntimeFields().ContainsKey(property.Name))));
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/Metadata/Internal/PropertyConfiguration.cs b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs
new file mode 100644
index 00000000000..cea60465e1b
--- /dev/null
+++ b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs
@@ -0,0 +1,199 @@
+// 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 Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
+{
+ ///
+ /// 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 class PropertyConfiguration : AnnotatableBase
+ {
+ ///
+ /// 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 PropertyConfiguration(Type clrType)
+ {
+ Check.NotNull(clrType, nameof(clrType));
+
+ ClrType = clrType;
+ }
+
+ ///
+ /// 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 Type ClrType { get; }
+
+ ///
+ /// 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 void Apply(IMutableProperty property)
+ {
+ foreach (var annotation in GetAnnotations())
+ {
+ switch (annotation.Name)
+ {
+ case CoreAnnotationNames.MaxLength:
+ property.SetMaxLength((int?)annotation.Value);
+
+ break;
+ case CoreAnnotationNames.Unicode:
+ property.SetIsUnicode((bool?)annotation.Value);
+
+ break;
+ case CoreAnnotationNames.Precision:
+ property.SetPrecision((int?)annotation.Value);
+
+ break;
+ case CoreAnnotationNames.Scale:
+ property.SetScale((int?)annotation.Value);
+
+ break;
+ case CoreAnnotationNames.ProviderClrType:
+ property.SetProviderClrType((Type?)annotation.Value);
+
+ break;
+ case CoreAnnotationNames.ValueConverterType:
+ property.SetValueConverter((Type?)annotation.Value);
+
+ break;
+ case CoreAnnotationNames.ValueComparerType:
+ property.SetValueComparer((Type?)annotation.Value);
+
+ break;
+ default:
+ if (!CoreAnnotationNames.AllNames.Contains(annotation.Name))
+ {
+ property.SetAnnotation(annotation.Name, annotation.Value);
+ }
+ break;
+ }
+ }
+ }
+
+ ///
+ /// 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 void SetMaxLength(int? maxLength)
+ {
+ if (maxLength != null
+ && maxLength < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(maxLength));
+ }
+
+ this[CoreAnnotationNames.MaxLength] = maxLength;
+ }
+
+ ///
+ /// 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 void SetIsUnicode(bool? unicode)
+ => this[CoreAnnotationNames.Unicode] = unicode;
+
+ ///
+ /// 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 void SetPrecision(int? precision)
+ {
+ if (precision != null && precision < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(precision));
+ }
+
+ this[CoreAnnotationNames.Precision] = precision;
+ }
+
+ ///
+ /// 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 void SetScale(int? scale)
+ {
+ if (scale != null && scale < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(scale));
+ }
+
+ this[CoreAnnotationNames.Scale] = scale;
+ }
+
+ ///
+ /// 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 void SetProviderClrType(Type? providerClrType)
+ => this[CoreAnnotationNames.ProviderClrType] = providerClrType;
+
+ ///
+ /// 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 void SetValueConverter(Type? converterType)
+ {
+ if (converterType != null)
+ {
+ if (!typeof(ValueConverter).IsAssignableFrom(converterType))
+ {
+ throw new InvalidOperationException(
+ CoreStrings.BadValueConverterType(converterType.ShortDisplayName(), typeof(ValueConverter).ShortDisplayName()));
+ }
+ }
+
+ this[CoreAnnotationNames.ValueConverterType] = converterType;
+ }
+
+ ///
+ /// 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 void SetValueComparer(Type? comparerType)
+ {
+ if (comparerType != null)
+ {
+ if (!typeof(ValueComparer).IsAssignableFrom(comparerType))
+ {
+ throw new InvalidOperationException(
+ CoreStrings.BadValueComparerType(comparerType.ShortDisplayName(), typeof(ValueComparer).ShortDisplayName()));
+ }
+ }
+
+ this[CoreAnnotationNames.ValueComparerType] = comparerType;
+ }
+ }
+}
diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs
index 641b17930b8..e7109d16791 100644
--- a/src/EFCore/Metadata/Internal/TypeBase.cs
+++ b/src/EFCore/Metadata/Internal/TypeBase.cs
@@ -27,8 +27,8 @@ private readonly Dictionary _ignoredMembers
private bool _indexerPropertyInitialized;
private PropertyInfo? _indexerPropertyInfo;
- private Dictionary? _runtimeProperties;
- private Dictionary? _runtimeFields;
+ private SortedDictionary? _runtimeProperties;
+ private SortedDictionary? _runtimeFields;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -145,7 +145,7 @@ public virtual IReadOnlyDictionary GetRuntimeProperties()
{
if (_runtimeProperties == null)
{
- var runtimeProperties = new Dictionary(StringComparer.Ordinal);
+ var runtimeProperties = new SortedDictionary(StringComparer.Ordinal);
foreach (var property in ClrType.GetRuntimeProperties())
{
if (!property.IsStatic()
@@ -171,7 +171,7 @@ public virtual IReadOnlyDictionary GetRuntimeFields()
{
if (_runtimeFields == null)
{
- var runtimeFields = new Dictionary(StringComparer.Ordinal);
+ var runtimeFields = new SortedDictionary(StringComparer.Ordinal);
foreach (var field in ClrType.GetRuntimeFields())
{
if (!field.IsStatic
diff --git a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs
index c26b371f1e5..61423f57074 100644
--- a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs
+++ b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs
@@ -20,7 +20,7 @@ public static class TypeBaseExtensions
/// 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 static IReadOnlyDictionary GetRuntimeProperties(this IConventionTypeBase type)
+ public static IReadOnlyDictionary GetRuntimeProperties(this IReadOnlyTypeBase type)
=> ((TypeBase)type).GetRuntimeProperties();
///
@@ -29,7 +29,7 @@ public static IReadOnlyDictionary GetRuntimeProperties(thi
/// 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 static IReadOnlyDictionary GetRuntimeFields(this IConventionTypeBase type)
+ public static IReadOnlyDictionary GetRuntimeFields(this IReadOnlyTypeBase type)
=> ((TypeBase)type).GetRuntimeFields();
}
}
diff --git a/src/EFCore/Metadata/Internal/TypeConfigurationType.cs b/src/EFCore/Metadata/Internal/TypeConfigurationType.cs
new file mode 100644
index 00000000000..eb31a441918
--- /dev/null
+++ b/src/EFCore/Metadata/Internal/TypeConfigurationType.cs
@@ -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.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
+{
+ ///
+ /// 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 enum TypeConfigurationType
+ {
+ ///
+ /// 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.
+ ///
+ Ignored,
+
+ ///
+ /// 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.
+ ///
+ EntityType,
+
+ ///
+ /// 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.
+ ///
+ SharedTypeEntityType,
+
+ ///
+ /// 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.
+ ///
+ OwnedEntityType,
+
+ ///
+ /// 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.
+ ///
+ Property,
+
+ ///
+ /// 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.
+ ///
+ ServiceProperty
+ }
+}
diff --git a/src/EFCore/Metadata/Internal/TypeConfigurationTypeExtensions.cs b/src/EFCore/Metadata/Internal/TypeConfigurationTypeExtensions.cs
new file mode 100644
index 00000000000..15a2dbc9506
--- /dev/null
+++ b/src/EFCore/Metadata/Internal/TypeConfigurationTypeExtensions.cs
@@ -0,0 +1,36 @@
+// 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
+{
+ ///
+ /// 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 static class TypeConfigurationTypeExtensions
+ {
+ ///
+ /// 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 static bool? IsEntityType(this TypeConfigurationType? configurationType)
+ => configurationType == null
+ ? null
+ : configurationType.Value.IsEntityType();
+
+ ///
+ /// 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 static bool IsEntityType(this TypeConfigurationType configurationType)
+ => configurationType == TypeConfigurationType.EntityType
+ || configurationType == TypeConfigurationType.SharedTypeEntityType
+ || configurationType == TypeConfigurationType.OwnedEntityType;
+ }
+}
diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs
index 56f52bb45b9..5d1395f62d5 100644
--- a/src/EFCore/ModelConfigurationBuilder.cs
+++ b/src/EFCore/ModelConfigurationBuilder.cs
@@ -1,8 +1,10 @@
// 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.ComponentModel;
using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
@@ -45,6 +47,98 @@ public ModelConfigurationBuilder(ConventionSet conventions)
[EntityFrameworkInternal]
protected virtual ModelConfiguration ModelConfiguration => _modelConfiguration;
+ ///
+ /// Prevents the conventions from the given type from discovering properties of the given or derived types.
+ ///
+ /// The type to be ignored.
+ ///
+ /// The same instance so that additional configuration calls can be chained.
+ ///
+ public virtual ModelConfigurationBuilder IgnoreAny()
+ => IgnoreAny(typeof(TEntity));
+
+ ///
+ /// Prevents the conventions from the given type from discovering properties of the given or derived types.
+ ///
+ /// The type to be ignored.
+ ///
+ /// The same instance so that additional configuration calls can be chained.
+ ///
+ public virtual ModelConfigurationBuilder IgnoreAny(Type type)
+ {
+ Check.NotNull(type, nameof(type));
+
+ _modelConfiguration.AddIgnored(type);
+
+ return this;
+ }
+
+ ///
+ /// Marks the given and derived types as corresponding to entity type properties.
+ ///
+ /// The property type to be configured.
+ /// An object that can be used to provide the configuration defaults for the properties.
+ public virtual PropertiesConfigurationBuilder Properties()
+ {
+ var property = _modelConfiguration.GetOrAddProperty(typeof(TProperty));
+
+ return new PropertiesConfigurationBuilder(property);
+ }
+
+ ///
+ /// Marks the given and derived types as corresponding to entity type properties.
+ ///
+ /// The property type to be configured.
+ /// An action that performs configuration of the property.
+ ///
+ /// The same instance so that additional configuration calls can be chained.
+ ///
+ public virtual ModelConfigurationBuilder Properties(
+ Action> buildAction)
+ {
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ var propertyBuilder = Properties();
+ buildAction(propertyBuilder);
+
+ return this;
+ }
+
+ ///
+ /// Marks the given and derived types as corresponding to entity type properties.
+ ///
+ /// The property type to be configured.
+ /// An object that can be used to configure the property.
+ public virtual PropertiesConfigurationBuilder Properties(Type propertyType)
+ {
+ Check.NotNull(propertyType, nameof(propertyType));
+
+ var property = _modelConfiguration.GetOrAddProperty(propertyType);
+
+ return new PropertiesConfigurationBuilder(property);
+ }
+
+ ///
+ /// Marks the given and derived types as corresponding to entity type properties.
+ ///
+ /// The property type to be configured.
+ /// An action that performs configuration of the property.
+ ///
+ /// The same instance so that additional configuration calls can be chained.
+ ///
+ public virtual ModelConfigurationBuilder Properties(
+ Type propertyType,
+ Action buildAction)
+ {
+ Check.NotNull(propertyType, nameof(propertyType));
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ var propertyBuilder = Properties(propertyType);
+ buildAction(propertyBuilder);
+
+ return this;
+ }
+
///
/// Creates the configured used to create the model. This is done automatically when using
/// ; this method allows it to be run
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 2c9c141ffaa..3d94e5ceb95 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -2669,6 +2669,14 @@ public static string TranslationFailedWithDetails(object? expression, object? de
GetString("TranslationFailedWithDetails", nameof(expression), nameof(details)),
expression, details);
+ ///
+ /// The type '{type}' has been configured as '{typeConfiguration}', this conflicts with type '{otherType}' configured as '{otherTypeConfiguration}'. All base types and implemented interfaces must have the same configuration type.
+ ///
+ public static string TypeConfigurationConflict(object? type, object? typeConfiguration, object? otherType, object? otherTypeConfiguration)
+ => string.Format(
+ GetString("TypeConfigurationConflict", nameof(type), nameof(typeConfiguration), nameof(otherType), nameof(otherTypeConfiguration)),
+ type, typeConfiguration, otherType, otherTypeConfiguration);
+
///
/// The type '{type}' has not been configured as a shared type in the model. Before calling 'UsingEntity' add the entity type in the model as a shared entity.
///
@@ -2693,6 +2701,14 @@ public static string UnableToSetIsUnique(object? isUnique, object? navigationNam
GetString("UnableToSetIsUnique", nameof(isUnique), "1_navigationName", "2_entityType"),
isUnique, navigationName, entityType);
+ ///
+ /// The type '{type}' cannot be configured as '{configuration}'. The current model building logic is unable to honor this configuration.
+ ///
+ public static string UnconfigurableType(object? type, object? configuration)
+ => string.Format(
+ GetString("UnconfigurableType", nameof(type), nameof(configuration)),
+ type, configuration);
+
///
/// Unhandled expression node type '{nodeType}'.
///
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 6a703d51ccb..150a14f0b98 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1457,6 +1457,9 @@
The LINQ expression '{expression}' could not be translated. Additional information: {details} Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
+
+ The type '{type}' has been configured as '{typeConfiguration}', this conflicts with type '{otherType}' configured as '{otherTypeConfiguration}'. All base types and implemented interfaces must have the same configuration type.
+
The type '{type}' has not been configured as a shared type in the model. Before calling 'UsingEntity' add the entity type in the model as a shared entity.
@@ -1466,6 +1469,9 @@
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.
+
Unhandled expression node type '{nodeType}'.
diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs
index 073d8c32f17..6564cdd58f5 100644
--- a/src/EFCore/Storage/TypeMappingSource.cs
+++ b/src/EFCore/Storage/TypeMappingSource.cs
@@ -84,7 +84,6 @@ protected TypeMappingSource(TypeMappingSourceDependencies dependencies)
if (mapping == null)
{
var sourceType = info.ClrType;
-
if (sourceType != null)
{
foreach (var converterInfo in Dependencies
diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs
index ff2409c9b6e..467584d0270 100644
--- a/src/Shared/SharedTypeExtensions.cs
+++ b/src/Shared/SharedTypeExtensions.cs
@@ -47,7 +47,8 @@ public static bool IsNullableType(this Type type)
=> !type.IsValueType || type.IsNullableValueType();
public static bool IsValidEntityType(this Type type)
- => type.IsClass;
+ => type.IsClass
+ && !type.IsArray;
public static bool IsPropertyBagType(this Type type)
{
diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
index 99d3b08bfbd..3cf86eb58c3 100644
--- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
+++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
@@ -30,6 +31,30 @@ public override void Properties_specified_by_string_are_shadow_properties_unless
// Fails due to extra shadow properties
}
+ [ConditionalFact]
+ public override void Properties_can_have_provider_type_set_for_type()
+ {
+ var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion());
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.Up);
+ b.Property(e => e.Down);
+ b.Property("Charm");
+ b.Property("Strange");
+ b.Property("__id").HasConversion((Type)null);
+ });
+
+ var model = modelBuilder.FinalizeModel();
+ var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks));
+
+ Assert.Null(entityType.FindProperty("Up").GetProviderClrType());
+ Assert.Same(typeof(byte[]), entityType.FindProperty("Down").GetProviderClrType());
+ Assert.Null(entityType.FindProperty("Charm").GetProviderClrType());
+ Assert.Same(typeof(byte[]), entityType.FindProperty("Strange").GetProviderClrType());
+ }
+
[ConditionalFact]
public virtual void Partition_key_is_added_to_the_keys()
{
diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/RelationalPropertyMappingValidationConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/RelationalPropertyMappingValidationConventionTest.cs
deleted file mode 100644
index 56ae9e337f3..00000000000
--- a/test/EFCore.Relational.Tests/Metadata/Conventions/Internal/RelationalPropertyMappingValidationConventionTest.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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 Microsoft.EntityFrameworkCore.Diagnostics;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-using Microsoft.EntityFrameworkCore.Storage;
-using Microsoft.EntityFrameworkCore.TestUtilities;
-using Xunit;
-
-// ReSharper disable InconsistentNaming
-namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal
-{
- public class RelationalPropertyMappingValidationConventionTest : PropertyMappingValidationConventionTest
- {
- [ConditionalFact]
- public void Throws_when_added_property_is_not_mapped_to_store()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(NonPrimitiveAsPropertyEntity), ConfigurationSource.Convention);
- entityTypeBuilder.Property(typeof(Tuple), "LongProperty", ConfigurationSource.Explicit);
- entityTypeBuilder.Ignore(nameof(NonPrimitiveAsPropertyEntity.Property), ConfigurationSource.Explicit);
-
- Assert.Equal(
- CoreStrings.PropertyNotMapped(
- typeof(NonPrimitiveAsPropertyEntity).ShortDisplayName(), "LongProperty", typeof(Tuple).ShortDisplayName()),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- [ConditionalFact]
- public void Throws_when_added_property_is_not_mapped_to_store_even_if_configured_to_use_column_type()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(NonPrimitiveNonNavigationAsPropertyEntity), ConfigurationSource.Convention);
- entityTypeBuilder.Property(typeof(Tuple), "LongProperty", ConfigurationSource.Explicit)
- .HasColumnType("some_int_mapping");
-
- Assert.Equal(
- CoreStrings.PropertyNotMapped(
- typeof(NonPrimitiveNonNavigationAsPropertyEntity).ShortDisplayName(), "LongProperty",
- typeof(Tuple).ShortDisplayName()),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- protected override TestHelpers TestHelpers
- => RelationalTestHelpers.Instance;
-
- protected override IModelValidator CreateModelValidator()
- {
- var typeMappingSource = new TestRelationalTypeMappingSource(
- TestServiceFactory.Instance.Create(),
- TestServiceFactory.Instance.Create());
-
- return new RelationalModelValidator(
- new ModelValidatorDependencies(
- typeMappingSource,
- new MemberClassifier(
- typeMappingSource,
- TestServiceFactory.Instance.Create())),
- new RelationalModelValidatorDependencies(typeMappingSource));
- }
- }
-}
diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
index 8531d6afb74..328a3eae132 100644
--- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
+++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs
@@ -76,6 +76,96 @@ public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_prop
Assert.Null(index.GetFilter());
}
+ [ConditionalFact]
+ public virtual void Can_set_store_type_for_property_type()
+ {
+ var modelBuilder = CreateModelBuilder(c =>
+ {
+ c.Properties().HaveColumnType("smallint");
+ c.Properties().HaveColumnType("nchar(max)");
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property("Charm");
+ b.Property("Strange");
+ b.Property("Top");
+ b.Property("Bottom");
+ });
+
+ var model = modelBuilder.FinalizeModel();
+ var entityType = model.FindEntityType(typeof(Quarks));
+
+ 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());
+ Assert.Equal("nchar(max)", entityType.FindProperty("Strange").GetColumnType());
+ Assert.Equal("smallint", entityType.FindProperty("Top").GetColumnType());
+ Assert.Equal("nchar(max)", entityType.FindProperty("Bottom").GetColumnType());
+ }
+
+ [ConditionalFact]
+ public virtual void Can_set_fixed_length_for_property_type()
+ {
+ var modelBuilder = CreateModelBuilder(c =>
+ {
+ c.Properties().AreFixedLength(false);
+ c.Properties().AreFixedLength();
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property("Charm");
+ b.Property("Strange");
+ b.Property("Top");
+ b.Property("Bottom");
+ });
+
+ var model = modelBuilder.FinalizeModel();
+ var entityType = model.FindEntityType(typeof(Quarks));
+
+ Assert.False(entityType.FindProperty(Customer.IdProperty.Name).IsFixedLength());
+ Assert.False(entityType.FindProperty("Up").IsFixedLength());
+ Assert.True(entityType.FindProperty("Down").IsFixedLength());
+ Assert.False(entityType.FindProperty("Charm").IsFixedLength());
+ Assert.True(entityType.FindProperty("Strange").IsFixedLength());
+ Assert.False(entityType.FindProperty("Top").IsFixedLength());
+ Assert.True(entityType.FindProperty("Bottom").IsFixedLength());
+ }
+
+ [ConditionalFact]
+ public virtual void Can_set_collation_for_property_type()
+ {
+ var modelBuilder = CreateModelBuilder(c =>
+ {
+ c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS");
+ c.Properties().UseCollation("Latin1_General_BIN");
+ });
+
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property("Charm");
+ b.Property("Strange");
+ b.Property("Top");
+ b.Property("Bottom");
+ });
+
+ var model = modelBuilder.FinalizeModel();
+ var entityType = model.FindEntityType(typeof(Quarks));
+
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name).GetCollation());
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up").GetCollation());
+ Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down").GetCollation());
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm").GetCollation());
+ Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange").GetCollation());
+ Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top").GetCollation());
+ Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom").GetCollation());
+ }
+
protected override TestModelBuilder CreateModelBuilder(Action configure = null)
=> CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure);
}
@@ -92,12 +182,12 @@ public void Can_use_shadow_FK_that_collides_with_convention_shadow_FK_on_other_d
.WithOne()
.HasForeignKey("ParentId");
- modelBuilder.FinalizeModel();
+ var model = modelBuilder.FinalizeModel();
- var property1 = modelBuilder.Model.FindEntityType(typeof(DisjointChildSubclass1)).FindProperty("ParentId");
+ var property1 = model.FindEntityType(typeof(DisjointChildSubclass1)).FindProperty("ParentId");
Assert.True(property1.IsForeignKey());
Assert.Equal("ParentId", property1.GetColumnBaseName());
- var property2 = modelBuilder.Model.FindEntityType(typeof(DisjointChildSubclass2)).FindProperty("ParentId");
+ var property2 = model.FindEntityType(typeof(DisjointChildSubclass2)).FindProperty("ParentId");
Assert.True(property2.IsForeignKey());
Assert.Equal("DisjointChildSubclass2_ParentId", property2.GetColumnBaseName());
}
@@ -111,11 +201,11 @@ public void Inherited_clr_properties_are_mapped_to_the_same_column()
modelBuilder.Entity();
modelBuilder.Entity();
- modelBuilder.FinalizeModel();
+ var model = modelBuilder.FinalizeModel();
- var property1 = modelBuilder.Model.FindEntityType(typeof(DisjointChildSubclass1)).FindProperty(nameof(Child.Name));
+ var property1 = model.FindEntityType(typeof(DisjointChildSubclass1)).FindProperty(nameof(Child.Name));
Assert.Equal(nameof(Child.Name), property1.GetColumnBaseName());
- var property2 = modelBuilder.Model.FindEntityType(typeof(DisjointChildSubclass2)).FindProperty(nameof(Child.Name));
+ var property2 = model.FindEntityType(typeof(DisjointChildSubclass2)).FindProperty(nameof(Child.Name));
Assert.Equal(nameof(Child.Name), property2.GetColumnBaseName());
}
diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs
index e52d4bac0d3..00b8e7de83a 100644
--- a/test/EFCore.Tests/ApiConsistencyTest.cs
+++ b/test/EFCore.Tests/ApiConsistencyTest.cs
@@ -162,6 +162,7 @@ protected override void Initialize()
typeof(IConventionAnnotatable).GetMethod(nameof(IConventionAnnotatable.SetOrRemoveAnnotation)),
typeof(IConventionAnnotatable).GetMethod(nameof(IConventionAnnotatable.AddAnnotations)),
typeof(IMutableAnnotatable).GetMethod(nameof(IMutableAnnotatable.AddAnnotations)),
+ typeof(IConventionModel).GetMethod(nameof(IConventionModel.IsIgnoredType)),
typeof(IConventionModel).GetMethod(nameof(IConventionModel.IsShared)),
typeof(IConventionModel).GetMethod(nameof(IConventionModel.AddOwned)),
typeof(IConventionModel).GetMethod(nameof(IConventionModel.AddShared)),
diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs
index 11aff80b3e8..13dfd09d990 100644
--- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs
+++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.PropertyMapping.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Threading;
using Microsoft.EntityFrameworkCore.Diagnostics;
@@ -58,7 +59,7 @@ public virtual void Throws_when_primitive_type_property_is_not_added_or_ignored(
public virtual void Throws_when_nonprimitive_value_type_property_is_not_added_or_ignored()
{
var modelBuilder = CreateConventionlessModelBuilder();
- modelBuilder.Entity(typeof(NonPrimitiveValueTypePropertyEntity));
+ modelBuilder.Entity(typeof(NonPrimitiveValueTypePropertyEntity)).HasNoKey();
Assert.Equal(
CoreStrings.PropertyNotAdded(
@@ -66,6 +67,16 @@ public virtual void Throws_when_nonprimitive_value_type_property_is_not_added_or
Assert.Throws(() => Validate(modelBuilder)).Message);
}
+ [ConditionalFact]
+ public virtual void Does_not_throw_when_nonprimitive_value_type_property_type_is_ignored()
+ {
+ var modelBuilder = CreateConventionlessModelBuilder(
+ configurationBuilder => configurationBuilder.IgnoreAny());
+ modelBuilder.Entity(typeof(NonPrimitiveValueTypePropertyEntity)).HasNoKey();
+
+ Validate(modelBuilder);
+ }
+
[ConditionalFact]
public virtual void Throws_when_keyless_type_property_is_not_added_or_ignored()
{
@@ -140,6 +151,16 @@ public virtual void Does_not_throw_when_navigation_is_ignored()
Validate(modelBuilder);
}
+ [ConditionalFact]
+ public virtual void Does_not_throw_when_navigation_type_is_ignored()
+ {
+ var modelBuilder = CreateConventionlessModelBuilder(
+ configurationBuilder => configurationBuilder.IgnoreAny());
+ modelBuilder.Entity(typeof(NavigationEntity)).HasNoKey();
+
+ Validate(modelBuilder);
+ }
+
[ConditionalFact]
public virtual void Does_not_throw_when_navigation_target_entity_is_ignored()
{
@@ -171,7 +192,7 @@ public virtual void Does_not_throw_when_explicit_navigation_is_not_added()
public virtual void Throws_when_interface_type_property_is_not_added_or_ignored()
{
var modelBuilder = CreateConventionlessModelBuilder();
- modelBuilder.Entity(typeof(InterfaceNavigationEntity));
+ modelBuilder.Entity(typeof(InterfaceNavigationEntity)).HasNoKey();
Assert.Equal(
CoreStrings.InterfacePropertyNotAdded(
@@ -181,6 +202,36 @@ public virtual void Throws_when_interface_type_property_is_not_added_or_ignored(
Assert.Throws(() => Validate(modelBuilder)).Message);
}
+ [ConditionalFact]
+ public virtual void Does_not_throw_when_interface_collection_type_property_type_is_ignored()
+ {
+ var modelBuilder = CreateConventionlessModelBuilder(
+ configurationBuilder => configurationBuilder.IgnoreAny());
+ modelBuilder.Entity(typeof(InterfaceNavigationEntity)).HasNoKey();
+
+ Validate(modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Does_not_throw_when_interface_generic_type_property_type_is_ignored()
+ {
+ var modelBuilder = CreateConventionlessModelBuilder(
+ configurationBuilder => configurationBuilder.IgnoreAny(typeof(IList<>)));
+ modelBuilder.Entity(typeof(InterfaceNavigationEntity)).HasNoKey();
+
+ Validate(modelBuilder);
+ }
+
+ [ConditionalFact]
+ public virtual void Does_not_throw_when_interface_base_type_property_type_is_ignored()
+ {
+ var modelBuilder = CreateConventionlessModelBuilder(
+ configurationBuilder => configurationBuilder.IgnoreAny());
+ modelBuilder.Entity(typeof(InterfaceNavigationEntity)).HasNoKey();
+
+ Validate(modelBuilder);
+ }
+
[ConditionalFact]
public virtual void Does_not_throw_when_non_candidate_property_is_not_added()
{
diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
index f8df09899c7..b76a2ddae1e 100644
--- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
+++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
@@ -1111,7 +1111,7 @@ public virtual void Detects_missing_signed_integer_key_values_in_seeds()
[InlineData(false)]
public virtual void Detects_duplicate_seeds(bool sensitiveDataLoggingEnabled)
{
- var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled);
+ var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled: sensitiveDataLoggingEnabled);
modelBuilder.Entity().HasData(
new A { Id = 1 });
modelBuilder.Entity().HasData(
@@ -1130,7 +1130,7 @@ public virtual void Detects_duplicate_seeds(bool sensitiveDataLoggingEnabled)
[InlineData(false)]
public virtual void Detects_incompatible_values(bool sensitiveDataLoggingEnabled)
{
- var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled);
+ var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled: sensitiveDataLoggingEnabled);
modelBuilder.Entity(
e =>
{
@@ -1151,7 +1151,7 @@ public virtual void Detects_incompatible_values(bool sensitiveDataLoggingEnabled
[InlineData(false)]
public virtual void Detects_reference_navigations_in_seeds(bool sensitiveDataLoggingEnabled)
{
- var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled);
+ var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled: sensitiveDataLoggingEnabled);
modelBuilder.Entity(
e =>
{
@@ -1181,7 +1181,7 @@ public virtual void Detects_reference_navigations_in_seeds(bool sensitiveDataLog
[InlineData(false)]
public virtual void Detects_reference_navigations_in_seeds2(bool sensitiveDataLoggingEnabled)
{
- var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled);
+ var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled: sensitiveDataLoggingEnabled);
modelBuilder.Entity(
e =>
{
@@ -1213,7 +1213,7 @@ public virtual void Detects_reference_navigations_in_seeds2(bool sensitiveDataLo
[InlineData(false)]
public virtual void Detects_collection_navigations_in_seeds(bool sensitiveDataLoggingEnabled)
{
- var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled);
+ var modelBuilder = CreateConventionalModelBuilder(sensitiveDataLoggingEnabled: sensitiveDataLoggingEnabled);
modelBuilder.Entity(
e =>
{
diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
index cd4ff06b5ef..340cf72987d 100644
--- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
+++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs
@@ -315,14 +315,21 @@ protected virtual IModel Validate(TestHelpers.TestModelBuilder modelBuilder, boo
new NullDbContextLogger());
}
- protected virtual TestHelpers.TestModelBuilder CreateConventionalModelBuilder(bool sensitiveDataLoggingEnabled = false)
+ protected virtual TestHelpers.TestModelBuilder CreateConventionalModelBuilder(
+ Action configure = null, bool sensitiveDataLoggingEnabled = false)
=> TestHelpers.CreateConventionBuilder(
- CreateModelLogger(sensitiveDataLoggingEnabled), CreateValidationLogger(sensitiveDataLoggingEnabled));
+ CreateModelLogger(sensitiveDataLoggingEnabled), CreateValidationLogger(sensitiveDataLoggingEnabled),
+ configurationBuilder => configure?.Invoke(configurationBuilder));
- protected virtual TestHelpers.TestModelBuilder CreateConventionlessModelBuilder(bool sensitiveDataLoggingEnabled = false)
+ protected virtual TestHelpers.TestModelBuilder CreateConventionlessModelBuilder(
+ Action configure = null, bool sensitiveDataLoggingEnabled = false)
=> TestHelpers.CreateConventionBuilder(
CreateModelLogger(sensitiveDataLoggingEnabled), CreateValidationLogger(sensitiveDataLoggingEnabled),
- configurationBuilder => configurationBuilder.RemoveAllConventions());
+ configurationBuilder =>
+ {
+ configure?.Invoke(configurationBuilder);
+ configurationBuilder.RemoveAllConventions();
+ });
protected virtual TestHelpers TestHelpers
=> InMemoryTestHelpers.Instance;
diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs
deleted file mode 100644
index 1473e1dabc5..00000000000
--- a/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs
+++ /dev/null
@@ -1,292 +0,0 @@
-// 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;
-using System.Linq;
-using System.Reflection;
-using System.Threading;
-using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
-using Microsoft.EntityFrameworkCore.Diagnostics;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.InMemory.Storage.Internal;
-using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-using Microsoft.EntityFrameworkCore.Query;
-using Microsoft.EntityFrameworkCore.Storage;
-using Microsoft.EntityFrameworkCore.TestUtilities;
-using Microsoft.EntityFrameworkCore.Update;
-using Microsoft.Extensions.DependencyInjection;
-using Xunit;
-
-// ReSharper disable InconsistentNaming
-namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
-{
- public class PropertyMappingValidationConventionTest : ModelValidatorTestBase
- {
- [ConditionalFact]
- public virtual void Throws_when_added_property_is_not_of_primitive_type()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(NonPrimitiveAsPropertyEntity), ConfigurationSource.Convention);
- entityTypeBuilder.Property(
- typeof(NavigationAsProperty), nameof(NonPrimitiveAsPropertyEntity.Property), ConfigurationSource.Convention);
-
- Assert.Equal(
- CoreStrings.PropertyNotMapped(
- typeof(NonPrimitiveAsPropertyEntity).ShortDisplayName(),
- nameof(NonPrimitiveAsPropertyEntity.Property),
- typeof(NavigationAsProperty).ShortDisplayName()),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_added_shadow_property_by_convention_is_not_of_primitive_type()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(NonPrimitiveAsPropertyEntity), ConfigurationSource.Convention);
- entityTypeBuilder.Property(typeof(NavigationAsProperty), "ShadowProperty", ConfigurationSource.Convention);
- entityTypeBuilder.Ignore(nameof(NonPrimitiveAsPropertyEntity.Property), ConfigurationSource.Explicit);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- [ConditionalFact]
- public virtual void Throws_when_primitive_type_property_is_not_added_or_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- modelBuilder.Entity(typeof(PrimitivePropertyEntity), ConfigurationSource.Convention);
-
- Assert.Equal(
- CoreStrings.PropertyNotAdded(
- typeof(PrimitivePropertyEntity).ShortDisplayName(), "Property", typeof(int).DisplayName()),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- [ConditionalFact]
- public virtual void Throws_when_nonprimitive_value_type_property_is_not_added_or_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- modelBuilder.Entity(typeof(NonPrimitiveValueTypePropertyEntity), ConfigurationSource.Convention);
-
- Assert.Equal(
- CoreStrings.PropertyNotAdded(
- typeof(NonPrimitiveValueTypePropertyEntity).ShortDisplayName(), "Property", typeof(CancellationToken).Name),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- [ConditionalFact]
- public virtual void Throws_when_keyless_type_property_is_not_added_or_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- modelBuilder.Entity(typeof(NonPrimitiveReferenceTypePropertyEntity), ConfigurationSource.Convention);
-
- Assert.Equal(
- CoreStrings.PropertyNotAdded(
- typeof(NonPrimitiveReferenceTypePropertyEntity).ShortDisplayName(),
- nameof(NonPrimitiveReferenceTypePropertyEntity.Property),
- typeof(ICollection).ShortDisplayName()),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_primitive_type_property_is_added()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(PrimitivePropertyEntity), ConfigurationSource.Convention);
- entityTypeBuilder.Property(typeof(int), "Property", ConfigurationSource.Convention);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_primitive_type_property_is_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(PrimitivePropertyEntity), ConfigurationSource.Convention);
- entityTypeBuilder.Ignore("Property", ConfigurationSource.DataAnnotation);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- [ConditionalFact]
- public virtual void Throws_when_navigation_is_not_added_or_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- modelBuilder.Entity(typeof(NavigationEntity), ConfigurationSource.Convention);
- modelBuilder.Entity(typeof(PrimitivePropertyEntity), ConfigurationSource.Convention);
-
- Assert.Equal(
- CoreStrings.NavigationNotAdded(
- typeof(NavigationEntity).ShortDisplayName(), "Navigation", typeof(PrimitivePropertyEntity).Name),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_navigation_is_added()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(NavigationEntity), ConfigurationSource.Convention);
- var referencedEntityTypeBuilder = modelBuilder.Entity(typeof(PrimitivePropertyEntity), ConfigurationSource.Convention);
- referencedEntityTypeBuilder.Ignore("Property", ConfigurationSource.DataAnnotation);
- entityTypeBuilder.HasRelationship(referencedEntityTypeBuilder.Metadata, "Navigation", null, ConfigurationSource.Convention);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_navigation_is_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(NavigationEntity), ConfigurationSource.Convention);
- entityTypeBuilder.Ignore("Navigation", ConfigurationSource.DataAnnotation);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_navigation_target_entity_is_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- modelBuilder.Entity(typeof(NavigationEntity), ConfigurationSource.Convention);
- modelBuilder.Ignore(typeof(PrimitivePropertyEntity), ConfigurationSource.Convention);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_explicit_navigation_is_not_added()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- var entityTypeBuilder = modelBuilder.Entity(typeof(ExplicitNavigationEntity), ConfigurationSource.Convention);
- var referencedEntityTypeBuilder = modelBuilder.Entity(typeof(PrimitivePropertyEntity), ConfigurationSource.Convention);
- referencedEntityTypeBuilder.Ignore("Property", ConfigurationSource.DataAnnotation);
- entityTypeBuilder.HasRelationship(
- referencedEntityTypeBuilder.Metadata, "Navigation", null, ConfigurationSource.Convention);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- [ConditionalFact]
- public virtual void Throws_when_interface_type_property_is_not_added_or_ignored()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- modelBuilder.Entity(typeof(InterfaceNavigationEntity), ConfigurationSource.Convention);
-
- Assert.Equal(
- CoreStrings.InterfacePropertyNotAdded(
- typeof(InterfaceNavigationEntity).ShortDisplayName(),
- "Navigation",
- typeof(IList).ShortDisplayName()),
- Assert.Throws(() => CreatePropertyMappingValidator()(modelBuilder.Metadata)).Message);
- }
-
- [ConditionalFact]
- public virtual void Does_not_throw_when_non_candidate_property_is_not_added()
- {
- var modelBuilder = CreateConventionlessInternalModelBuilder();
- modelBuilder.Entity(typeof(NonCandidatePropertyEntity), ConfigurationSource.Convention);
-
- CreatePropertyMappingValidator()(modelBuilder.Metadata);
- }
-
- protected virtual Action CreatePropertyMappingValidator()
- {
- var validator = CreateModelValidator();
- var logger = new TestLogger();
-
- var validatePropertyMappingMethod = typeof(ModelValidator).GetRuntimeMethods().Single(e => e.Name == "ValidatePropertyMapping");
-
- var modelRuntimeInitializer = TestHelpers.CreateContextServices().GetRequiredService();
-
- return m =>
- {
- try
- {
- modelRuntimeInitializer.Initialize(m, designTime: false, validationLogger: null);
- validatePropertyMappingMethod.Invoke(
- validator, new object[] { m, logger });
- }
- catch (TargetInvocationException exception)
- {
- throw exception.InnerException;
- }
- };
- }
-
- protected virtual IModelValidator CreateModelValidator()
- {
- var typeMappingSource = new InMemoryTypeMappingSource(
- TestServiceFactory.Instance.Create());
-
- return new ModelValidator(
- new ModelValidatorDependencies(
- typeMappingSource,
- new MemberClassifier(
- typeMappingSource,
- TestServiceFactory.Instance.Create())));
- }
-
- protected class NonPrimitiveNonNavigationAsPropertyEntity
- {
- }
-
- protected class NonPrimitiveAsPropertyEntity
- {
- public NavigationAsProperty Property { get; set; }
- }
-
- protected class NavigationAsProperty
- {
- }
-
- protected class PrimitivePropertyEntity
- {
- public int Property { get; set; }
- }
-
- protected class NonPrimitiveValueTypePropertyEntity
- {
- public CancellationToken Property { get; set; }
- }
-
- protected class NonPrimitiveReferenceTypePropertyEntity
- {
- public ICollection Property { get; set; }
- }
-
- protected class NavigationEntity
- {
- public PrimitivePropertyEntity Navigation { get; set; }
- }
-
- protected class NonCandidatePropertyEntity
- {
- public static int StaticProperty { get; set; }
-
- public int _writeOnlyField = 1;
-
- public int WriteOnlyProperty
- {
- set => _writeOnlyField = value;
- }
- }
-
- protected interface INavigationEntity
- {
- PrimitivePropertyEntity Navigation { get; set; }
- }
-
- protected class ExplicitNavigationEntity : INavigationEntity
- {
- PrimitivePropertyEntity INavigationEntity.Navigation { get; set; }
-
- public PrimitivePropertyEntity Navigation { get; set; }
- }
-
- protected class InterfaceNavigationEntity
- {
- public IList Navigation { get; set; }
- }
- }
-}
diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs
index 6aa694f8e6b..00211990f85 100644
--- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs
@@ -2199,22 +2199,26 @@ public void Navigation_returns_same_value()
var foreignKeyBuilder = dependentEntityBuilder.HasRelationship(
principalEntityBuilder.Metadata, ConfigurationSource.DataAnnotation);
- Assert.True(dependentEntityBuilder.CanHaveNavigation(Order.CustomerProperty.Name, ConfigurationSource.Convention));
+ Assert.True(dependentEntityBuilder.CanHaveNavigation(
+ Order.CustomerProperty.Name, Order.CustomerProperty.GetMemberType(), ConfigurationSource.Convention));
foreignKeyBuilder = foreignKeyBuilder.HasNavigation(
Order.CustomerProperty.Name,
pointsToPrincipal: true,
ConfigurationSource.DataAnnotation);
- Assert.True(dependentEntityBuilder.CanHaveNavigation(Order.CustomerProperty.Name, ConfigurationSource.Explicit));
- Assert.True(principalEntityBuilder.CanHaveNavigation(Customer.OrdersProperty.Name, ConfigurationSource.Convention));
+ Assert.True(dependentEntityBuilder.CanHaveNavigation(
+ Order.CustomerProperty.Name, Order.CustomerProperty.GetMemberType(), ConfigurationSource.Explicit));
+ Assert.True(principalEntityBuilder.CanHaveNavigation(
+ Customer.OrdersProperty.Name, Customer.OrdersProperty.GetMemberType(), ConfigurationSource.Convention));
foreignKeyBuilder = foreignKeyBuilder.HasNavigation(
Customer.OrdersProperty.Name,
pointsToPrincipal: false,
ConfigurationSource.DataAnnotation);
- Assert.True(principalEntityBuilder.CanHaveNavigation(Customer.OrdersProperty.Name, ConfigurationSource.Explicit));
+ Assert.True(principalEntityBuilder.CanHaveNavigation(
+ Customer.OrdersProperty.Name, Customer.OrdersProperty.GetMemberType(), ConfigurationSource.Explicit));
var newForeignKeyBuilder = dependentEntityBuilder
.HasRelationship(principalEntityBuilder.Metadata, ConfigurationSource.Convention).HasNavigation(
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
index 2acab8cdf65..78ce0e36021 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
@@ -466,6 +466,9 @@ public override TestPropertyBuilder IsRequired(bool isRequired = true
public override TestPropertyBuilder HasMaxLength(int maxLength)
=> new GenericTestPropertyBuilder(PropertyBuilder.HasMaxLength(maxLength));
+ public override TestPropertyBuilder HasPrecision(int precision, int scale)
+ => new GenericTestPropertyBuilder(PropertyBuilder.HasPrecision(precision, scale));
+
public override TestPropertyBuilder IsUnicode(bool unicode = true)
=> new GenericTestPropertyBuilder(PropertyBuilder.IsUnicode(unicode));
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
index fc36aed2658..5a833a1d46f 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
@@ -543,6 +543,9 @@ public override TestPropertyBuilder IsRequired(bool isRequired = true
public override TestPropertyBuilder HasMaxLength(int maxLength)
=> new NonGenericTestPropertyBuilder(PropertyBuilder.HasMaxLength(maxLength));
+ public override TestPropertyBuilder HasPrecision(int precision, int scale)
+ => new NonGenericTestPropertyBuilder(PropertyBuilder.HasPrecision(precision, scale));
+
public override TestPropertyBuilder IsUnicode(bool unicode = true)
=> new NonGenericTestPropertyBuilder(PropertyBuilder.IsUnicode(unicode));
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs
index 3535f9758b0..6ea27d822d1 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs
@@ -379,6 +379,7 @@ public abstract class TestPropertyBuilder
public abstract TestPropertyBuilder HasAnnotation(string annotation, object? value);
public abstract TestPropertyBuilder IsRequired(bool isRequired = true);
public abstract TestPropertyBuilder HasMaxLength(int maxLength);
+ public abstract TestPropertyBuilder HasPrecision(int precision, int scale);
public abstract TestPropertyBuilder IsUnicode(bool unicode = true);
public abstract TestPropertyBuilder IsRowVersion();
public abstract TestPropertyBuilder IsConcurrencyToken(bool isConcurrencyToken = true);
diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs
index df77f975a73..01c433b42e3 100644
--- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs
+++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs
@@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Text;
@@ -311,11 +313,13 @@ public virtual void Can_set_property_annotation()
{
var modelBuilder = CreateModelBuilder();
- var propertyBuilder = modelBuilder
+ modelBuilder
.Entity()
.Property(c => c.Name).HasAnnotation("foo", "bar");
- Assert.Equal("bar", propertyBuilder.Metadata["foo"]);
+ var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)).FindProperty(nameof(Customer.Name));
+
+ Assert.Equal("bar", property["foo"]);
}
[ConditionalFact]
@@ -323,18 +327,33 @@ public virtual void Can_set_property_annotation_when_no_clr_property()
{
var modelBuilder = CreateModelBuilder();
- var propertyBuilder = modelBuilder
+ modelBuilder
.Entity()
.Property(Customer.NameProperty.Name).HasAnnotation("foo", "bar");
- Assert.Equal("bar", propertyBuilder.Metadata["foo"]);
+ var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)).FindProperty(nameof(Customer.Name));
+
+ Assert.Equal("bar", property["foo"]);
+ }
+
+ [ConditionalFact]
+ public virtual void Can_set_property_annotation_by_type()
+ {
+ var modelBuilder = CreateModelBuilder(c => c.Properties().HaveAnnotation("foo", "bar"));
+
+ var propertyBuilder = modelBuilder
+ .Entity()
+ .Property(c => c.Name).HasAnnotation("foo", "bar");
+
+ var property = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)).FindProperty(nameof(Customer.Name));
+
+ Assert.Equal("bar", property["foo"]);
}
[ConditionalFact]
public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nullable()
{
var modelBuilder = CreateModelBuilder();
- var model = modelBuilder.Model;
modelBuilder.Entity(
b =>
@@ -347,7 +366,7 @@ public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nulla
b.Property("Bottom");
});
- var entityType = (IReadOnlyEntityType)model.FindEntityType(typeof(Quarks));
+ var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks));
Assert.False(entityType.FindProperty("Up").IsNullable);
Assert.True(entityType.FindProperty("Down").IsNullable);
@@ -362,8 +381,6 @@ public virtual void Properties_can_be_ignored()
{
var modelBuilder = CreateModelBuilder();
- var entityType = (IReadOnlyEntityType)modelBuilder.Entity().Metadata;
-
modelBuilder.Entity(
b =>
{
@@ -376,11 +393,30 @@ public virtual void Properties_can_be_ignored()
b.Ignore("Shadow");
});
+ var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Quarks));
Assert.Contains(nameof(Quarks.Id), entityType.GetProperties().Select(p => p.Name));
Assert.DoesNotContain(nameof(Quarks.Up), entityType.GetProperties().Select(p => p.Name));
Assert.DoesNotContain(nameof(Quarks.Down), entityType.GetProperties().Select(p => p.Name));
}
+ [ConditionalFact]
+ public virtual void Properties_can_be_ignored_by_type()
+ {
+ var modelBuilder = CreateModelBuilder(c => c.IgnoreAny());
+
+ modelBuilder.Entity();
+
+ var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer));
+ Assert.Null(entityType.FindProperty(nameof(Customer.AlternateKey)));
+ }
+
+ [ConditionalFact]
+ public virtual void Int32_cannot_be_ignored()
+ {
+ Assert.Equal(CoreStrings.UnconfigurableType("int", "Ignored"),
+ Assert.Throws(() => CreateModelBuilder(c => c.IgnoreAny())).Message);
+ }
+
[ConditionalFact]
public virtual void Can_ignore_a_property_that_is_part_of_explicit_entity_key()
{
@@ -417,7 +453,9 @@ public virtual void Can_add_shadow_properties_when_they_have_been_ignored()
b.Property("Shadow");
});
- Assert.NotNull(modelBuilder.Model.FindEntityType(typeof(Customer)).FindProperty("Shadow"));
+ var model = modelBuilder.FinalizeModel();
+
+ Assert.NotNull(model.FindEntityType(typeof(Customer)).FindProperty("Shadow"));
}
[ConditionalFact]
@@ -440,7 +478,7 @@ public virtual void Can_override_navigations_as_properties()
public virtual void Ignoring_a_navigation_property_removes_discovered_entity_types()
{
var modelBuilder = CreateModelBuilder();
- var model = modelBuilder.Model;
+
modelBuilder.Entity(
b =>
{
@@ -448,7 +486,7 @@ public virtual void Ignoring_a_navigation_property_removes_discovered_entity_typ
b.Ignore(c => c.Orders);
});
- modelBuilder.FinalizeModel();
+ var model = modelBuilder.FinalizeModel();
Assert.Single(model.GetEntityTypes());
}
@@ -457,7 +495,7 @@ public virtual void Ignoring_a_navigation_property_removes_discovered_entity_typ
public virtual void Ignoring_a_navigation_property_removes_discovered_relationship()
{
var modelBuilder = CreateModelBuilder();
- var model = modelBuilder.Model;
+
modelBuilder.Entity(
b =>
{
@@ -466,18 +504,36 @@ public virtual void Ignoring_a_navigation_property_removes_discovered_relationsh
});
modelBuilder.Entity(b => b.Ignore(c => c.Customer));
- modelBuilder.FinalizeModel();
+ var model = modelBuilder.FinalizeModel();
Assert.Empty(model.GetEntityTypes().First().GetForeignKeys());
Assert.Empty(model.GetEntityTypes().Last().GetForeignKeys());
Assert.Equal(2, model.GetEntityTypes().Count());
}
+ [ConditionalFact]
+ public virtual void Ignoring_a_base_type_removes_relationships()
+ {
+ var modelBuilder = CreateModelBuilder(c => c.IgnoreAny());
+
+ modelBuilder.Entity();
+
+ var model = modelBuilder.FinalizeModel();
+
+ Assert.Empty(model.GetEntityTypes().Single().GetForeignKeys());
+ }
+
+ [ConditionalFact]
+ public virtual void Object_cannot_be_ignored()
+ {
+ Assert.Equal(CoreStrings.UnconfigurableType("object", "Ignored"),
+ Assert.Throws(() => CreateModelBuilder(c => c.IgnoreAny