/// <summary> /// Materializes the primitive data values in the given list of <paramref name="values"/>. /// </summary> /// <param name="actualType">Actual type for properties being materialized.</param> /// <param name="values">List of values to materialize.</param> /// <param name="ignoreMissingProperties"> /// Whether properties missing from the client types should be ignored. /// </param> /// <remarks> /// Values are materialized in-place withi each <see cref="ODataProperty"/> /// instance. /// </remarks> internal void MaterializeDataValues(ClientTypeAnnotation actualType, IEnumerable<ODataProperty> values, bool ignoreMissingProperties) { Debug.Assert(actualType != null, "actualType != null"); Debug.Assert(values != null, "values != null"); foreach (var odataProperty in values) { if (odataProperty.Value is ODataStreamReferenceValue) { continue; } string propertyName = odataProperty.Name; var property = actualType.GetProperty(propertyName, ignoreMissingProperties); // may throw if (property == null) { continue; } // we should throw if the property type on the client does not match with the property type in the server // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa if (ClientTypeUtil.TypeOrElementTypeIsEntity(property.PropertyType)) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidEntityType(property.EntityCollectionItemType ?? property.PropertyType)); } if (property.IsKnownType) { // Note: MaterializeDataValue materializes only properties of primitive types. Materialization specific // to complex types and collections is done later. this.MaterializePrimitiveDataValue(property.NullablePropertyType, odataProperty); } } }
/// <summary>Applies a data value to the specified <paramref name="instance"/>.</summary> /// <param name="type">Type to which a property value will be applied.</param> /// <param name="property">Property with value to apply.</param> /// <param name="instance">Instance on which value will be applied.</param> internal void ApplyDataValue(ClientTypeAnnotation type, ODataProperty property, object instance) { Debug.Assert(type != null, "type != null"); Debug.Assert(property != null, "property != null"); Debug.Assert(instance != null, "instance != null"); var prop = type.GetProperty(property.Name, this.MaterializerContext.IgnoreMissingProperties); if (prop == null) { return; } // Is it a collection? (note: property.Properties will be null if the Collection is empty (contains no elements)) Type enumTypeTmp = null; if (prop.IsPrimitiveOrEnumOrComplexCollection) { // Collections must not be null if (property.Value == null) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Collection_NullCollectionNotSupported(property.Name)); } // This happens if the payload contain just primitive value for a Collection property if (property.Value is string) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MixedTextWithComment); } if (property.Value is ODataComplexValue) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidCollectionItem(property.Name)); } // ODataLib already parsed the data and materialized all the primitive types. There is nothing more to materialize // anymore. Only complex type instance and collection instances need to be materialized, but those will be // materialized later on. // We need to materialize items before we change collectionInstance since this may throw. If we tried materializing // after the Collection is wiped or created we would leave the object in half constructed state. object collectionInstance = prop.GetValue(instance); if (collectionInstance == null) { collectionInstance = this.CollectionValueMaterializationPolicy.CreateCollectionPropertyInstance(property, prop.PropertyType); // allowAdd is false - we need to assign instance as the new property value prop.SetValue(instance, collectionInstance, property.Name, false /* allowAdd? */); } else { // Clear existing Collection prop.ClearBackingICollectionInstance(collectionInstance); } bool isElementNullable = prop.EdmProperty.Type.AsCollection().ElementType().IsNullable; this.CollectionValueMaterializationPolicy.ApplyCollectionDataValues( property, collectionInstance, prop.PrimitiveOrComplexCollectionItemType, prop.AddValueToBackingICollectionInstance, isElementNullable); } else if ((enumTypeTmp = Nullable.GetUnderlyingType(prop.NullablePropertyType) ?? prop.NullablePropertyType) != null && enumTypeTmp.IsEnum()) { ODataEnumValue enumValue = property.Value as ODataEnumValue; object tmpValue = EnumValueMaterializationPolicy.MaterializeODataEnumValue(enumTypeTmp, enumValue); // TODO: 1. use EnumValueMaterializationPolicy 2. handle nullable enum property prop.SetValue(instance, tmpValue, property.Name, false /* allowAdd? */); } else { object propertyValue = property.Value; ODataComplexValue complexValue = propertyValue as ODataComplexValue; if (propertyValue != null && complexValue != null) { if (!prop.EdmProperty.Type.IsComplex()) { // The error message is a bit odd, but it's compatible with V1. throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_ExpectingSimpleValue); } // Complex type. bool needToSet = false; ClientEdmModel edmModel = this.MaterializerContext.Model; ClientTypeAnnotation complexType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(prop.PropertyType)); object complexInstance = prop.GetValue(instance); // Validating property inheritance in complexvalue and instance if (prop.PropertyType.Name != property.Name) { complexType = this.MaterializerContext.ResolveTypeForMaterialization(prop.PropertyType, complexValue.TypeName); // recreate complexInstance with derived type complexInstance = null; } if (complexValue.Properties.Any() || complexInstance == null) { complexInstance = this.CreateNewInstance(complexType.EdmTypeReference, complexType.ElementType); needToSet = true; } this.MaterializeDataValues(complexType, complexValue.Properties, this.MaterializerContext.IgnoreMissingProperties); this.ApplyDataValues(complexType, complexValue.Properties, complexInstance); if (needToSet) { prop.SetValue(instance, complexInstance, property.Name, true /* allowAdd? */); } if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization) { // Set instance annotation for this complex instance this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(property, complexInstance); } } else { this.MaterializePrimitiveDataValue(prop.NullablePropertyType, property); prop.SetValue(instance, property.GetMaterializedValue(), property.Name, true /* allowAdd? */); } } if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization) { // Apply instance annotation for Property this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(property, type.ElementType, instance); } }