diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs
index 37e5cc2505f..e3e42802fd9 100644
--- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs
+++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs
@@ -34,7 +34,7 @@ private ColumnMappingBaseComparer()
///
public int Compare(IColumnMappingBase x, IColumnMappingBase y)
{
- var result = y.Column.IsNullable.CompareTo(x.Column.IsNullable);
+ var result = y.Property.IsPrimaryKey().CompareTo(x.Property.IsPrimaryKey());
if (result != 0)
{
return result;
diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnNameComparer.cs b/src/EFCore.Relational/Metadata/Internal/ColumnNameComparer.cs
new file mode 100644
index 00000000000..e00aa09a1bf
--- /dev/null
+++ b/src/EFCore.Relational/Metadata/Internal/ColumnNameComparer.cs
@@ -0,0 +1,77 @@
+// 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.Diagnostics.CodeAnalysis;
+
+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 sealed class ColumnNameComparer : IComparer
+ {
+ private readonly Table _table;
+
+ ///
+ /// 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 ColumnNameComparer([NotNull] Table table)
+ {
+ _table = table;
+ }
+
+ ///
+ /// 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(string x, string y)
+ {
+ var xIndex = -1;
+ var yIndex = -1;
+
+ var columns = _table.PrimaryKey?.Columns;
+ if (columns != null)
+ {
+ for (var i = 0; i < columns.Count; i++)
+ {
+ var name = columns[i].Name;
+ if (name == x)
+ {
+ xIndex = i;
+ }
+
+ if (name == y)
+ {
+ yIndex = i;
+ }
+ }
+ }
+
+ if (xIndex == -1
+ && yIndex == -1)
+ {
+ return StringComparer.Ordinal.Compare(x, y);
+ }
+
+ if (xIndex > -1
+ && yIndex > -1)
+ {
+ return xIndex - yIndex;
+ }
+
+ return xIndex > yIndex
+ ? -1
+ : 1;
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Metadata/Internal/Table.cs b/src/EFCore.Relational/Metadata/Internal/Table.cs
index 14127b4ae9e..bde9005dde8 100644
--- a/src/EFCore.Relational/Metadata/Internal/Table.cs
+++ b/src/EFCore.Relational/Metadata/Internal/Table.cs
@@ -1,7 +1,6 @@
// 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.Diagnostics;
using System.Linq;
@@ -18,6 +17,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
///
public class Table : TableBase, ITable
{
+ private UniqueConstraint _primaryKey;
+
///
/// 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
@@ -27,6 +28,7 @@ public class Table : TableBase, ITable
public Table([NotNull] string name, [CanBeNull] string schema, [NotNull] RelationalModel model)
: base(name, schema, model)
{
+ Columns = new SortedDictionary(new ColumnNameComparer(this));
}
///
@@ -44,7 +46,47 @@ public Table([NotNull] string name, [CanBeNull] string schema, [NotNull] Relatio
/// 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 UniqueConstraint PrimaryKey { get; [param: NotNull] set; }
+ public virtual UniqueConstraint PrimaryKey
+ {
+ get => _primaryKey; [param: NotNull]
+ set
+ {
+ var oldPrimaryKey = _primaryKey;
+ if (oldPrimaryKey != null)
+ {
+ foreach (var column in oldPrimaryKey.Columns)
+ {
+ Columns.Remove(column.Name);
+ }
+ }
+
+ if (value != null)
+ {
+ foreach (var column in value.Columns)
+ {
+ Columns.Remove(column.Name);
+ }
+ }
+
+ _primaryKey = value;
+
+ if (oldPrimaryKey != null)
+ {
+ foreach (var column in oldPrimaryKey.Columns)
+ {
+ Columns.TryAdd(column.Name, column);
+ }
+ }
+
+ if (value != null)
+ {
+ foreach (var column in value.Columns)
+ {
+ Columns.TryAdd(column.Name, column);
+ }
+ }
+ }
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Metadata/Internal/TableBase.cs b/src/EFCore.Relational/Metadata/Internal/TableBase.cs
index 62c90a45f44..9475009f3e2 100644
--- a/src/EFCore.Relational/Metadata/Internal/TableBase.cs
+++ b/src/EFCore.Relational/Metadata/Internal/TableBase.cs
@@ -62,7 +62,7 @@ public TableBase([NotNull] string name, [CanBeNull] string schema, [NotNull] Rel
/// 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 SortedDictionary Columns { get; }
+ public virtual SortedDictionary Columns { get; [param: NotNull] protected set; }
= new SortedDictionary(StringComparer.Ordinal);
///
diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs
index 2e091d7cc76..bdf28fe2e9b 100644
--- a/src/EFCore.Relational/Update/ModificationCommand.cs
+++ b/src/EFCore.Relational/Update/ModificationCommand.cs
@@ -284,17 +284,27 @@ private IReadOnlyList GenerateColumnModifications()
&& (entry.EntityState == EntityState.Deleted
|| entry.EntityState == EntityState.Added);
- foreach (var property in entry.EntityType.GetProperties())
+ ITableMappingBase tableMapping = null;
+ foreach (var mapping in entry.EntityType.GetTableMappings())
{
- var columnMapping = property.GetTableColumnMappings()
- .Where(m => m.TableMapping.Table.Name == TableName && m.TableMapping.Table.Schema == Schema)
- .FirstOrDefault();
- if (columnMapping == null)
+ var table = ((ITableMappingBase)mapping).Table;
+ if (table.Name == TableName
+ && table.Schema == Schema)
{
- continue;
+ tableMapping = mapping;
+ break;
}
+ }
- var column = columnMapping.Column;
+ if (tableMapping == null)
+ {
+ continue;
+ }
+
+ foreach (var columnMapping in tableMapping.ColumnMappings)
+ {
+ var property = columnMapping.Property;
+ var column = (IColumn)columnMapping.Column;
var isKey = property.IsPrimaryKey();
var isCondition = !adding && (isKey || property.IsConcurrencyToken);
var readValue = state != EntityState.Deleted && entry.IsStoreGenerated(property);
diff --git a/src/EFCore/Extensions/ConventionPropertyExtensions.cs b/src/EFCore/Extensions/ConventionPropertyExtensions.cs
index 640f3c9703c..06f2815befa 100644
--- a/src/EFCore/Extensions/ConventionPropertyExtensions.cs
+++ b/src/EFCore/Extensions/ConventionPropertyExtensions.cs
@@ -8,6 +8,7 @@
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.ValueGeneration;
@@ -81,13 +82,26 @@ public static IConventionKey FindContainingPrimaryKey([NotNull] this IConvention
public static IEnumerable GetContainingKeys([NotNull] this IConventionProperty property)
=> ((Property)property).GetContainingKeys();
+ ///
+ /// Sets the for the given property
+ ///
+ /// The property.
+ /// The for this property.
+ /// Indicates whether the configuration was specified using a data annotation.
+ public static CoreTypeMapping SetTypeMapping(
+ [NotNull] this IConventionProperty property,
+ [NotNull] CoreTypeMapping typeMapping,
+ bool fromDataAnnotation = false)
+ => ((Property)property).SetTypeMapping(
+ typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
///
/// Gets the for .
///
/// The property.
/// The for .
public static ConfigurationSource? GetTypeMappingConfigurationSource([NotNull] this IConventionProperty property)
- => property.FindAnnotation(CoreAnnotationNames.TypeMapping)?.GetConfigurationSource();
+ => ((Property)property).GetTypeMappingConfigurationSource();
///
/// Sets the maximum length of data that is allowed in this property. For example, if the property is a '
@@ -115,7 +129,7 @@ public static IEnumerable GetContainingKeys([NotNull] this IConv
/// For example, if the property is a
/// then this is the maximum number of digits.
///
- /// The property to get the precision of.
+ /// The property.
/// The maximum number of digits that is allowed in this property.
/// Indicates whether the configuration was specified using a data annotation.
public static int? SetPrecision([NotNull] this IConventionProperty property, int? precision, bool fromDataAnnotation = false)
@@ -135,7 +149,7 @@ public static IEnumerable GetContainingKeys([NotNull] this IConv
/// For example, if the property is a
/// then this is the maximum number of decimal places.
///
- /// The property to get the precision of.
+ /// The property.
/// The maximum number of decimal places that is allowed in this property.
/// Indicates whether the configuration was specified using a data annotation.
public static int? SetScale([NotNull] this IConventionProperty property, int? scale, bool fromDataAnnotation = false)
diff --git a/src/EFCore/Extensions/MutablePropertyExtensions.cs b/src/EFCore/Extensions/MutablePropertyExtensions.cs
index 5b9f3299a1d..5c31ef0b749 100644
--- a/src/EFCore/Extensions/MutablePropertyExtensions.cs
+++ b/src/EFCore/Extensions/MutablePropertyExtensions.cs
@@ -8,6 +8,7 @@
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.ValueGeneration;
@@ -198,6 +199,16 @@ public static void SetValueConverter([NotNull] this IMutableProperty property, [
public static void SetProviderClrType([NotNull] this IMutableProperty property, [CanBeNull] Type providerClrType)
=> property.AsProperty().SetProviderClrType(providerClrType, ConfigurationSource.Explicit);
+ ///
+ /// Sets the for the given property
+ ///
+ /// The property.
+ /// The for this property.
+ public static CoreTypeMapping SetTypeMapping(
+ [NotNull] this IMutableProperty property,
+ [NotNull] CoreTypeMapping typeMapping)
+ => ((Property)property).SetTypeMapping(typeMapping, ConfigurationSource.Explicit);
+
///
/// Sets the custom for this property.
///
diff --git a/src/EFCore/Extensions/PropertyExtensions.cs b/src/EFCore/Extensions/PropertyExtensions.cs
index f96879def4b..50e8e27ab7e 100644
--- a/src/EFCore/Extensions/PropertyExtensions.cs
+++ b/src/EFCore/Extensions/PropertyExtensions.cs
@@ -33,8 +33,7 @@ public static class PropertyExtensions
/// The type mapping.
public static CoreTypeMapping GetTypeMapping([NotNull] this IProperty property)
{
- var mapping = (CoreTypeMapping)property[CoreAnnotationNames.TypeMapping];
-
+ var mapping = ((Property)property).TypeMapping;
if (mapping == null)
{
throw new InvalidOperationException(
@@ -50,7 +49,7 @@ public static CoreTypeMapping GetTypeMapping([NotNull] this IProperty property)
/// The property.
/// The type mapping, or if none was found.
public static CoreTypeMapping FindTypeMapping([NotNull] this IProperty property)
- => (CoreTypeMapping)property[CoreAnnotationNames.TypeMapping];
+ => ((Property)property).TypeMapping;
///
/// Finds the first principal property that the given property is constrained by
@@ -124,7 +123,7 @@ private static void AddPrincipals(IProperty property, List visited)
/// The property to check.
/// if the property is used as a foreign key, otherwise .
public static bool IsForeignKey([NotNull] this IProperty property)
- => Check.NotNull(property, nameof(property)).AsProperty().ForeignKeys != null;
+ => Check.NotNull((Property)property, nameof(property)).ForeignKeys != null;
///
/// Gets a value indicating whether this property is used as an index (or part of a composite index).
@@ -132,7 +131,7 @@ public static bool IsForeignKey([NotNull] this IProperty property)
/// The property to check.
/// if the property is used as an index, otherwise .
public static bool IsIndex([NotNull] this IProperty property)
- => Check.NotNull(property, nameof(property)).AsProperty().Indexes != null;
+ => Check.NotNull((Property)property, nameof(property)).Indexes != null;
///
/// Gets a value indicating whether this property is used as a unique index (or part of a unique composite index).
@@ -157,7 +156,7 @@ public static bool IsPrimaryKey([NotNull] this IProperty property)
/// The property to check.
/// if the property is used as a key, otherwise .
public static bool IsKey([NotNull] this IProperty property)
- => Check.NotNull(property, nameof(property)).AsProperty().Keys != null;
+ => Check.NotNull((Property)property, nameof(property)).Keys != null;
///
/// Gets all foreign keys that use this property (including composite foreign keys in which this property
@@ -166,7 +165,7 @@ public static bool IsKey([NotNull] this IProperty property)
/// The property to get foreign keys for.
/// The foreign keys that use this property.
public static IEnumerable GetContainingForeignKeys([NotNull] this IProperty property)
- => Check.NotNull(property, nameof(property)).AsProperty().GetContainingForeignKeys();
+ => Check.NotNull((Property)property, nameof(property)).GetContainingForeignKeys();
///
/// Gets all indexes that use this property (including composite indexes in which this property
@@ -175,7 +174,7 @@ public static IEnumerable GetContainingForeignKeys([NotNull] this I
/// The property to get indexes for.
/// The indexes that use this property.
public static IEnumerable GetContainingIndexes([NotNull] this IProperty property)
- => Check.NotNull(property, nameof(property)).AsProperty().GetContainingIndexes();
+ => Check.NotNull((Property)property, nameof(property)).GetContainingIndexes();
///
/// Gets the primary key that uses this property (including a composite primary key in which this property
@@ -184,7 +183,7 @@ public static IEnumerable GetContainingIndexes([NotNull] this IProperty
/// The property to get primary key for.
/// The primary that use this property, or if it is not part of the primary key.
public static IKey FindContainingPrimaryKey([NotNull] this IProperty property)
- => Check.NotNull(property, nameof(property)).AsProperty().PrimaryKey;
+ => Check.NotNull((Property)property, nameof(property)).PrimaryKey;
///
/// Gets all primary or alternate keys that use this property (including composite keys in which this property
@@ -193,7 +192,7 @@ public static IKey FindContainingPrimaryKey([NotNull] this IProperty property)
/// The property to get primary and alternate keys for.
/// The primary and alternate keys that use this property.
public static IEnumerable GetContainingKeys([NotNull] this IProperty property)
- => Check.NotNull(property, nameof(property)).AsProperty().GetContainingKeys();
+ => Check.NotNull((Property)property, nameof(property)).GetContainingKeys();
///
/// Gets the maximum length of data that is allowed in this property. For example, if the property is a '
diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
index fef9d241c7e..83f990f5aaf 100644
--- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
@@ -5,6 +5,7 @@
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.ValueGeneration;
@@ -323,6 +324,28 @@ bool CanSetValueGenerator(
///
bool CanSetConversion([CanBeNull] Type providerClrType, bool fromDataAnnotation = false);
+ ///
+ /// Configures the for this property.
+ ///
+ /// The type mapping, or to remove any previously set type mapping.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionPropertyBuilder HasTypeMapping([CanBeNull] CoreTypeMapping typeMapping, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the given
+ /// can be configured for this property from the current configuration source.
+ ///
+ /// The type mapping, or to remove any previously set type mapping.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// if the given can be configured for this property.
+ ///
+ bool CanSetTypeMapping([CanBeNull] CoreTypeMapping typeMapping, bool fromDataAnnotation = false);
+
///
/// Configures the for this property.
///
diff --git a/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs b/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs
index 321542fcaad..b33ebf75a9e 100644
--- a/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs
+++ b/src/EFCore/Metadata/Conventions/TypeMappingConvention.cs
@@ -5,7 +5,6 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
{
@@ -33,9 +32,7 @@ public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder,
{
foreach (var property in modelBuilder.Metadata.GetEntityTypes().SelectMany(e => e.GetDeclaredProperties()))
{
- property.Builder.HasAnnotation(
- CoreAnnotationNames.TypeMapping,
- Dependencies.TypeMappingSource.FindMapping(property));
+ property.Builder.HasTypeMapping(Dependencies.TypeMappingSource.FindMapping(property));
}
}
}
diff --git a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs
index 88daa81d1b2..e170f185413 100644
--- a/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs
+++ b/src/EFCore/Metadata/Internal/CoreAnnotationNames.cs
@@ -134,14 +134,6 @@ public static class CoreAnnotationNames
///
public const string ServiceOnlyConstructorBinding = "ServiceOnlyConstructorBinding";
- ///
- /// 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 const string TypeMapping = "TypeMapping";
-
///
/// 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
@@ -304,7 +296,6 @@ public static class CoreAnnotationNames
DiscriminatorValue,
ConstructorBinding,
ServiceOnlyConstructorBinding,
- TypeMapping,
ValueConverter,
ValueComparer,
#pragma warning disable 618
diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
index 65e35c85ac0..0ac3ab4efcb 100644
--- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
@@ -10,6 +10,7 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.EntityFrameworkCore.ValueGeneration;
@@ -470,6 +471,35 @@ public virtual bool CanSetConversion([CanBeNull] Type providerClrType, Configura
=> configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource())
|| Metadata.GetProviderClrType() == 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 InternalPropertyBuilder HasTypeMapping(
+ [CanBeNull] CoreTypeMapping typeMapping, ConfigurationSource configurationSource)
+ {
+ if (CanSetTypeMapping(typeMapping, configurationSource))
+ {
+ Metadata.SetTypeMapping(typeMapping, configurationSource);
+
+ return this;
+ }
+
+ return 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 CanSetTypeMapping([CanBeNull] CoreTypeMapping typeMapping, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetTypeMappingConfigurationSource())
+ || Metadata.TypeMapping == typeMapping;
+
///
/// 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
@@ -920,6 +950,14 @@ IConventionPropertyBuilder IConventionPropertyBuilder.HasConversion(Type provide
bool IConventionPropertyBuilder.CanSetConversion(Type providerClrType, bool fromDataAnnotation)
=> CanSetConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ ///
+ IConventionPropertyBuilder IConventionPropertyBuilder.HasTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation)
+ => HasTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ bool IConventionPropertyBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, bool fromDataAnnotation)
+ => CanSetTypeMapping(typeMapping, 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
diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs
index 66d7d45897f..0c60862b497 100644
--- a/src/EFCore/Metadata/Internal/Property.cs
+++ b/src/EFCore/Metadata/Internal/Property.cs
@@ -11,6 +11,7 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.EntityFrameworkCore.ValueGeneration;
@@ -28,11 +29,13 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty
private bool? _isConcurrencyToken;
private bool? _isNullable;
private ValueGenerated? _valueGenerated;
+ private CoreTypeMapping _typeMapping;
private ConfigurationSource? _typeConfigurationSource;
private ConfigurationSource? _isNullableConfigurationSource;
private ConfigurationSource? _isConcurrencyTokenConfigurationSource;
private ConfigurationSource? _valueGeneratedConfigurationSource;
+ private ConfigurationSource? _typeMappingConfigurationSource;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -453,6 +456,44 @@ public virtual Type SetProviderClrType([CanBeNull] Type providerClrType, Configu
return 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 CoreTypeMapping TypeMapping
+ {
+ get => _typeMapping;
+ [param: NotNull]
+ set => SetTypeMapping(value, ConfigurationSource.Explicit);
+ }
+
+ ///
+ /// 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 CoreTypeMapping SetTypeMapping([NotNull] CoreTypeMapping typeMapping, ConfigurationSource configurationSource)
+ {
+ _typeMapping = typeMapping;
+ _typeMappingConfigurationSource = typeMapping == null
+ ? (ConfigurationSource?)null
+ : configurationSource.Max(_typeMappingConfigurationSource);
+
+ return typeMapping;
+ }
+
+ ///
+ /// 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 ConfigurationSource? GetTypeMappingConfigurationSource()
+ => _typeMappingConfigurationSource;
+
///
/// 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/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
index 4abadc36e4b..6a6b6f74126 100644
--- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs
@@ -49,7 +49,6 @@ public void Test_new_annotations_handled_for_entity_types()
CoreAnnotationNames.ProductVersion,
CoreAnnotationNames.ValueGeneratorFactory,
CoreAnnotationNames.OwnedTypes,
- CoreAnnotationNames.TypeMapping,
CoreAnnotationNames.ValueConverter,
CoreAnnotationNames.ValueComparer,
#pragma warning disable 618
@@ -211,10 +210,6 @@ public void Test_new_annotations_handled_for_properties()
RelationalAnnotationNames.DefaultValue,
("1", $@"{columnMapping}{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}(""1"")")
},
- {
- CoreAnnotationNames.TypeMapping,
- (new LongTypeMapping("bigint"), $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""bigint"")")
- },
{
RelationalAnnotationNames.IsFixedLength,
(true, $@"{columnMapping}{_nl}.{nameof(RelationalPropertyBuilderExtensions.IsFixedLength)}(true)")
diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
index e6dcc840e6b..e3d58a3bc84 100644
--- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs
@@ -2030,8 +2030,7 @@ public virtual void Property_annotations_are_stored_in_snapshot()
{
builder.Entity()
.Property("Id")
- .HasAnnotation("AnnotationName", "AnnotationValue")
- .HasAnnotation(CoreAnnotationNames.TypeMapping, new IntTypeMapping("int"));
+ .HasAnnotation("AnnotationName", "AnnotationValue");
builder.Ignore();
},
@@ -2752,7 +2751,7 @@ public virtual void Property_multiple_annotations_are_stored_in_snapshot()
o =>
{
var property = o.GetEntityTypes().First().FindProperty("AlternateId");
- Assert.Equal(6, property.GetAnnotations().Count());
+ Assert.Equal(5, property.GetAnnotations().Count());
Assert.Equal("AnnotationValue", property["AnnotationName"]);
Assert.Equal("CName", property["Relational:ColumnName"]);
Assert.Equal("int", property["Relational:ColumnType"]);
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index 1d4f434bebf..5e8cbe68fa4 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -1764,11 +1764,10 @@ public virtual void Non_TPH_as_a_result_of_DbFunction_throws()
}
private static void GenerateMapping(IMutableProperty property)
- => property[CoreAnnotationNames.TypeMapping]
- = new TestRelationalTypeMappingSource(
+ => property.SetTypeMapping(new TestRelationalTypeMappingSource(
TestServiceFactory.Instance.Create(),
TestServiceFactory.Instance.Create())
- .FindMapping(property);
+ .FindMapping(property));
protected override void SetBaseType(IMutableEntityType entityType, IMutableEntityType baseEntityType)
{
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
index 747168180f9..6e5c4d15619 100644
--- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
@@ -71,7 +71,7 @@ private static void AssertDefaultMappings(IRelationalModel model)
var orderMapping = orderType.GetDefaultMappings().Single();
Assert.True(orderMapping.IncludesDerivedTypes);
Assert.Equal(
- new[] { nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate), nameof(Order.OrderId) },
+ new[] { nameof(Order.OrderId), nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate) },
orderMapping.ColumnMappings.Select(m => m.Property.Name));
var ordersTable = orderMapping.Table;
@@ -142,7 +142,7 @@ private static void AssertViews(IRelationalModel model, Mapping mapping)
Assert.Same(orderType.GetViewMappings(), orderType.GetViewOrTableMappings());
Assert.True(orderMapping.IncludesDerivedTypes);
Assert.Equal(
- new[] { nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate), nameof(Order.OrderId) },
+ new[] { nameof(Order.OrderId), nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate) },
orderMapping.ColumnMappings.Select(m => m.Property.Name));
var ordersView = orderMapping.View;
@@ -243,7 +243,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
var orderMapping = orderType.GetTableMappings().Single();
Assert.True(orderMapping.IncludesDerivedTypes);
Assert.Equal(
- new[] { nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate), nameof(Order.OrderId) },
+ new[] { nameof(Order.OrderId), nameof(Order.AlternateId), nameof(Order.CustomerId), nameof(Order.OrderDate) },
orderMapping.ColumnMappings.Select(m => m.Property.Name));
var ordersTable = orderMapping.Table;
@@ -252,14 +252,14 @@ private static void AssertTables(IRelationalModel model, Mapping mapping)
new[] { nameof(Order), "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address", nameof(OrderDetails) },
ordersTable.EntityTypeMappings.Select(m => m.EntityType.DisplayName()));
Assert.Equal(new[] {
+ nameof(Order.OrderId),
nameof(Order.AlternateId),
nameof(Order.CustomerId),
"Details_BillingAddress_City",
"Details_BillingAddress_Street",
"Details_ShippingAddress_City",
"Details_ShippingAddress_Street",
- nameof(Order.OrderDate),
- nameof(Order.OrderId)
+ nameof(Order.OrderDate)
},
ordersTable.Columns.Select(m => m.Name));
Assert.Equal("Order", ordersTable.Name);
diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
index d2aabff2c40..d215d3d6d02 100644
--- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
+++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs
@@ -5167,8 +5167,8 @@ public void Add_subtype_with_shared_column_with_seed_data()
AssertMultidimensionalArray(
m.Values,
v => Assert.Equal(43, v),
- v => Assert.Equal("Dog", v),
- v => Assert.Equal("43", v));
+ v => Assert.Equal("43", v),
+ v => Assert.Equal("Dog", v));
}),
downOps => Assert.Collection(
downOps,
diff --git a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs
index 5606abdd532..1836d189ef5 100644
--- a/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs
+++ b/test/EFCore.Relational.Tests/Storage/RelationalParameterBuilderTest.cs
@@ -79,7 +79,7 @@ public void Can_add_type_mapped_parameter_by_property(bool nullable)
var property = ((IMutableModel)new Model()).AddEntityType("MyType").AddProperty("MyProp", typeof(string));
property.IsNullable = nullable;
- property[CoreAnnotationNames.TypeMapping] = GetMapping(typeMapper, property);
+ property.SetTypeMapping(GetMapping(typeMapper, property));
var parameterBuilder = new RelationalCommandBuilder(
new RelationalCommandBuilderDependencies(
diff --git a/test/EFCore.Specification.Tests/TestUtilities/AnnotationComparer.cs b/test/EFCore.Specification.Tests/TestUtilities/AnnotationComparer.cs
index a1e47ef6ec1..45d04265f88 100644
--- a/test/EFCore.Specification.Tests/TestUtilities/AnnotationComparer.cs
+++ b/test/EFCore.Specification.Tests/TestUtilities/AnnotationComparer.cs
@@ -30,7 +30,6 @@ public bool Equals(IAnnotation x, IAnnotation y)
? false
: x.Name == y.Name
&& (x.Name == CoreAnnotationNames.ValueGeneratorFactory
- || x.Name == CoreAnnotationNames.TypeMapping
|| Equals(x.Value, y.Value));
}
diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs
index 4508a22f80a..44dcb008327 100644
--- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs
+++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs
@@ -583,11 +583,11 @@ protected virtual void ConfigureProperty(IMutableProperty property, string confi
}
private static void GenerateMapping(IMutableProperty property)
- => property[CoreAnnotationNames.TypeMapping] =
+ => property.SetTypeMapping(
new SqlServerTypeMappingSource(
TestServiceFactory.Instance.Create(),
TestServiceFactory.Instance.Create())
- .FindMapping(property);
+ .FindMapping(property));
private class Cheese
{
diff --git a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
index 25f447077b2..82a306a5e73 100644
--- a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
+++ b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
@@ -97,9 +97,9 @@ public void Detects_sequences()
}
private static void GenerateMapping(IMutableProperty property)
- => property[CoreAnnotationNames.TypeMapping]
- = TestServiceFactory.Instance.Create()
- .FindMapping(property);
+ => property.SetTypeMapping(
+ TestServiceFactory.Instance.Create()
+ .FindMapping(property));
protected override TestHelpers TestHelpers => SqliteTestHelpers.Instance;
}
diff --git a/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs
index 461107e5585..5ca7dcbec54 100644
--- a/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs
+++ b/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs
@@ -351,11 +351,11 @@ protected internal override void OnModelCreating(ModelBuilder modelBuilder)
if (UseTypeMapping)
{
- property[CoreAnnotationNames.TypeMapping]
- = new ConcreteTypeMapping(typeof(int[]), intArrayConverter, intArrayComparer);
+ property.SetTypeMapping(
+ new ConcreteTypeMapping(typeof(int[]), intArrayConverter, intArrayComparer));
- shadowProperty[CoreAnnotationNames.TypeMapping]
- = new ConcreteTypeMapping(typeof(int[]), intArrayConverter, intArrayComparer);
+ shadowProperty.SetTypeMapping(
+ new ConcreteTypeMapping(typeof(int[]), intArrayConverter, intArrayComparer));
}
else
{