|  | 
| 4 | 4 | using System.Diagnostics.CodeAnalysis; | 
| 5 | 5 | using System.Globalization; | 
| 6 | 6 | using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; | 
|  | 7 | +using Microsoft.EntityFrameworkCore.Infrastructure; | 
| 7 | 8 | using Microsoft.EntityFrameworkCore.Internal; | 
| 8 | 9 | using Microsoft.EntityFrameworkCore.Storage.Json; | 
| 9 | 10 | 
 | 
| @@ -767,53 +768,10 @@ private object? DefaultSentinel | 
| 767 | 768 |     public virtual ValueConverter? GetValueConverter() | 
| 768 | 769 |     { | 
| 769 | 770 |         var annotation = FindAnnotation(CoreAnnotationNames.ValueConverter); | 
| 770 |  | -        if (annotation != null) | 
| 771 |  | -        { | 
| 772 |  | -            return (ValueConverter?)annotation.Value; | 
| 773 |  | -        } | 
| 774 |  | - | 
| 775 |  | -        var property = this; | 
| 776 |  | -        var i = 0; | 
| 777 |  | -        for (; i < ForeignKey.LongestFkChainAllowedLength; i++) | 
| 778 |  | -        { | 
| 779 |  | -            Property? nextProperty = null; | 
| 780 |  | -            foreach (var foreignKey in property.GetContainingForeignKeys()) | 
| 781 |  | -            { | 
| 782 |  | -                for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) | 
| 783 |  | -                { | 
| 784 |  | -                    if (property == foreignKey.Properties[propertyIndex]) | 
| 785 |  | -                    { | 
| 786 |  | -                        var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; | 
| 787 |  | -                        if (principalProperty == this | 
| 788 |  | -                            || principalProperty == property) | 
| 789 |  | -                        { | 
| 790 |  | -                            break; | 
| 791 |  | -                        } | 
| 792 |  | - | 
| 793 |  | -                        annotation = principalProperty.FindAnnotation(CoreAnnotationNames.ValueConverter); | 
| 794 |  | -                        if (annotation != null) | 
| 795 |  | -                        { | 
| 796 |  | -                            return (ValueConverter?)annotation.Value; | 
| 797 |  | -                        } | 
| 798 |  | - | 
| 799 |  | -                        nextProperty = principalProperty; | 
| 800 |  | -                    } | 
| 801 |  | -                } | 
| 802 |  | -            } | 
| 803 |  | - | 
| 804 |  | -            if (nextProperty == null) | 
| 805 |  | -            { | 
| 806 |  | -                break; | 
| 807 |  | -            } | 
| 808 |  | - | 
| 809 |  | -            property = nextProperty; | 
| 810 |  | -        } | 
| 811 |  | - | 
| 812 |  | -        return i == ForeignKey.LongestFkChainAllowedLength | 
| 813 |  | -            ? throw new InvalidOperationException( | 
| 814 |  | -                CoreStrings.RelationshipCycle( | 
| 815 |  | -                    DeclaringType.DisplayName(), Name, "ValueConverter")) | 
| 816 |  | -            : null; | 
|  | 771 | +        return annotation != null | 
|  | 772 | +            ? (ValueConverter?)annotation.Value | 
|  | 773 | +            : GetConversion(throwOnProviderClrTypeConflict: FindAnnotation(CoreAnnotationNames.ProviderClrType) == null) | 
|  | 774 | +                .ValueConverter; | 
| 817 | 775 |     } | 
| 818 | 776 | 
 | 
