diff --git a/src/EFCore.Relational/Storage/Internal/FallbackRelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/Internal/FallbackRelationalTypeMappingSource.cs index aec79becd84..f52fe66afd7 100644 --- a/src/EFCore.Relational/Storage/Internal/FallbackRelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/Internal/FallbackRelationalTypeMappingSource.cs @@ -3,6 +3,7 @@ using System; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Storage.Internal @@ -17,6 +18,9 @@ public class FallbackRelationalTypeMappingSource : RelationalTypeMappingSource private readonly IRelationalTypeMapper _relationalTypeMapper; #pragma warning restore 618 + [ThreadStatic] + private static IProperty _property; + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -32,6 +36,19 @@ public FallbackRelationalTypeMappingSource( _relationalTypeMapper = typeMapper; } + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected override CoreTypeMapping FindMappingWithConversion( + TypeMappingInfo mappingInfo, + IProperty property) + { + _property = property; + + return base.FindMappingWithConversion(mappingInfo, property); + } + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -67,8 +84,8 @@ protected override RelationalTypeMapping FindMapping(RelationalTypeMappingInfo m } private RelationalTypeMapping FindMappingForProperty(RelationalTypeMappingInfo mappingInfo) - => mappingInfo.Property != null - ? _relationalTypeMapper.FindMapping(mappingInfo.Property) + => _property != null + ? _relationalTypeMapper.FindMapping(_property) : null; private RelationalTypeMapping FindMappingForClrType(RelationalTypeMappingInfo mappingInfo) diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs index c966e3e0691..cdbf1a05e61 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSource.cs @@ -77,7 +77,7 @@ protected override CoreTypeMapping FindMapping(TypeMappingInfo mappingInfo) /// The type mapping, or null if none was found. public override CoreTypeMapping FindMapping(IProperty property) => property.FindRelationalMapping() - ?? FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(property)); + ?? FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(property), property); /// /// @@ -95,7 +95,7 @@ public override CoreTypeMapping FindMapping(IProperty property) /// The CLR type. /// The type mapping, or null if none was found. public override CoreTypeMapping FindMapping(Type type) - => FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(type)); + => FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(type), null); /// /// @@ -113,7 +113,7 @@ public override CoreTypeMapping FindMapping(Type type) /// The field or property. /// The type mapping, or null if none was found. public override CoreTypeMapping FindMapping(MemberInfo member) - => FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(member)); + => FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(member), null); /// /// @@ -130,7 +130,7 @@ public override CoreTypeMapping FindMapping(MemberInfo member) /// The database type name. /// The type mapping, or null if none was found. public virtual RelationalTypeMapping FindMapping(string storeTypeName) - => (RelationalTypeMapping)FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(storeTypeName)); + => (RelationalTypeMapping)FindMappingWithConversion(new ConcreteRelationalTypeMappingInfo(storeTypeName), null); /// /// @@ -164,7 +164,7 @@ public virtual RelationalTypeMapping FindMapping( int? scale = null) => (RelationalTypeMapping)FindMappingWithConversion( new ConcreteRelationalTypeMappingInfo( - type, keyOrIndex, unicode, size, rowVersion, fixedLength, precision, scale)); + type, keyOrIndex, unicode, size, rowVersion, fixedLength, precision, scale), null); RelationalTypeMapping IRelationalTypeMappingSource.FindMapping(IProperty property) => (RelationalTypeMapping)FindMapping(property); diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs index fa6a1315a0c..774e253598c 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs @@ -8,6 +8,7 @@ using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -204,26 +205,28 @@ public SqlServerTypeMappingSource( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected override RelationalTypeMapping FindMapping(RelationalTypeMappingInfo mappingInfo) + protected override void ValidateMapping(CoreTypeMapping mapping, IProperty property) { - var mapping = FindRawMapping(mappingInfo)?.Clone(mappingInfo); + var relationalMapping = mapping as RelationalTypeMapping; - if (_disallowedMappings.Contains(mapping?.StoreType)) + if (_disallowedMappings.Contains(relationalMapping?.StoreType)) { - var propertyName = mappingInfo.Property?.Name - ?? mappingInfo.MemberInfo?.Name; - - if (propertyName == null) + if (property== null) { - throw new ArgumentException(SqlServerStrings.UnqualifiedDataType(mapping.StoreType)); + throw new ArgumentException(SqlServerStrings.UnqualifiedDataType(relationalMapping.StoreType)); } - throw new ArgumentException(SqlServerStrings.UnqualifiedDataTypeOnProperty(mapping.StoreType, propertyName)); + throw new ArgumentException(SqlServerStrings.UnqualifiedDataTypeOnProperty(relationalMapping.StoreType, property.Name)); } - - return mapping; } + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + protected override RelationalTypeMapping FindMapping(RelationalTypeMappingInfo mappingInfo) + => FindRawMapping(mappingInfo)?.Clone(mappingInfo); + private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingInfo) { var clrType = mappingInfo.ClrType; diff --git a/src/EFCore/Storage/TypeMappingInfo.cs b/src/EFCore/Storage/TypeMappingInfo.cs index fe35ffb747b..bb5de3e05f9 100644 --- a/src/EFCore/Storage/TypeMappingInfo.cs +++ b/src/EFCore/Storage/TypeMappingInfo.cs @@ -17,6 +17,9 @@ namespace Microsoft.EntityFrameworkCore.Storage /// public abstract class TypeMappingInfo { + private readonly Type _providerClrType; + private readonly ValueConverter _customConverter; + /// /// Creates a new instance of . /// @@ -34,13 +37,24 @@ protected TypeMappingInfo([NotNull] IProperty property) var principals = property.FindPrincipals().ToList(); - Property = property; - MemberInfo = property.GetIdentifyingMemberInfo(); + _providerClrType = principals + ?.Select(p => p.GetProviderClrType()) + .FirstOrDefault(t => t != null) + ?.UnwrapNullableType(); + + _customConverter = principals + ?.Select(p => p.GetValueConverter()) + .FirstOrDefault(c => c != null); + + var mappingHints = _customConverter?.MappingHints; + IsKeyOrIndex = property.IsKeyOrForeignKey() || property.IsIndex(); - Size = principals.Select(p => p.GetMaxLength()).FirstOrDefault(t => t != null); - IsUnicode = principals.Select(p => p.IsUnicode()).FirstOrDefault(t => t != null); + Size = principals.Select(p => p.GetMaxLength()).FirstOrDefault(t => t != null) ?? mappingHints?.Size; + IsUnicode = principals.Select(p => p.IsUnicode()).FirstOrDefault(t => t != null) ?? mappingHints?.IsUnicode; IsRowVersion = property.IsConcurrencyToken && property.ValueGenerated == ValueGenerated.OnAddOrUpdate; - ClrType = property.ClrType.UnwrapNullableType(); + ClrType = (_customConverter?.ProviderClrType ?? property.ClrType).UnwrapNullableType(); + Scale = mappingHints?.Scale; + Precision = mappingHints?.Precision; } /// @@ -63,7 +77,6 @@ protected TypeMappingInfo([NotNull] MemberInfo member) Check.NotNull(member, nameof(member)); ClrType = member.GetMemberType().UnwrapNullableType(); - MemberInfo = member; } /// @@ -105,10 +118,10 @@ protected TypeMappingInfo( { Check.NotNull(source, nameof(source)); - Property = source.Property; IsRowVersion = source.IsRowVersion; IsKeyOrIndex = source.IsKeyOrIndex; - MemberInfo = source.MemberInfo; + _providerClrType = source._providerClrType; + _customConverter = source._customConverter; var mappingHints = converter.MappingHints; @@ -127,11 +140,6 @@ protected TypeMappingInfo( /// The new mapping info. public abstract TypeMappingInfo WithConverter(ValueConverterInfo converterInfo); - /// - /// The property for which mapping is needed. - /// - public virtual IProperty Property { get; } - /// /// Indicates whether or not the mapping is part of a key or index. /// @@ -162,11 +170,6 @@ protected TypeMappingInfo( /// public virtual int? Scale { get; } - /// - /// The field or property info for the property. - /// - public virtual MemberInfo MemberInfo { get; } - /// /// The CLR type in the model. /// @@ -178,9 +181,9 @@ protected TypeMappingInfo( /// The other object. /// True if they represent the same mapping; false otherwise. protected virtual bool Equals([NotNull] TypeMappingInfo other) - => Property == other.Property - && ClrType == other.ClrType - && MemberInfo == other.MemberInfo + => ClrType == other.ClrType + && _providerClrType == other._providerClrType + && _customConverter == other._customConverter && IsKeyOrIndex == other.IsKeyOrIndex && Size == other.Size && IsUnicode == other.IsUnicode @@ -205,11 +208,11 @@ public override bool Equals(object obj) /// The hash code. public override int GetHashCode() { - var hashCode = Property?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ ClrType?.GetHashCode() ?? 0; + var hashCode = ClrType?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (_providerClrType?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (_customConverter?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ IsKeyOrIndex.GetHashCode(); hashCode = (hashCode * 397) ^ (Size?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (MemberInfo?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (IsUnicode?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (IsRowVersion?.GetHashCode() ?? 0); hashCode = (hashCode * 397) ^ (Scale?.GetHashCode() ?? 0); diff --git a/src/EFCore/Storage/TypeMappingSource.cs b/src/EFCore/Storage/TypeMappingSource.cs index f9fccbdc4cf..be7c17a58e0 100644 --- a/src/EFCore/Storage/TypeMappingSource.cs +++ b/src/EFCore/Storage/TypeMappingSource.cs @@ -58,6 +58,17 @@ protected TypeMappingSource([NotNull] TypeMappingSourceDependencies dependencies /// The type mapping, or null if none could be found. protected abstract CoreTypeMapping FindMapping([NotNull] TypeMappingInfo mappingInfo); + /// + /// Called after a mapping has been found so that it can be validated for the given property. + /// + /// The mapping, if any. + /// The property, if any. + protected virtual void ValidateMapping( + [CanBeNull] CoreTypeMapping mapping, + [CanBeNull] IProperty property) + { + } + /// /// /// Uses any available to help find a mapping that works. @@ -67,36 +78,29 @@ protected TypeMappingSource([NotNull] TypeMappingSourceDependencies dependencies /// /// /// The mapping info. + /// The property for which the type mapping is needed, if any. /// The type mapping with conversions applied, or null if none could be found. - protected virtual CoreTypeMapping FindMappingWithConversion([NotNull] TypeMappingInfo mappingInfo) + protected virtual CoreTypeMapping FindMappingWithConversion( + [NotNull] TypeMappingInfo mappingInfo, + [CanBeNull] IProperty property) { Check.NotNull(mappingInfo, nameof(mappingInfo)); - return _explicitMappings.GetOrAdd( - mappingInfo, - k => - { - var principals = mappingInfo.Property?.FindPrincipals().ToList(); - - var customConverter = principals - ?.Select(p => p.GetValueConverter()) - .FirstOrDefault(c => c != null); + var principals = property?.FindPrincipals().ToList(); - if (customConverter != null) - { - mappingInfo = mappingInfo.WithConverter( - new ValueConverterInfo( - customConverter.ModelClrType, - customConverter.ProviderClrType, - i => customConverter, - customConverter.MappingHints)); - } + var customConverter = principals + ?.Select(p => p.GetValueConverter()) + .FirstOrDefault(c => c != null); - var providerClrType = principals - ?.Select(p => p.GetProviderClrType()) - .FirstOrDefault(t => t != null) - ?.UnwrapNullableType(); + var providerClrType = principals + ?.Select(p => p.GetProviderClrType()) + .FirstOrDefault(t => t != null) + ?.UnwrapNullableType(); + var resolvedMapping = _explicitMappings.GetOrAdd( + mappingInfo, + k => + { var mapping = providerClrType == null || providerClrType == mappingInfo.ClrType ? FindMapping(mappingInfo) @@ -149,6 +153,10 @@ protected virtual CoreTypeMapping FindMappingWithConversion([NotNull] TypeMappin return mapping; }); + + ValidateMapping(resolvedMapping, property); + + return resolvedMapping; } /// @@ -163,7 +171,7 @@ protected virtual CoreTypeMapping FindMappingWithConversion([NotNull] TypeMappin /// The type mapping, or null if none was found. public virtual CoreTypeMapping FindMapping(IProperty property) => property.FindMapping() - ?? FindMappingWithConversion(new ConcreteTypeMappingInfo(property)); + ?? FindMappingWithConversion(new ConcreteTypeMappingInfo(property), property); /// /// @@ -181,7 +189,7 @@ public virtual CoreTypeMapping FindMapping(IProperty property) /// The CLR type. /// The type mapping, or null if none was found. public virtual CoreTypeMapping FindMapping(Type type) - => FindMappingWithConversion(new ConcreteTypeMappingInfo(type)); + => FindMappingWithConversion(new ConcreteTypeMappingInfo(type), null); /// /// @@ -199,7 +207,7 @@ public virtual CoreTypeMapping FindMapping(Type type) /// The field or property. /// The type mapping, or null if none was found. public virtual CoreTypeMapping FindMapping(MemberInfo member) - => FindMappingWithConversion(new ConcreteTypeMappingInfo(member)); + => FindMappingWithConversion(new ConcreteTypeMappingInfo(member), null); private sealed class ConcreteTypeMappingInfo : TypeMappingInfo {