< Summary

Class:Azure.Data.Tables.DictionaryTableExtensions
Assembly:Azure.Data.Tables
File(s):C:\Git\azure-sdk-for-net\sdk\tables\Azure.Data.Tables\src\Extensions\DictionaryTableExtensions.cs
Covered lines:79
Uncovered lines:8
Coverable lines:87
Total lines:203
Line coverage:90.8% (79 of 87)
Covered branches:47
Total branches:52
Branch coverage:90.3% (47 of 52)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-90.8%90.38%
ToOdataAnnotatedDictionary(...)-88.24%92.86%
CastAndRemoveAnnotations(...)-0%0%
CastAndRemoveAnnotations(...)-94.74%93.75%
ToTableEntityList(...)-100%100%
ToTableEntity(...)-94.44%93.75%

File(s)

C:\Git\azure-sdk-for-net\sdk\tables\Azure.Data.Tables\src\Extensions\DictionaryTableExtensions.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4#nullable enable
 5
 6using System;
 7using System.Collections.Concurrent;
 8using System.Collections.Generic;
 9using System.Globalization;
 10using System.Reflection;
 11
 12namespace 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>
 219        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        {
 84028            var annotatedDictionary = new Dictionary<string, object>(tableEntityProperties.Keys.Count * 2);
 29
 1735230            foreach (var item in tableEntityProperties)
 31            {
 783632                annotatedDictionary[item.Key] = item.Value;
 33
 783634                switch (item.Value)
 35                {
 36                    case byte[] _:
 70437                        annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmBinary;
 70438                        break;
 39                    case long _:
 70440                        annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmInt64;
 41                        // Int64 / long should be serialized as string.
 70442                        annotatedDictionary[item.Key] = item.Value.ToString();
 70443                        break;
 44                    case double _:
 140845                        annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmDouble;
 140846                        break;
 47                    case Guid _:
 70448                        annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmGuid;
 70449                        break;
 50                    case DateTimeOffset _:
 051                        annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmDateTime;
 052                        break;
 53                    case DateTime _:
 79254                        annotatedDictionary[item.Key.ToOdataTypeString()] = TableConstants.Odata.EdmDateTime;
 55                        break;
 56                }
 57            }
 58
 84059            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        {
 067            var typeAnnotationsWithKeys = new Dictionary<string, (string typeAnnotation, string annotationKey)>();
 68
 069            foreach (var entity in entityList)
 70            {
 071                entity.CastAndRemoveAnnotations(typeAnnotationsWithKeys);
 72            }
 073        }
 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        {
 158880            typeAnnotationsWithKeys ??= new Dictionary<string, (string typeAnnotation, string annotationKey)>();
 158881            var spanOdataSuffix = TableConstants.Odata.OdataTypeString.AsSpan();
 82
 158883            typeAnnotationsWithKeys.Clear();
 84
 7361685            foreach (var propertyName in entity.Keys)
 86            {
 3522087                var spanPropertyName = propertyName.AsSpan();
 3522088                var iSuffix = spanPropertyName.IndexOf(spanOdataSuffix);
 3522089                if (iSuffix > 0)
 90                {
 91                    // This property is an Odata annotation. Save it in the typeAnnoations dictionary.
 933292                    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
 2184097            foreach (var annotation in typeAnnotationsWithKeys.Keys)
 98            {
 933299                entity[annotation] = typeAnnotationsWithKeys[annotation].typeAnnotation switch
 9332100                {
 11020101                    TableConstants.Odata.EdmBinary => Convert.FromBase64String(entity[annotation] as string),
 13104102                    TableConstants.Odata.EdmDateTime => DateTime.Parse(entity[annotation] as string, CultureInfo.Invaria
 11020103                    TableConstants.Odata.EdmGuid => Guid.Parse(entity[annotation] as string),
 11516104                    TableConstants.Odata.EdmInt64 => long.Parse(entity[annotation] as string, CultureInfo.InvariantCultu
 0105                    _ => throw new NotSupportedException("Not supported type " + typeAnnotationsWithKeys[annotation])
 9332106                };
 107
 108                // Remove the type annotation property from the dictionary.
 9332109                entity.Remove(typeAnnotationsWithKeys[annotation].annotationKey);
 110            }
 1588111        }
 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        {
 732118            PropertyInfo[] properties = s_propertyInfoCache.GetOrAdd(typeof(T), (type) =>
 732119            {
 734120                return type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 732121            });
 122
 732123            var result = new List<T>(entityList.Count);
 124
 5488125            foreach (var entity in entityList)
 126            {
 2012127                var tableEntity = entity.ToTableEntity<T>(properties);
 128
 2012129                result.Add(tableEntity);
 130            }
 131
 732132            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        {
 3724140            var result = new T();
 141
 3724142            if (result is IDictionary<string, object> dictionary)
 143            {
 1588144                entity.CastAndRemoveAnnotations();
 145
 54952146                foreach (var entProperty in entity.Keys)
 147                {
 25888148                    dictionary[entProperty] = entity[entProperty];
 149                }
 150
 1588151                return result;
 152            }
 153
 2136154            properties ??= s_propertyInfoCache.GetOrAdd(typeof(T), (type) =>
 2136155            {
 2140156                return type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 2136157            });
 158
 159            // Iterate through each property of the entity and set them as the correct type.
 101664160            foreach (var property in properties)
 161            {
 48696162                if (entity.TryGetValue(property.Name, out var propertyValue))
 163                {
 37632164                    if (typeActions.TryGetValue(property.PropertyType, out var propertyAction))
 165                    {
 37632166                        propertyAction(property, propertyValue, result);
 167                    }
 168                    else
 169                    {
 0170                        property.SetValue(result, propertyValue);
 171                    }
 172                }
 173            }
 174
 175            // Populate the ETag if present.
 2136176            if (entity.TryGetValue(TableConstants.PropertyNames.Etag, out var etag))
 177            {
 2136178                result.ETag = etag as string;
 179            }
 2136180            return result;
 181        }
 182
 2183        private static Dictionary<Type, Action<PropertyInfo, object, object>> typeActions = new Dictionary<Type, Action<
 2184        {
 2882185            {typeof(byte[]), (property, propertyValue, result) =>  property.SetValue(result, Convert.FromBase64String(pr
 2882186            {typeof(long), (property, propertyValue, result) =>  property.SetValue(result, long.Parse(propertyValue as s
 1490187            {typeof(long?), (property, propertyValue, result) =>  property.SetValue(result, long.Parse(propertyValue as 
 3626188            {typeof(double), (property, propertyValue, result) =>  property.SetValue(result, propertyValue)},
 1490189            {typeof(double?), (property, propertyValue, result) =>  property.SetValue(result, propertyValue)},
 1490190            {typeof(bool), (property, propertyValue, result) =>  property.SetValue(result, (bool)propertyValue)},
 1490191            {typeof(bool?), (property, propertyValue, result) =>  property.SetValue(result, (bool?)propertyValue)},
 2138192            {typeof(Guid), (property, propertyValue, result) =>  property.SetValue(result, Guid.Parse(propertyValue as s
 746193            {typeof(Guid?), (property, propertyValue, result) =>  property.SetValue(result, Guid.Parse(propertyValue as 
 2138194            {typeof(DateTimeOffset), (property, propertyValue, result) =>  property.SetValue(result, DateTimeOffset.Pars
 2882195            {typeof(DateTimeOffset?), (property, propertyValue, result) =>  property.SetValue(result, DateTimeOffset.Par
 2138196            {typeof(DateTime), (property, propertyValue, result) =>  property.SetValue(result, DateTime.Parse(propertyVa
 746197            {typeof(DateTime?), (property, propertyValue, result) =>  property.SetValue(result, DateTime.Parse(propertyV
 7154198            {typeof(string), (property, propertyValue, result) =>  property.SetValue(result, propertyValue as string)},
 2882199            {typeof(int), (property, propertyValue, result) =>  property.SetValue(result, (int)propertyValue)},
 1490200            {typeof(int?), (property, propertyValue, result) =>  property.SetValue(result, (int?)propertyValue)},
 2201        };
 202    }
 203}