|
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,179 @@ 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 | + ? default |
| 878 | + : throw new InvalidOperationException( |
| 879 | + RelationalStrings.JsonRequiredEntityWithNullJson(typeof(TEntity).Name)); |
886 | 880 | }
|
| 881 | + |
| 882 | + var manager = new Utf8JsonReaderManager(jsonReaderData); |
| 883 | + |
| 884 | + if (manager.CurrentReader.TokenType == JsonTokenType.Null) |
| 885 | + { |
| 886 | + return nullable |
| 887 | + ? default |
| 888 | + : throw new InvalidOperationException( |
| 889 | + RelationalStrings.JsonRequiredEntityWithNullJson(typeof(TEntity).Name)); |
| 890 | + } |
| 891 | + |
| 892 | + if (manager.CurrentReader.TokenType == JsonTokenType.StartObject) |
| 893 | + { |
| 894 | + manager.CaptureState(); |
| 895 | + var result = shaper(queryContext, keyPropertyValues, jsonReaderData); |
| 896 | + |
| 897 | + return result; |
| 898 | + } |
| 899 | + |
| 900 | + // TODO: cleanup & add strings when all is ready |
| 901 | + throw new InvalidOperationException("Invalid token type: " + manager.CurrentReader.TokenType.ToString()); |
887 | 902 | }
|
888 | 903 |
|
889 |
| - private static void IncludeJsonEntityCollection<TIncludingEntity, TIncludedCollectionElement>( |
| 904 | + private static TResult? MaterializeJsonEntityCollection<TEntity, TResult>( |
890 | 905 | QueryContext queryContext,
|
891 |
| - JsonElement? jsonElement, |
892 | 906 | 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 |
| 907 | + JsonReaderData jsonReaderData, |
| 908 | + INavigationBase navigation, |
| 909 | + Func<QueryContext, object[], JsonReaderData, TEntity> innerShaper) |
| 910 | + where TEntity : class |
| 911 | + where TResult : ICollection<TEntity> |
898 | 912 | {
|
899 |
| - if (jsonElement.HasValue && jsonElement.Value.ValueKind != JsonValueKind.Null) |
| 913 | + if (jsonReaderData == null) |
| 914 | + { |
| 915 | + return default; |
| 916 | + } |
| 917 | + |
| 918 | + var manager = new Utf8JsonReaderManager(jsonReaderData); |
| 919 | + var tokenType = manager.CurrentReader.TokenType; |
| 920 | + |
| 921 | + if (tokenType == JsonTokenType.Null) |
900 | 922 | {
|
| 923 | + return default; |
| 924 | + } |
| 925 | + |
| 926 | + if (tokenType == JsonTokenType.StartArray) |
| 927 | + { |
| 928 | + var collectionAccessor = navigation.GetCollectionAccessor(); |
| 929 | + var result = (TResult)collectionAccessor!.Create(); |
| 930 | + |
901 | 931 | var newKeyPropertyValues = new object[keyPropertyValues.Length + 1];
|
902 | 932 | Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length);
|
903 | 933 |
|
| 934 | + tokenType = manager.MoveNext(); |
| 935 | + |
904 | 936 | var i = 0;
|
905 |
| - foreach (var jsonArrayElement in jsonElement.Value.EnumerateArray()) |
| 937 | + while (tokenType != JsonTokenType.EndArray) |
906 | 938 | {
|
907 | 939 | newKeyPropertyValues[^1] = ++i;
|
908 | 940 |
|
909 |
| - var resultElement = innerShaper(queryContext, newKeyPropertyValues, jsonArrayElement); |
| 941 | + if (tokenType == JsonTokenType.StartObject) |
| 942 | + { |
| 943 | + manager.CaptureState(); |
| 944 | + var entity = innerShaper(queryContext, newKeyPropertyValues, jsonReaderData); |
| 945 | + result.Add(entity); |
| 946 | + manager = new Utf8JsonReaderManager(manager.Data); |
910 | 947 |
|
911 |
| - fixup(entity, resultElement); |
| 948 | + if (manager.CurrentReader.TokenType != JsonTokenType.EndObject) |
| 949 | + { |
| 950 | + // TODO: cleanup & add strings when all is ready |
| 951 | + throw new InvalidOperationException("expecting end object, got: " + tokenType.ToString()); |
| 952 | + } |
| 953 | + |
| 954 | + tokenType = manager.MoveNext(); |
| 955 | + } |
912 | 956 | }
|
| 957 | + |
| 958 | + manager.CaptureState(); |
| 959 | + |
| 960 | + return result; |
913 | 961 | }
|
| 962 | + |
| 963 | + // TODO: cleanup & add strings when all is ready |
| 964 | + throw new InvalidOperationException("Expecting StartArray token, got: " + tokenType.ToString()); |
914 | 965 | }
|
915 | 966 |
|
916 |
| - private static TEntity? MaterializeJsonEntity<TEntity>( |
| 967 | + private static void IncludeJsonEntityReference<TIncludingEntity, TIncludedEntity>( |
917 | 968 | QueryContext queryContext,
|
918 |
| - JsonElement? jsonElement, |
919 | 969 | object[] keyPropertyValues,
|
920 |
| - bool nullable, |
921 |
| - Func<QueryContext, object[], JsonElement, TEntity> shaper) |
922 |
| - where TEntity : class |
| 970 | + JsonReaderData jsonReaderData, |
| 971 | + TIncludingEntity entity, |
| 972 | + Func<QueryContext, object[], JsonReaderData, TIncludedEntity> innerShaper, |
| 973 | + Action<TIncludingEntity, TIncludedEntity> fixup) |
| 974 | + where TIncludingEntity : class |
| 975 | + where TIncludedEntity : class |
923 | 976 | {
|
924 |
| - if (jsonElement.HasValue && jsonElement.Value.ValueKind != JsonValueKind.Null) |
| 977 | + if (jsonReaderData == null) |
925 | 978 | {
|
926 |
| - var result = shaper(queryContext, keyPropertyValues, jsonElement.Value); |
927 |
| - |
928 |
| - return result; |
929 |
| - } |
930 |
| - |
931 |
| - if (nullable) |
932 |
| - { |
933 |
| - return default; |
| 979 | + return; |
934 | 980 | }
|
935 | 981 |
|
936 |
| - throw new InvalidOperationException( |
937 |
| - RelationalStrings.JsonRequiredEntityWithNullJson(typeof(TEntity).Name)); |
| 982 | + var included = innerShaper(queryContext, keyPropertyValues, jsonReaderData); |
| 983 | + fixup(entity, included); |
938 | 984 | }
|
939 | 985 |
|
940 |
| - private static TResult? MaterializeJsonEntityCollection<TEntity, TResult>( |
| 986 | + private static void IncludeJsonEntityCollection<TIncludingEntity, TIncludedCollectionElement>( |
941 | 987 | QueryContext queryContext,
|
942 |
| - JsonElement? jsonElement, |
943 | 988 | object[] keyPropertyValues,
|
944 |
| - INavigationBase navigation, |
945 |
| - Func<QueryContext, object[], JsonElement, TEntity> innerShaper) |
946 |
| - where TEntity : class |
947 |
| - where TResult : ICollection<TEntity> |
| 989 | + JsonReaderData jsonReaderData, |
| 990 | + TIncludingEntity entity, |
| 991 | + Func<QueryContext, object[], JsonReaderData, TIncludedCollectionElement> innerShaper, |
| 992 | + Action<TIncludingEntity, TIncludedCollectionElement> fixup) |
| 993 | + where TIncludingEntity : class |
| 994 | + where TIncludedCollectionElement : class |
948 | 995 | {
|
949 |
| - if (jsonElement.HasValue && jsonElement.Value.ValueKind != JsonValueKind.Null) |
| 996 | + if (jsonReaderData == null) |
950 | 997 | {
|
951 |
| - var collectionAccessor = navigation.GetCollectionAccessor(); |
952 |
| - var result = (TResult)collectionAccessor!.Create(); |
| 998 | + return; |
| 999 | + } |
953 | 1000 |
|
| 1001 | + var manager = new Utf8JsonReaderManager(jsonReaderData); |
| 1002 | + var tokenType = manager.CurrentReader.TokenType; |
| 1003 | + if (tokenType == JsonTokenType.StartArray) |
| 1004 | + { |
954 | 1005 | var newKeyPropertyValues = new object[keyPropertyValues.Length + 1];
|
955 | 1006 | Array.Copy(keyPropertyValues, newKeyPropertyValues, keyPropertyValues.Length);
|
956 | 1007 |
|
| 1008 | + tokenType = manager.MoveNext(); |
| 1009 | + |
957 | 1010 | var i = 0;
|
958 |
| - foreach (var jsonArrayElement in jsonElement.Value.EnumerateArray()) |
| 1011 | + while (tokenType != JsonTokenType.EndArray) |
959 | 1012 | {
|
960 | 1013 | newKeyPropertyValues[^1] = ++i;
|
961 | 1014 |
|
962 |
| - var resultElement = innerShaper(queryContext, newKeyPropertyValues, jsonArrayElement); |
| 1015 | + if (tokenType == JsonTokenType.StartObject) |
| 1016 | + { |
| 1017 | + manager.CaptureState(); |
| 1018 | + var resultElement = innerShaper(queryContext, newKeyPropertyValues, jsonReaderData); |
| 1019 | + fixup(entity, resultElement); |
| 1020 | + manager = new Utf8JsonReaderManager(manager.Data); |
| 1021 | + |
| 1022 | + if (manager.CurrentReader.TokenType != JsonTokenType.EndObject) |
| 1023 | + { |
| 1024 | + // TODO: cleanup & add strings when all is ready |
| 1025 | + throw new InvalidOperationException("expecting end object, got: " + tokenType.ToString()); |
| 1026 | + } |
963 | 1027 |
|
964 |
| - result.Add(resultElement); |
| 1028 | + tokenType = manager.MoveNext(); |
| 1029 | + } |
965 | 1030 | }
|
966 | 1031 |
|
967 |
| - return result; |
| 1032 | + manager.CaptureState(); |
| 1033 | + } |
| 1034 | + else |
| 1035 | + { |
| 1036 | + // TODO: cleanup & add strings when all is ready |
| 1037 | + throw new InvalidOperationException("Expecting StartArray token, got: " + tokenType.ToString()); |
968 | 1038 | }
|
969 |
| - |
970 |
| - return default; |
971 | 1039 | }
|
972 | 1040 |
|
973 | 1041 | private static async Task TaskAwaiter(Func<Task>[] taskFactories)
|
|
0 commit comments