| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | #nullable enable |
| | 5 | |
|
| | 6 | | using System; |
| | 7 | | using System.Collections.Concurrent; |
| | 8 | | using System.Collections.Generic; |
| | 9 | | using System.Globalization; |
| | 10 | | using System.Reflection; |
| | 11 | |
|
| | 12 | | namespace Azure.Data.Tables |
| | 13 | | { |
| | 14 | | internal static class DictionaryTableExtensions |
| | 15 | | { |
| | 16 | | /// <summary> |
| | 17 | | /// A cache for reflected <see cref="PropertyInfo"/> array for the given <see cref="Type"/>. |
| | 18 | | /// </summary> |
| 2 | 19 | | private static readonly ConcurrentDictionary<Type, PropertyInfo[]> s_propertyInfoCache = new ConcurrentDictionar |
| | 20 | |
|
| | 21 | | /// <summary> |
| | 22 | | /// Returns a new Dictionary with the appropriate Odata type annotation for a given propertyName value pair. |
| | 23 | | /// The default case is intentionally unhandled as this means that no type annotation for the specified type is |
| | 24 | | /// This is because the type is naturally serialized in a way that the table service can interpret without hints |
| | 25 | | /// </summary> |
| | 26 | | internal static Dictionary<string, object> ToOdataAnnotatedDictionary(this IDictionary<string, object> tableEnti |
| | 27 | | { |
| 840 | 28 | | var annotatedDictionary = new Dictionary<string, object>(tableEntityProperties.Keys.Count * 2); |
| | 29 | |
|
| 17352 | 30 | | foreach (var item in tableEntityProperties) |
| | 31 | | { |
| 7836 | 32 | | annotatedDictionary[item.Key] = item.Value; |
| | 33 | |
|
| 7836 | 34 | | switch (item.Value) |
| | 35 | | { |
| | 36 | | case byte[] _: |
| 704 | 37 | | annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmBinary; |
| 704 | 38 | | break; |
| | 39 | | case long _: |
| 704 | 40 | | annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmInt64; |
| | 41 | | // Int64 / long should be serialized as string. |
| 704 | 42 | | annotatedDictionary[item.Key] = item.Value.ToString(); |
| 704 | 43 | | break; |
| | 44 | | case double _: |
| 1408 | 45 | | annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmDouble; |
| 1408 | 46 | | break; |
| | 47 | | case Guid _: |
| 704 | 48 | | annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmGuid; |
| 704 | 49 | | break; |
| | 50 | | case DateTimeOffset _: |
| 0 | 51 | | annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmDateTime; |
| 0 | 52 | | break; |
| | 53 | | case DateTime _: |
| 792 | 54 | | annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmDateTime; |
| | 55 | | break; |
| | 56 | | } |
| | 57 | | } |
| | 58 | |
|
| 840 | 59 | | return annotatedDictionary; |
| | 60 | | } |
| | 61 | |
|
| | 62 | | /// <summary> |
| | 63 | | /// Cleans a List of Dictionaries of its Odata type annotations, while using them to cast its entities according |
| | 64 | | /// </summary> |
| | 65 | | internal static void CastAndRemoveAnnotations(this IReadOnlyList<IDictionary<string, object>> entityList) |
| | 66 | | { |
| 0 | 67 | | var typeAnnotationsWithKeys = new Dictionary<string, (string typeAnnotation, string annotationKey)>(); |
| | 68 | |
|
| 0 | 69 | | foreach (var entity in entityList) |
| | 70 | | { |
| 0 | 71 | | entity.CastAndRemoveAnnotations(typeAnnotationsWithKeys); |
| | 72 | | } |
| 0 | 73 | | } |
| | 74 | |
|
| | 75 | | /// <summary> |
| | 76 | | /// Cleans a Dictionary of its Odata type annotations, while using them to cast its entities accordingly. |
| | 77 | | /// </summary> |
| | 78 | | internal static void CastAndRemoveAnnotations(this IDictionary<string, object> entity, Dictionary<string, (strin |
| | 79 | | { |
| 1588 | 80 | | typeAnnotationsWithKeys ??= new Dictionary<string, (string typeAnnotation, string annotationKey)>(); |
| 1588 | 81 | | var spanOdataSuffix = TableConstants.Odata.OdataTypeString.AsSpan(); |
| | 82 | |
|
| 1588 | 83 | | typeAnnotationsWithKeys.Clear(); |
| | 84 | |
|
| 73616 | 85 | | foreach (var propertyName in entity.Keys) |
| | 86 | | { |
| 35220 | 87 | | var spanPropertyName = propertyName.AsSpan(); |
| 35220 | 88 | | var iSuffix = spanPropertyName.IndexOf(spanOdataSuffix); |
| 35220 | 89 | | if (iSuffix > 0) |
| | 90 | | { |
| | 91 | | // This property is an Odata annotation. Save it in the typeAnnoations dictionary. |
| 9332 | 92 | | typeAnnotationsWithKeys[spanPropertyName.Slice(0, iSuffix).ToString()] = (typeAnnotation: (entity[pr |
| | 93 | | } |
| | 94 | | } |
| | 95 | |
|
| | 96 | | // Iterate through the types that are serialized as string by default and Parse them as the correct type, as |
| 21840 | 97 | | foreach (var annotation in typeAnnotationsWithKeys.Keys) |
| | 98 | | { |
| 9332 | 99 | | entity[annotation] = typeAnnotationsWithKeys[annotation].typeAnnotation switch |
| 9332 | 100 | | { |
| 11020 | 101 | | TableConstants.Odata.EdmBinary => Convert.FromBase64String(entity[annotation] as string), |
| 13104 | 102 | | TableConstants.Odata.EdmDateTime => DateTime.Parse(entity[annotation] as string, CultureInfo.Invaria |
| 11020 | 103 | | TableConstants.Odata.EdmGuid => Guid.Parse(entity[annotation] as string), |
| 11516 | 104 | | TableConstants.Odata.EdmInt64 => long.Parse(entity[annotation] as string, CultureInfo.InvariantCultu |
| 0 | 105 | | _ => throw new NotSupportedException("Not supported type " + typeAnnotationsWithKeys[annotation]) |
| 9332 | 106 | | }; |
| | 107 | |
|
| | 108 | | // Remove the type annotation property from the dictionary. |
| 9332 | 109 | | entity.Remove(typeAnnotationsWithKeys[annotation].annotationKey); |
| | 110 | | } |
| 1588 | 111 | | } |
| | 112 | |
|
| | 113 | | /// <summary> |
| | 114 | | /// Converts a List of Dictionaries containing properties and Odata type annotations to a custom entity type. |
| | 115 | | /// </summary> |
| | 116 | | internal static List<T> ToTableEntityList<T>(this IReadOnlyList<IDictionary<string, object>> entityList) where T |
| | 117 | | { |
| 732 | 118 | | PropertyInfo[] properties = s_propertyInfoCache.GetOrAdd(typeof(T), (type) => |
| 732 | 119 | | { |
| 734 | 120 | | return type.GetProperties(BindingFlags.Instance | BindingFlags.Public); |
| 732 | 121 | | }); |
| | 122 | |
|
| 732 | 123 | | var result = new List<T>(entityList.Count); |
| | 124 | |
|
| 5488 | 125 | | foreach (var entity in entityList) |
| | 126 | | { |
| 2012 | 127 | | var tableEntity = entity.ToTableEntity<T>(properties); |
| | 128 | |
|
| 2012 | 129 | | result.Add(tableEntity); |
| | 130 | | } |
| | 131 | |
|
| 732 | 132 | | return result; |
| | 133 | | } |
| | 134 | |
|
| | 135 | | /// <summary> |
| | 136 | | /// Cleans a Dictionary of its Odata type annotations, while using them to cast its entities accordingly. |
| | 137 | | /// </summary> |
| | 138 | | internal static T ToTableEntity<T>(this IDictionary<string, object> entity, PropertyInfo[]? properties = null) w |
| | 139 | | { |
| 3724 | 140 | | var result = new T(); |
| | 141 | |
|
| 3724 | 142 | | if (result is IDictionary<string, object> dictionary) |
| | 143 | | { |
| 1588 | 144 | | entity.CastAndRemoveAnnotations(); |
| | 145 | |
|
| 54952 | 146 | | foreach (var entProperty in entity.Keys) |
| | 147 | | { |
| 25888 | 148 | | dictionary[entProperty] = entity[entProperty]; |
| | 149 | | } |
| | 150 | |
|
| 1588 | 151 | | return result; |
| | 152 | | } |
| | 153 | |
|
| 2136 | 154 | | properties ??= s_propertyInfoCache.GetOrAdd(typeof(T), (type) => |
| 2136 | 155 | | { |
| 2140 | 156 | | return type.GetProperties(BindingFlags.Instance | BindingFlags.Public); |
| 2136 | 157 | | }); |
| | 158 | |
|
| | 159 | | // Iterate through each property of the entity and set them as the correct type. |
| 101664 | 160 | | foreach (var property in properties) |
| | 161 | | { |
| 48696 | 162 | | if (entity.TryGetValue(property.Name, out var propertyValue)) |
| | 163 | | { |
| 37632 | 164 | | if (typeActions.TryGetValue(property.PropertyType, out var propertyAction)) |
| | 165 | | { |
| 37632 | 166 | | propertyAction(property, propertyValue, result); |
| | 167 | | } |
| | 168 | | else |
| | 169 | | { |
| 0 | 170 | | property.SetValue(result, propertyValue); |
| | 171 | | } |
| | 172 | | } |
| | 173 | | } |
| | 174 | |
|
| | 175 | | // Populate the ETag if present. |
| 2136 | 176 | | if (entity.TryGetValue(TableConstants.PropertyNames.Etag, out var etag)) |
| | 177 | | { |
| 2136 | 178 | | result.ETag = etag as string; |
| | 179 | | } |
| 2136 | 180 | | return result; |
| | 181 | | } |
| | 182 | |
|
| 2 | 183 | | private static Dictionary<Type, Action<PropertyInfo, object, object>> typeActions = new Dictionary<Type, Action< |
| 2 | 184 | | { |
| 2882 | 185 | | {typeof(byte[]), (property, propertyValue, result) => property.SetValue(result, Convert.FromBase64String(pr |
| 2882 | 186 | | {typeof(long), (property, propertyValue, result) => property.SetValue(result, long.Parse(propertyValue as s |
| 1490 | 187 | | {typeof(long?), (property, propertyValue, result) => property.SetValue(result, long.Parse(propertyValue as |
| 3626 | 188 | | {typeof(double), (property, propertyValue, result) => property.SetValue(result, propertyValue)}, |
| 1490 | 189 | | {typeof(double?), (property, propertyValue, result) => property.SetValue(result, propertyValue)}, |
| 1490 | 190 | | {typeof(bool), (property, propertyValue, result) => property.SetValue(result, (bool)propertyValue)}, |
| 1490 | 191 | | {typeof(bool?), (property, propertyValue, result) => property.SetValue(result, (bool?)propertyValue)}, |
| 2138 | 192 | | {typeof(Guid), (property, propertyValue, result) => property.SetValue(result, Guid.Parse(propertyValue as s |
| 746 | 193 | | {typeof(Guid?), (property, propertyValue, result) => property.SetValue(result, Guid.Parse(propertyValue as |
| 2138 | 194 | | {typeof(DateTimeOffset), (property, propertyValue, result) => property.SetValue(result, DateTimeOffset.Pars |
| 2882 | 195 | | {typeof(DateTimeOffset?), (property, propertyValue, result) => property.SetValue(result, DateTimeOffset.Par |
| 2138 | 196 | | {typeof(DateTime), (property, propertyValue, result) => property.SetValue(result, DateTime.Parse(propertyVa |
| 746 | 197 | | {typeof(DateTime?), (property, propertyValue, result) => property.SetValue(result, DateTime.Parse(propertyV |
| 7154 | 198 | | {typeof(string), (property, propertyValue, result) => property.SetValue(result, propertyValue as string)}, |
| 2882 | 199 | | {typeof(int), (property, propertyValue, result) => property.SetValue(result, (int)propertyValue)}, |
| 1490 | 200 | | {typeof(int?), (property, propertyValue, result) => property.SetValue(result, (int?)propertyValue)}, |
| 2 | 201 | | }; |
| | 202 | | } |
| | 203 | | } |