| 819 | 777 |     /// <summary> | 
| @@ -859,53 +817,120 @@ private object? DefaultSentinel | 
| 859 | 817 |     public virtual Type? GetProviderClrType() | 
| 860 | 818 |     { | 
| 861 | 819 |         var annotation = FindAnnotation(CoreAnnotationNames.ProviderClrType); | 
| 862 |  | -        if (annotation != null) | 
| 863 |  | -        { | 
| 864 |  | -            return (Type?)annotation.Value; | 
| 865 |  | -        } | 
|  | 820 | +        return annotation != null | 
|  | 821 | +            ? (Type?)annotation.Value | 
|  | 822 | +            : GetConversion(throwOnValueConverterConflict: FindAnnotation(CoreAnnotationNames.ValueConverter) == null) | 
|  | 823 | +                .ProviderClrType; | 
|  | 824 | +    } | 
|  | 825 | + | 
|  | 826 | +    private (ValueConverter? ValueConverter, Type? ProviderClrType) GetConversion( | 
|  | 827 | +        bool throwOnValueConverterConflict = true, bool throwOnProviderClrTypeConflict = true) | 
|  | 828 | +    { | 
|  | 829 | +        var queue = new Queue<(Property CurrentProperty, Property CycleBreakingPropert, int CyclePosition, int MaxCycleLength)>(); | 
|  | 830 | +        queue.Enqueue((this, this, 0, 2)); | 
| 866 | 831 | 
 | 
| 867 |  | -        var property = this; | 
| 868 |  | -        var i = 0; | 
| 869 |  | -        for (; i < ForeignKey.LongestFkChainAllowedLength; i++) | 
|  | 832 | +        ValueConverter? valueConverter = null; | 
|  | 833 | +        Type? providerClrType = null; | 
|  | 834 | +        while (queue.Count > 0) | 
| 870 | 835 |         { | 
| 871 |  | -            Property? nextProperty = null; | 
|  | 836 | +            var (property, cycleBreakingProperty, cyclePosition, maxCycleLength) = queue.Dequeue(); | 
|  | 837 | +            if (cyclePosition >= ForeignKey.LongestFkChainAllowedLength) | 
|  | 838 | +            { | 
|  | 839 | +                throw new InvalidOperationException( | 
|  | 840 | +                    CoreStrings.RelationshipCycle(DeclaringType.DisplayName(), Name, "ValueConverter")); | 
|  | 841 | +            } | 
|  | 842 | + | 
| 872 | 843 |             foreach (var foreignKey in property.GetContainingForeignKeys()) | 
| 873 | 844 |             { | 
| 874 | 845 |                 for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) | 
| 875 | 846 |                 { | 
| 876 |  | -                    if (property == foreignKey.Properties[propertyIndex]) | 
|  | 847 | +                    if (property != foreignKey.Properties[propertyIndex]) | 
|  | 848 | +                    { | 
|  | 849 | +                        continue; | 
|  | 850 | +                    } | 
|  | 851 | + | 
|  | 852 | +                    var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; | 
|  | 853 | +                    if (principalProperty == cycleBreakingProperty) | 
|  | 854 | +                    { | 
|  | 855 | +                        break; | 
|  | 856 | +                    } | 
|  | 857 | + | 
|  | 858 | +                    var annotationFound = false; | 
|  | 859 | +                    var valueConverterAnnotation = principalProperty.FindAnnotation(CoreAnnotationNames.ValueConverter); | 
|  | 860 | +                    if (valueConverterAnnotation != null) | 
| 877 | 861 |                     { | 
| 878 |  | -                        var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; | 
| 879 |  | -                        if (principalProperty == this | 
| 880 |  | -                            || principalProperty == property) | 
|  | 862 | +                        var annotationValue = (ValueConverter?)valueConverterAnnotation.Value; | 
|  | 863 | +                        if (annotationValue != null) | 
| 881 | 864 |                         { | 
| 882 |  | -                            break; | 
|  | 865 | +                            if (valueConverter != null) | 
|  | 866 | +                            { | 
|  | 867 | +                                throw new InvalidOperationException( | 
|  | 868 | +                                    CoreStrings.ConflictingRelationshipConversions( | 
|  | 869 | +                                        DeclaringType.DisplayName(), Name, | 
|  | 870 | +                                        valueConverter.GetType().ShortDisplayName(), annotationValue.GetType().ShortDisplayName())); | 
|  | 871 | +                            } | 
|  | 872 | + | 
|  | 873 | +                            if (providerClrType != null | 
|  | 874 | +                                && throwOnProviderClrTypeConflict) | 
|  | 875 | +                            { | 
|  | 876 | +                                throw new InvalidOperationException( | 
|  | 877 | +                                    CoreStrings.ConflictingRelationshipConversions( | 
|  | 878 | +                                        DeclaringType.DisplayName(), Name, | 
|  | 879 | +                                        providerClrType.ShortDisplayName(), annotationValue.GetType().ShortDisplayName())); | 
|  | 880 | +                            } | 
|  | 881 | + | 
|  | 882 | +                            valueConverter = annotationValue; | 
| 883 | 883 |                         } | 
|  | 884 | +                        annotationFound = true; | 
|  | 885 | +                    } | 
| 884 | 886 | 
 | 
| 885 |  | -                        annotation = principalProperty.FindAnnotation(CoreAnnotationNames.ProviderClrType); | 
| 886 |  | -                        if (annotation != null) | 
|  | 887 | +                    var providerClrTypeAnnotation = principalProperty.FindAnnotation(CoreAnnotationNames.ProviderClrType); | 
|  | 888 | +                    if (providerClrTypeAnnotation != null) | 
|  | 889 | +                    { | 
|  | 890 | +                        var annotationValue = (Type?)providerClrTypeAnnotation.Value; | 
|  | 891 | +                        if (annotationValue != null) | 
| 887 | 892 |                         { | 
| 888 |  | -                            return (Type?)annotation.Value; | 
|  | 893 | +                            if (providerClrType != null) | 
|  | 894 | +                            { | 
|  | 895 | +                                throw new InvalidOperationException( | 
|  | 896 | +                                    CoreStrings.ConflictingRelationshipConversions( | 
|  | 897 | +                                        DeclaringType.DisplayName(), Name, | 
|  | 898 | +                                        providerClrType.ShortDisplayName(), annotationValue.ShortDisplayName())); | 
|  | 899 | +                            } | 
|  | 900 | + | 
|  | 901 | +                            if (valueConverter != null | 
|  | 902 | +                                && throwOnValueConverterConflict) | 
|  | 903 | +                            { | 
|  | 904 | +                                throw new InvalidOperationException( | 
|  | 905 | +                                    CoreStrings.ConflictingRelationshipConversions( | 
|  | 906 | +                                        DeclaringType.DisplayName(), Name, | 
|  | 907 | +                                        valueConverter.GetType().ShortDisplayName(), annotationValue.ShortDisplayName())); | 
|  | 908 | +                            } | 
|  | 909 | + | 
|  | 910 | +                            providerClrType = annotationValue; | 
| 889 | 911 |                         } | 
|  | 912 | +                        annotationFound = true; | 
|  | 913 | +                    } | 
| 890 | 914 | 
 | 
| 891 |  | -                        nextProperty = principalProperty; | 
|  | 915 | +                    if (!annotationFound) | 
|  | 916 | +                    { | 
|  | 917 | +                        if (cyclePosition == maxCycleLength) | 
|  | 918 | +                        { | 
|  | 919 | +                            // We need to use all possible factors to ensure a different cycleBreakingProperty is selected | 
|  | 920 | +                            // each time when traversing properties that participate in multiple relationship cycles | 
|  | 921 | +                            queue.Enqueue((principalProperty, property, 0, maxCycleLength * HashHelpers.GetPrime(maxCycleLength + 1))); | 
|  | 922 | +                        } | 
|  | 923 | +                        else | 
|  | 924 | +                        { | 
|  | 925 | +                            queue.Enqueue((principalProperty, cycleBreakingProperty, cyclePosition + 1, maxCycleLength)); | 
|  | 926 | +                        } | 
| 892 | 927 |                     } | 
|  | 928 | +                    break; | 
| 893 | 929 |                 } | 
| 894 | 930 |             } | 
| 895 |  | - | 
| 896 |  | -            if (nextProperty == null) | 
| 897 |  | -            { | 
| 898 |  | -                break; | 
| 899 |  | -            } | 
| 900 |  | - | 
| 901 |  | -            property = nextProperty; | 
| 902 | 931 |         } | 
| 903 | 932 | 
 | 
