|
5 | 5 | using System.Text.Json;
|
6 | 6 | using Microsoft.EntityFrameworkCore.Internal;
|
7 | 7 | using Microsoft.EntityFrameworkCore.Query.Internal;
|
| 8 | +using Microsoft.EntityFrameworkCore.Storage.Json; |
8 | 9 |
|
9 | 10 | namespace Microsoft.EntityFrameworkCore.Query;
|
10 | 11 |
|
@@ -67,8 +68,8 @@ private static readonly MethodInfo MaterializeJsonEntityMethodInfo
|
67 | 68 | private static readonly MethodInfo MaterializeJsonEntityCollectionMethodInfo
|
68 | 69 | = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(MaterializeJsonEntityCollection))!;
|
69 | 70 |
|
70 |
| - private static readonly MethodInfo ExtractJsonPropertyMethodInfo |
71 |
| - = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(ExtractJsonProperty))!; |
| 71 | + private static readonly MethodInfo InverseCollectionFixupMethod |
| 72 | + = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(InverseCollectionFixup))!; |
72 | 73 |
|
73 | 74 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
74 | 75 | private static TValue ThrowReadValueException<TValue>(
|
@@ -123,13 +124,6 @@ private static TValue ThrowExtractJsonPropertyException<TValue>(
|
123 | 124 | exception);
|
124 | 125 | }
|
125 | 126 |
|
126 |
| - private static T? ExtractJsonProperty<T>(JsonElement element, string propertyName, bool nullable) |
127 |
| - => nullable |
128 |
| - ? element.TryGetProperty(propertyName, out var jsonValue) |
129 |
| - ? jsonValue.Deserialize<T>() |
130 |
| - : default |
131 |
| - : element.GetProperty(propertyName).Deserialize<T>(); |
132 |
| - |
133 | 127 | private static void IncludeReference<TEntity, TIncludingEntity, TIncludedEntity>(
|
134 | 128 | QueryContext queryContext,
|
135 | 129 | TEntity entity,
|
@@ -869,105 +863,189 @@ static async Task<RelationalDataReader> InitializeReaderAsync(
|
869 | 863 | dataReaderContext.HasNext = false;
|
870 | 864 | }
|
871 | 865 |
|
872 |
| - private static void IncludeJsonEntityReference<TIncludingEntity, TIncludedEntity>( |
| 866 | + private static TEntity? MaterializeJsonEntity<TEntity>( |
873 | 867 | QueryContext queryContext,
|
874 |
| - JsonElement? jsonElement, |
875 | 868 | object[] keyPropertyValues,
|
876 |
| - TIncludingEntity entity, |
877 |
| - Func<QueryContext, object[], JsonElement, TIncludedEntity> innerShaper, |
878 |
| - Action<TIncludingEntity, TIncludedEntity> fixup) |
879 |
| - where TIncludingEntity : class |
880 |
| - where TIncludedEntity : class |
| 869 | + JsonReaderData? jsonReaderData, |
| 870 | + bool nullable, |
| 871 | + Func<QueryContext, object[], JsonReaderData, TEntity> shaper) |
| 872 | + where TEntity : class |
881 | 873 | {
|
882 |
| - if (jsonElement.HasValue && jsonElement.Value.ValueKind != JsonValueKind.Null) |
| 874 | + if (jsonReaderData == null) |
883 | 875 | {
|
884 |
| - var included = innerShaper(queryContext, keyPropertyValues, jsonElement.Value); |
885 |
| - fixup(entity, included); |
| 876 | + return nullable |
| 877 | + ? null |
| 878 | + : throw new InvalidOperationException( |
| 879 | + RelationalStrings.JsonRequiredEntityWithNullJson(typeof(TEntity).Name)); |
| 880 | + } |
| 881 | + |
| 882 | + var manager = new Utf8JsonReaderManager(jsonReaderData); |
| 883 | + var tokenType = manager.CurrentReader.TokenType; |
| 884 | + |
| 885 | + if (tokenType == JsonTokenType.Null) |
| 886 | + { |
| 887 | + return nullable |
| 888 | + ? null |
| 889 | + : throw new InvalidOperationException( |
| 890 | + RelationalStrings.JsonRequiredEntityWithNullJson(typeof(TEntity).Name)); |
| 891 | + } |
| 892 | + |
| 893 | + if (tokenType != JsonTokenType.StartObject) |
| 894 | + { |
| 895 | + throw new InvalidOperationException( |
| 896 | + RelationalStrings.JsonReaderInvalidTokenType(tokenType.ToString())); |
886 | 897 | }
|
| 898 | + |
| 899 | + manager.CaptureState(); |
| 900 | + var result = shaper(queryContext, keyPropertyValues, jsonReaderData); |
| 901 | + |
| 902 | + return result; |
887 | 903 | }
|
888 | 904 |
|
889 |
| - private static void IncludeJsonEntityCollection<TIncludingEntity, TIncludedCollectionElement>( |
| 905 | + private static TResult? MaterializeJsonEntityCollection<TEntity, TResult>( |
890 | 906 | QueryContext queryContext,
|
891 |
| - JsonElement? jsonElement, |
892 | 907 | object[] keyPropertyValues,
|
893 |
| - TIncludingEntity entity, |
894 |
| - Func<QueryContext, object[], JsonElement, TIncludedCollectionElement> innerShaper, |
895 |
| - Action<TIncludingEntity, TIncludedCollectionElement> fixup) |
896 |
| - where TIncludingEntity : class |
897 |
| - where TIncludedCollectionElement : class |
| 908 | + JsonReaderData? jsonReaderData, |
| 909 | + INavigationBase navigation, |
| 910 | + Func<QueryContext, object[], JsonReaderData, TEntity> innerShaper) |
| 911 | + where TEntity : class |
| 912 | + where TResult : ICollection<TEntity> |
898 | 913 | {
|
899 |
| - if (jsonElement.HasValue && jsonElement.Value.ValueKind != JsonValueKind.Null) |
| 914 | + if (jsonReaderData == null) |
900 | 915 | {
|
901 |
| - var newKeyPropertyValues = new object[keyPropertyValues.Length + 1]; |
902 |
| - Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length); |
| 916 | + return default; |
| 917 | + } |
| 918 | + |
| 919 | + var manager = new Utf8JsonReaderManager(jsonReaderData); |
| 920 | + var tokenType = manager.CurrentReader.TokenType; |
| 921 | + |
| 922 | + if (tokenType == JsonTokenType.Null) |
| 923 | + { |
| 924 | + return default; |
| 925 | + } |
903 | 926 |
|
904 |
| - var i = 0; |
905 |
| - foreach (var jsonArrayElement in jsonElement.Value.EnumerateArray()) |
| 927 | + if (tokenType != JsonTokenType.StartArray) |
| 928 | + { |
| 929 | + throw new InvalidOperationException( |
| 930 | + RelationalStrings.JsonReaderInvalidTokenType(tokenType.ToString())); |
| 931 | + } |
| 932 | + |
| 933 | + var collectionAccessor = navigation.GetCollectionAccessor(); |
| 934 | + var result = (TResult)collectionAccessor!.Create(); |
| 935 | + |
| 936 | + var newKeyPropertyValues = new object[keyPropertyValues.Length + 1]; |
| 937 | + Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length); |
| 938 | + |
| 939 | + tokenType = manager.MoveNext(); |
| 940 | + |
| 941 | + var i = 0; |
| 942 | + while (tokenType != JsonTokenType.EndArray) |
| 943 | + { |
| 944 | + newKeyPropertyValues[^1] = ++i; |
| 945 | + |
| 946 | + if (tokenType == JsonTokenType.StartObject) |
906 | 947 | {
|
907 |
| - newKeyPropertyValues[^1] = ++i; |
| 948 | + manager.CaptureState(); |
| 949 | + var entity = innerShaper(queryContext, newKeyPropertyValues, jsonReaderData); |
| 950 | + result.Add(entity); |
| 951 | + manager = new Utf8JsonReaderManager(manager.Data); |
908 | 952 |
|
909 |
| - var resultElement = innerShaper(queryContext, newKeyPropertyValues, jsonArrayElement); |
| 953 | + if (manager.CurrentReader.TokenType != JsonTokenType.EndObject) |
| 954 | + { |
| 955 | + throw new InvalidOperationException( |
| 956 | + RelationalStrings.JsonReaderInvalidTokenType(tokenType.ToString())); |
| 957 | + } |
910 | 958 |
|
911 |
| - fixup(entity, resultElement); |
| 959 | + tokenType = manager.MoveNext(); |
912 | 960 | }
|
913 | 961 | }
|
| 962 | + |
| 963 | + manager.CaptureState(); |
| 964 | + |
| 965 | + return result; |
914 | 966 | }
|
915 | 967 |
|
916 |
| - private static TEntity? MaterializeJsonEntity<TEntity>( |
| 968 | + private static void IncludeJsonEntityReference<TIncludingEntity, TIncludedEntity>( |
917 | 969 | QueryContext queryContext,
|
918 |
| - JsonElement? jsonElement, |
919 | 970 | object[] keyPropertyValues,
|
920 |
| - bool nullable, |
921 |
| - Func<QueryContext, object[], JsonElement, TEntity> shaper) |
922 |
| - where TEntity : class |
| 971 | + JsonReaderData? jsonReaderData, |
| 972 | + TIncludingEntity entity, |
| 973 | + Func<QueryContext, object[], JsonReaderData, TIncludedEntity> innerShaper, |
| 974 | + Action<TIncludingEntity, TIncludedEntity> fixup, |
| 975 | + bool trackingQuery) |
| 976 | + where TIncludingEntity : class |
| 977 | + where TIncludedEntity : class |
923 | 978 | {
|
924 |
| - if (jsonElement.HasValue && jsonElement.Value.ValueKind != JsonValueKind.Null) |
| 979 | + if (jsonReaderData == null) |
925 | 980 | {
|
926 |
| - var result = shaper(queryContext, keyPropertyValues, jsonElement.Value); |
927 |
| - |
928 |
| - return result; |
| 981 | + return; |
929 | 982 | }
|
930 | 983 |
|
931 |
| - if (nullable) |
| 984 | + var included = innerShaper(queryContext, keyPropertyValues, jsonReaderData); |
| 985 | + |
| 986 | + if (!trackingQuery) |
932 | 987 | {
|
933 |
| - return default; |
| 988 | + fixup(entity, included); |
934 | 989 | }
|
935 |
| - |
936 |
| - throw new InvalidOperationException( |
937 |
| - RelationalStrings.JsonRequiredEntityWithNullJson(typeof(TEntity).Name)); |
938 | 990 | }
|
939 | 991 |
|
940 |
| - private static TResult? MaterializeJsonEntityCollection<TEntity, TResult>( |
| 992 | + private static void IncludeJsonEntityCollection<TIncludingEntity, TIncludedCollectionElement>( |
941 | 993 | QueryContext queryContext,
|
942 |
| - JsonElement? jsonElement, |
943 | 994 | object[] keyPropertyValues,
|
944 |
| - INavigationBase navigation, |
945 |
| - Func<QueryContext, object[], JsonElement, TEntity> innerShaper) |
946 |
| - where TEntity : class |
947 |
| - where TResult : ICollection<TEntity> |
| 995 | + JsonReaderData? jsonReaderData, |
| 996 | + TIncludingEntity entity, |
| 997 | + Func<QueryContext, object[], JsonReaderData, TIncludedCollectionElement> innerShaper, |
| 998 | + Action<TIncludingEntity, TIncludedCollectionElement> fixup, |
| 999 | + bool trackingQuery) |
| 1000 | + where TIncludingEntity : class |
| 1001 | + where TIncludedCollectionElement : class |
948 | 1002 | {
|
949 |
| - if (jsonElement.HasValue && jsonElement.Value.ValueKind != JsonValueKind.Null) |
| 1003 | + if (jsonReaderData == null) |
950 | 1004 | {
|
951 |
| - var collectionAccessor = navigation.GetCollectionAccessor(); |
952 |
| - var result = (TResult)collectionAccessor!.Create(); |
| 1005 | + return; |
| 1006 | + } |
| 1007 | + |
| 1008 | + var manager = new Utf8JsonReaderManager(jsonReaderData); |
| 1009 | + var tokenType = manager.CurrentReader.TokenType; |
| 1010 | + |
| 1011 | + if (tokenType != JsonTokenType.StartArray) |
| 1012 | + { |
| 1013 | + throw new InvalidOperationException( |
| 1014 | + RelationalStrings.JsonReaderInvalidTokenType(tokenType.ToString())); |
| 1015 | + } |
953 | 1016 |
|
954 |
| - var newKeyPropertyValues = new object[keyPropertyValues.Length + 1]; |
955 |
| - Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length); |
| 1017 | + var newKeyPropertyValues = new object[keyPropertyValues.Length + 1]; |
| 1018 | + Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length); |
956 | 1019 |
|
957 |
| - var i = 0; |
958 |
| - foreach (var jsonArrayElement in jsonElement.Value.EnumerateArray()) |
| 1020 | + tokenType = manager.MoveNext(); |
| 1021 | + |
| 1022 | + var i = 0; |
| 1023 | + while (tokenType != JsonTokenType.EndArray) |
| 1024 | + { |
| 1025 | + newKeyPropertyValues[^1] = ++i; |
| 1026 | + |
| 1027 | + if (tokenType == JsonTokenType.StartObject) |
959 | 1028 | {
|
960 |
| - newKeyPropertyValues[^1] = ++i; |
| 1029 | + manager.CaptureState(); |
| 1030 | + var resultElement = innerShaper(queryContext, newKeyPropertyValues, jsonReaderData); |
| 1031 | + |
| 1032 | + if (!trackingQuery) |
| 1033 | + { |
| 1034 | + fixup(entity, resultElement); |
| 1035 | + } |
961 | 1036 |
|
962 |
| - var resultElement = innerShaper(queryContext, newKeyPropertyValues, jsonArrayElement); |
| 1037 | + manager = new Utf8JsonReaderManager(manager.Data); |
| 1038 | + if (manager.CurrentReader.TokenType != JsonTokenType.EndObject) |
| 1039 | + { |
| 1040 | + throw new InvalidOperationException( |
| 1041 | + RelationalStrings.JsonReaderInvalidTokenType(tokenType.ToString())); |
| 1042 | + } |
963 | 1043 |
|
964 |
| - result.Add(resultElement); |
| 1044 | + tokenType = manager.MoveNext(); |
965 | 1045 | }
|
966 |
| - |
967 |
| - return result; |
968 | 1046 | }
|
969 | 1047 |
|
970 |
| - return default; |
| 1048 | + manager.CaptureState(); |
971 | 1049 | }
|
972 | 1050 |
|
973 | 1051 | private static async Task TaskAwaiter(Func<Task>[] taskFactories)
|
|
0 commit comments