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