@@ -31,6 +31,15 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IPr
3131 private ConfigurationSource ? _valueGeneratedConfigurationSource ;
3232 private ConfigurationSource ? _typeMappingConfigurationSource ;
3333
34+ /// <summary>
35+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
36+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
37+ /// any release. You should only use it directly in your code with extreme caution and knowing that
38+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
39+ /// </summary>
40+ public static readonly bool UseOldBehavior32422 =
41+ AppContext . TryGetSwitch ( "Microsoft.EntityFrameworkCore.Issue32422" , out var enabled32422 ) && enabled32422 ;
42+
3443 /// <summary>
3544 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
3645 /// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -744,6 +753,12 @@ private object? DefaultSentinel
744753 return ( ValueConverter ? ) annotation . Value ;
745754 }
746755
756+ if ( ! UseOldBehavior32422 )
757+ {
758+ return GetConversion ( throwOnProviderClrTypeConflict : FindAnnotation ( CoreAnnotationNames . ProviderClrType ) == null )
759+ . ValueConverter ;
760+ }
761+
747762 var property = this ;
748763 var i = 0 ;
749764 for ( ; i < ForeignKey . LongestFkChainAllowedLength ; i ++ )
@@ -836,6 +851,12 @@ private object? DefaultSentinel
836851 return ( Type ? ) annotation . Value ;
837852 }
838853
854+ if ( ! UseOldBehavior32422 )
855+ {
856+ return GetConversion ( throwOnValueConverterConflict : FindAnnotation ( CoreAnnotationNames . ValueConverter ) == null )
857+ . ProviderClrType ;
858+ }
859+
839860 var property = this ;
840861 var i = 0 ;
841862 for ( ; i < ForeignKey . LongestFkChainAllowedLength ; i ++ )
@@ -880,6 +901,214 @@ private object? DefaultSentinel
880901 : null ;
881902 }
882903
904+ /// <summary>
905+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
906+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
907+ /// any release. You should only use it directly in your code with extreme caution and knowing that
908+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
909+ /// </summary>
910+ public virtual ( ValueConverter ? ValueConverter , Type ? ValueConverterType , Type ? ProviderClrType ) GetConversion (
911+ bool throwOnValueConverterConflict = true ,
912+ bool throwOnProviderClrTypeConflict = true )
913+ {
914+ Queue < ( Property CurrentProperty , Property CycleBreakingPropert , int CyclePosition , int MaxCycleLength ) > ? queue = null ;
915+ ( Property CurrentProperty , Property CycleBreakingPropert , int CyclePosition , int MaxCycleLength ) ? currentNode =
916+ ( this , this , 0 , 2 ) ;
917+
918+ ValueConverter ? valueConverter = null ;
919+ Type ? valueConverterType = null ;
920+ Type ? providerClrType = null ;
921+ while ( currentNode is not null || queue is { Count : > 0 } )
922+ {
923+ var ( property , cycleBreakingProperty , cyclePosition , maxCycleLength ) = currentNode ?? queue ! . Dequeue ( ) ;
924+ currentNode = null ;
925+ if ( cyclePosition >= ForeignKey . LongestFkChainAllowedLength )
926+ {
927+ throw new InvalidOperationException (
928+ CoreStrings . RelationshipCycle ( DeclaringType . DisplayName ( ) , Name , "ValueConverter" ) ) ;
929+ }
930+
931+ foreach ( var foreignKey in property . GetContainingForeignKeys ( ) )
932+ {
933+ for ( var propertyIndex = 0 ; propertyIndex < foreignKey . Properties . Count ; propertyIndex ++ )
934+ {
935+ if ( property != foreignKey . Properties [ propertyIndex ] )
936+ {
937+ continue ;
938+ }
939+
940+ var principalProperty = foreignKey . PrincipalKey . Properties [ propertyIndex ] ;
941+ if ( principalProperty == cycleBreakingProperty )
942+ {
943+ break ;
944+ }
945+
946+ var annotationFound = GetConversion (
947+ principalProperty ,
948+ throwOnValueConverterConflict ,
949+ throwOnProviderClrTypeConflict ,
950+ ref valueConverter ,
951+ ref valueConverterType ,
952+ ref providerClrType ) ;
953+ if ( ! annotationFound )
954+ {
955+ if ( currentNode != null )
956+ {
957+ queue = new ( ) ;
958+ queue . Enqueue ( currentNode . Value ) ;
959+ }
960+
961+ if ( cyclePosition == maxCycleLength - 1 )
962+ {
963+ // We need to use different primes to ensure a different cycleBreakingProperty is selected
964+ // each time when traversing properties that participate in multiple relationship cycles
965+ currentNode = ( principalProperty , property , 0 , HashHelpers . GetPrime ( maxCycleLength << 1 ) ) ;
966+ }
967+ else
968+ {
969+ currentNode = ( principalProperty , cycleBreakingProperty , cyclePosition + 1 , maxCycleLength ) ;
970+ }
971+
972+ if ( queue != null )
973+ {
974+ queue . Enqueue ( currentNode . Value ) ;
975+ currentNode = null ;
976+ }
977+ }
978+ break ;
979+ }
980+ }
981+ }
982+
983+ return ( valueConverter , valueConverterType , providerClrType ) ;
984+
985+ bool GetConversion (
986+ Property principalProperty ,
987+ bool throwOnValueConverterConflict ,
988+ bool throwOnProviderClrTypeConflict ,
989+ ref ValueConverter ? valueConverter ,
990+ ref Type ? valueConverterType ,
991+ ref Type ? providerClrType )
992+ {
993+ var annotationFound = false ;
994+ var valueConverterAnnotation = principalProperty . FindAnnotation ( CoreAnnotationNames . ValueConverter ) ;
995+ if ( valueConverterAnnotation != null )
996+ {
997+ var annotationValue = ( ValueConverter ? ) valueConverterAnnotation . Value ;
998+ if ( annotationValue != null )
999+ {
1000+ if ( valueConverter != null
1001+ && annotationValue . GetType ( ) != valueConverter . GetType ( ) )
1002+ {
1003+ throw new InvalidOperationException (
1004+ CoreStrings . ConflictingRelationshipConversions (
1005+ DeclaringType . DisplayName ( ) , Name ,
1006+ valueConverter . GetType ( ) . ShortDisplayName ( ) , annotationValue . GetType ( ) . ShortDisplayName ( ) ) ) ;
1007+ }
1008+
1009+ if ( valueConverterType != null
1010+ && annotationValue . GetType ( ) != valueConverterType )
1011+ {
1012+ throw new InvalidOperationException (
1013+ CoreStrings . ConflictingRelationshipConversions (
1014+ DeclaringType . DisplayName ( ) , Name ,
1015+ valueConverterType . ShortDisplayName ( ) , annotationValue . GetType ( ) . ShortDisplayName ( ) ) ) ;
1016+ }
1017+
1018+ if ( providerClrType != null
1019+ && throwOnProviderClrTypeConflict )
1020+ {
1021+ throw new InvalidOperationException (
1022+ CoreStrings . ConflictingRelationshipConversions (
1023+ DeclaringType . DisplayName ( ) , Name ,
1024+ providerClrType . ShortDisplayName ( ) , annotationValue . GetType ( ) . ShortDisplayName ( ) ) ) ;
1025+ }
1026+
1027+ valueConverter = annotationValue ;
1028+ }
1029+ annotationFound = true ;
1030+ }
1031+
1032+ var valueConverterTypeAnnotation = principalProperty . FindAnnotation ( CoreAnnotationNames . ValueConverterType ) ;
1033+ if ( valueConverterTypeAnnotation != null )
1034+ {
1035+ var annotationValue = ( Type ? ) valueConverterTypeAnnotation . Value ;
1036+ if ( annotationValue != null )
1037+ {
1038+ if ( valueConverter != null
1039+ && valueConverter . GetType ( ) != annotationValue )
1040+ {
1041+ throw new InvalidOperationException (
1042+ CoreStrings . ConflictingRelationshipConversions (
1043+ DeclaringType . DisplayName ( ) , Name ,
1044+ valueConverter . GetType ( ) . ShortDisplayName ( ) , annotationValue . ShortDisplayName ( ) ) ) ;
1045+ }
1046+
1047+ if ( valueConverterType != null
1048+ && valueConverterType != annotationValue )
1049+ {
1050+ throw new InvalidOperationException (
1051+ CoreStrings . ConflictingRelationshipConversions (
1052+ DeclaringType . DisplayName ( ) , Name ,
1053+ valueConverterType . ShortDisplayName ( ) , annotationValue . ShortDisplayName ( ) ) ) ;
1054+ }
1055+
1056+ if ( providerClrType != null
1057+ && throwOnProviderClrTypeConflict )
1058+ {
1059+ throw new InvalidOperationException (
1060+ CoreStrings . ConflictingRelationshipConversions (
1061+ DeclaringType . DisplayName ( ) , Name ,
1062+ providerClrType . ShortDisplayName ( ) , annotationValue . ShortDisplayName ( ) ) ) ;
1063+ }
1064+
1065+ valueConverterType = annotationValue ;
1066+ }
1067+ annotationFound = true ;
1068+ }
1069+
1070+ var providerClrTypeAnnotation = principalProperty . FindAnnotation ( CoreAnnotationNames . ProviderClrType ) ;
1071+ if ( providerClrTypeAnnotation != null )
1072+ {
1073+ var annotationValue = ( Type ? ) providerClrTypeAnnotation . Value ;
1074+ if ( annotationValue != null )
1075+ {
1076+ if ( providerClrType != null
1077+ && annotationValue != providerClrType )
1078+ {
1079+ throw new InvalidOperationException (
1080+ CoreStrings . ConflictingRelationshipConversions (
1081+ DeclaringType . DisplayName ( ) , Name ,
1082+ providerClrType . ShortDisplayName ( ) , annotationValue . ShortDisplayName ( ) ) ) ;
1083+ }
1084+
1085+ if ( valueConverter != null
1086+ && throwOnValueConverterConflict )
1087+ {
1088+ throw new InvalidOperationException (
1089+ CoreStrings . ConflictingRelationshipConversions (
1090+ DeclaringType . DisplayName ( ) , Name ,
1091+ valueConverter . GetType ( ) . ShortDisplayName ( ) , annotationValue . ShortDisplayName ( ) ) ) ;
1092+ }
1093+
1094+ if ( valueConverterType != null
1095+ && throwOnValueConverterConflict )
1096+ {
1097+ throw new InvalidOperationException (
1098+ CoreStrings . ConflictingRelationshipConversions (
1099+ DeclaringType . DisplayName ( ) , Name ,
1100+ valueConverterType . ShortDisplayName ( ) , annotationValue . ShortDisplayName ( ) ) ) ;
1101+ }
1102+
1103+ providerClrType = annotationValue ;
1104+ }
1105+ annotationFound = true ;
1106+ }
1107+
1108+ return annotationFound ;
1109+ }
1110+ }
1111+
8831112 /// <summary>
8841113 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
8851114 /// the same compatibility standards as public APIs. It may be changed or removed without notice in
0 commit comments