| 904 |  | -        return i == ForeignKey.LongestFkChainAllowedLength | 
| 905 |  | -            ? throw new InvalidOperationException( | 
| 906 |  | -                CoreStrings.RelationshipCycle( | 
| 907 |  | -                    DeclaringType.DisplayName(), Name, "ProviderClrType")) | 
| 908 |  | -            : null; | 
|  | 933 | +        return (valueConverter, providerClrType); | 
| 909 | 934 |     } | 
| 910 | 935 | 
 | 
| 911 | 936 |     /// <summary> | 
| @@ -1376,7 +1401,7 @@ public virtual bool IsForeignKey() | 
| 1376 | 1401 |     ///     doing so can result in application failures when updating to a new Entity Framework Core release. | 
| 1377 | 1402 |     /// </summary> | 
| 1378 | 1403 |     public virtual IEnumerable<ForeignKey> GetContainingForeignKeys() | 
| 1379 |  | -        => ForeignKeys?.OrderBy(fk => fk.Properties, PropertyListComparer.Instance) ?? Enumerable.Empty<ForeignKey>(); | 
|  | 1404 | +        => ForeignKeys?.OrderBy(fk => fk, ForeignKeyComparer.Instance) ?? Enumerable.Empty<ForeignKey>(); | 
| 1380 | 1405 | 
 | 
| 1381 | 1406 |     /// <summary> | 
| 1382 | 1407 |     ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to | 
|  | 
0 commit comments