/// <summary> /// Traverses object graph, flattens and converts all nested (and not nested) properties to EntityProperties, stores them in the property dictionary. /// The keys are constructed by appending the names of the properties visited during pre-order depth first traversal from root to each end node property delimited by '_'. /// Allows complex objects to be stored in persistent storage systems or passed between web services in a generic way. /// </summary> /// <param name="root">The object to flatten and convert.</param> /// <param name="entityPropertyConverterOptions">A <see cref="EntityPropertyConverterOptions"/> object that specifies options for the entity property conversion.</param> /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param> /// <returns>The result containing <see cref="IDictionary{TKey,TValue}"/> of <see cref="EntityProperty"/> objects for all properties of the flattened root object.</returns> public static Dictionary <string, EntityProperty> Flatten(object root, EntityPropertyConverterOptions entityPropertyConverterOptions, OperationContext operationContext) { if (root == null) { return(null); } Dictionary <string, EntityProperty> propertyDictionary = new Dictionary <string, EntityProperty>(); HashSet <object> antecedents = new HashSet <object>(new ObjectReferenceEqualityComparer()); return(Flatten(propertyDictionary, root, string.Empty, antecedents, entityPropertyConverterOptions, operationContext) ? propertyDictionary : null); }
/// <summary> /// Reconstructs the complete object graph of type T using the flattened entity property dictionary and returns reconstructed object. /// The property dictionary may contain only basic properties, only nested properties or a mix of both types. /// </summary> /// <typeparam name="T">The type of the object to populate</typeparam> /// <param name="flattenedEntityProperties">The flattened entity property dictionary.</param> /// <param name="entityPropertyConverterOptions">A <see cref="EntityPropertyConverterOptions"/> object that specifies options for the entity property conversion.</param> /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param> /// <returns>The result containing the reconstructed object with its full object hierarchy.</returns> public static T ConvertBack <T>( IDictionary <string, EntityProperty> flattenedEntityProperties, EntityPropertyConverterOptions entityPropertyConverterOptions, OperationContext operationContext) { if (flattenedEntityProperties == null) { return(default(T)); } #if WINDOWS_DESKTOP || WINDOWS_RT || NETCORE T root = (T)Activator.CreateInstance(typeof(T)); #else T root = (T)FormatterServices.GetUninitializedObject(typeof(T)); #endif return(flattenedEntityProperties.Aggregate(root, (current, kvp) => (T)SetProperty(current, kvp.Key, kvp.Value.PropertyAsObject, entityPropertyConverterOptions, operationContext))); }
/// <summary>Sets the property given with the property path on the passed in object.</summary> /// <param name="root">The root object.</param> /// <param name="propertyPath">The full property path formed by the name of properties from root object to the target property(included), appended by '.'.</param> /// <param name="propertyValue">The property value.</param> /// <param name="entityPropertyConverterOptions">A <see cref="EntityPropertyConverterOptions"/> object that specifies options for the entity property conversion.</param> /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param> /// <returns>The updated <see cref="object"/>.</returns> private static object SetProperty( object root, string propertyPath, object propertyValue, EntityPropertyConverterOptions entityPropertyConverterOptions, OperationContext operationContext) { if (root == null) { throw new ArgumentNullException("root"); } if (propertyPath == null) { throw new ArgumentNullException("propertyPath"); } try { string propertyNameDelimiter = entityPropertyConverterOptions != null ? entityPropertyConverterOptions.PropertyNameDelimiter : DefaultPropertyNameDelimiter; Stack <Tuple <object, object, PropertyInfo> > valueTypePropertyHierarchy = new Stack <Tuple <object, object, PropertyInfo> >(); string[] properties = propertyPath.Split(new[] { propertyNameDelimiter }, StringSplitOptions.RemoveEmptyEntries); object parentProperty = root; bool valueTypeDetected = false; for (int i = 0; i < properties.Length - 1; i++) { #if WINDOWS_RT || NETCORE PropertyInfo propertyToGet = parentProperty.GetType().GetRuntimeProperty(properties[i]); #else PropertyInfo propertyToGet = parentProperty.GetType().GetProperty(properties[i]); #endif object temp = propertyToGet.GetValue(parentProperty, null); Type type = propertyToGet.PropertyType; if (temp == null) { #if WINDOWS_DESKTOP || WINDOWS_RT || NETCORE temp = Activator.CreateInstance(type); #else temp = FormatterServices.GetUninitializedObject(type); #endif propertyToGet.SetValue(parentProperty, ChangeType(temp, propertyToGet.PropertyType), index: null); } #if WINDOWS_RT || NETCORE if (valueTypeDetected || type.GetTypeInfo().IsValueType) #else if (valueTypeDetected || type.IsValueType) #endif { valueTypeDetected = true; valueTypePropertyHierarchy.Push(new Tuple <object, object, PropertyInfo>(temp, parentProperty, propertyToGet)); } parentProperty = temp; } #if WINDOWS_RT || NETCORE PropertyInfo propertyToSet = parentProperty.GetType().GetRuntimeProperty(properties.Last()); #else PropertyInfo propertyToSet = parentProperty.GetType().GetProperty(properties.Last()); #endif propertyToSet.SetValue(parentProperty, ChangeType(propertyValue, propertyToSet.PropertyType), index: null); object termValue = parentProperty; while (valueTypePropertyHierarchy.Count != 0) { Tuple <object, object, PropertyInfo> propertyTuple = valueTypePropertyHierarchy.Pop(); propertyTuple.Item3.SetValue(propertyTuple.Item2, ChangeType(termValue, propertyTuple.Item3.PropertyType), index: null); termValue = propertyTuple.Item2; } return(root); } catch (Exception ex) { Logger.LogError(operationContext, SR.TraceSetPropertyError, propertyPath, propertyValue, ex.Message); throw; } }
/// <summary> /// Traverses object graph, flattens and converts all nested (and not nested) properties to EntityProperties, stores them in the property dictionary. /// The keys are constructed by appending the names of the properties visited during pre-order depth first traversal from root to each end node property delimited by '.'. /// Allows complex objects to be stored in persistent storage systems or passed between web services in a generic way. /// </summary> /// <param name="propertyDictionary">The property dictionary.</param> /// <param name="current">The current object.</param> /// <param name="objectPath">The object path.</param> /// <param name="antecedents">The antecedents of current object, used to detect circular references in object graph.</param> /// <param name="entityPropertyConverterOptions">A <see cref="EntityPropertyConverterOptions"/> object that specifies options for the entity property conversion.</param> /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param> /// <returns>The <see cref="bool"/> to indicate success of conversion to flattened EntityPropertyDictionary.</returns> private static bool Flatten( Dictionary <string, EntityProperty> propertyDictionary, object current, string objectPath, HashSet <object> antecedents, EntityPropertyConverterOptions entityPropertyConverterOptions, OperationContext operationContext) { if (current == null) { return(true); } Type type = current.GetType(); EntityProperty entityProperty = CreateEntityPropertyWithType(current, type); if (entityProperty != null) { propertyDictionary.Add(objectPath, entityProperty); return(true); } #if WINDOWS_RT IEnumerable <PropertyInfo> propertyInfos = type.GetRuntimeProperties(); #elif NETCORE IEnumerable <PropertyInfo> propertyInfos = type.GetTypeInfo().GetAllProperties(); #else IEnumerable <PropertyInfo> propertyInfos = type.GetProperties(); #endif if (!propertyInfos.Any()) { throw new SerializationException(string.Format(CultureInfo.InvariantCulture, SR.UnsupportedPropertyTypeForEntityPropertyConversion, type, objectPath)); } bool isAntecedent = false; #if WINDOWS_RT || NETCORE if (!type.GetTypeInfo().IsValueType) #else if (!type.IsValueType) #endif { if (antecedents.Contains(current)) { throw new SerializationException(string.Format(CultureInfo.InvariantCulture, SR.RecursiveReferencedObject, objectPath, type)); } antecedents.Add(current); isAntecedent = true; } string propertyNameDelimiter = entityPropertyConverterOptions != null ? entityPropertyConverterOptions.PropertyNameDelimiter : DefaultPropertyNameDelimiter; bool success = propertyInfos .Where(propertyInfo => !ShouldSkip(propertyInfo, objectPath, operationContext)) .All(propertyInfo => { if (propertyInfo.Name.Contains(propertyNameDelimiter)) { throw new SerializationException( string.Format(CultureInfo.InvariantCulture, SR.PropertyDelimiterExistsInPropertyName, propertyNameDelimiter, propertyInfo.Name, objectPath)); } return(Flatten( propertyDictionary, propertyInfo.GetValue(current, index: null), string.IsNullOrWhiteSpace(objectPath) ? propertyInfo.Name : objectPath + propertyNameDelimiter + propertyInfo.Name, antecedents, entityPropertyConverterOptions, operationContext)); }); if (isAntecedent) { antecedents.Remove(current); } return(success); }
/// <summary> /// Flattens the entity and creates a <see cref="IDictionary{TKey,TValue}"/> of <see cref="EntityProperty"/> objects for all properties of the specified entity object. /// </summary> /// <param name="entity">The entity object to serialize.</param> /// <param name="entityPropertyConverterOptions">A <see cref="EntityPropertyConverterOptions"/> object that specifies options for the entity property conversion.</param> /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param> /// <returns>An <see cref="IDictionary{TKey,TValue}"/> of <see cref="EntityProperty"/> objects for all the properties of the specified entity object.</returns> /// <remarks>The entity type can be a simple object with a flat structure or a complex object with complex properties and multiple levels of object hierarchy. /// Generic ConvertBack method can recompose the original entity using the return value of this method.</remarks> public static IDictionary <string, EntityProperty> Flatten(object entity, EntityPropertyConverterOptions entityPropertyConverterOptions, OperationContext operationContext) { CommonUtility.AssertNotNull("entity", entity); return(EntityPropertyConverter.Flatten(entity, entityPropertyConverterOptions, operationContext)); }
/// <summary> /// Returns a custom entity instance which is recomposed using the specified <see cref="IDictionary{TKey,TValue}"/> of property names to <see cref="EntityProperty"/> data typed values. /// </summary> /// <typeparam name="TResult">The type of the recomposed object. This can be a simple object with a flat structure or a complex object with complex properties and multiple levels of object hierarchy.</typeparam> /// <param name="properties">An <see cref="IDictionary{TKey,TValue}"/> object that maps string property names to <see cref="EntityProperty"/> data values to deserialize and store in this table entity instance.</param> /// <param name="entityPropertyConverterOptions">A <see cref="EntityPropertyConverterOptions"/> object that specifies options for the entity property conversion.</param> /// <param name="operationContext">An <see cref="OperationContext"/> object that represents the context for the current operation.</param> public static TResult ConvertBack <TResult>(IDictionary <string, EntityProperty> properties, EntityPropertyConverterOptions entityPropertyConverterOptions, OperationContext operationContext) { return(EntityPropertyConverter.ConvertBack <TResult>(properties, entityPropertyConverterOptions, operationContext)); }