/// <summary> /// Tries to get the value of a property and corresponding BindingPropertyInfo or ClientPropertyAnnotation if the property exists /// </summary> /// <param name="source">Source object whose property needs to be read</param> /// <param name="sourceProperty">Name of the source object property</param> /// <param name="model">The client model.</param> /// <param name="bindingPropertyInfo">BindingPropertyInfo corresponding to <paramref name="sourceProperty"/></param> /// <param name="clientProperty">Instance of ClientProperty corresponding to <paramref name="sourceProperty"/></param> /// <param name="propertyValue">Value of the property</param> /// <returns>true if the property exists and the value was read; otherwise false.</returns> internal static bool TryGetPropertyValue(object source, string sourceProperty, ClientEdmModel model, out BindingPropertyInfo bindingPropertyInfo, out ClientPropertyAnnotation clientProperty, out object propertyValue) { Type sourceType = source.GetType(); bindingPropertyInfo = BindingEntityInfo.GetObservableProperties(sourceType, model) .SingleOrDefault(x => x.PropertyInfo.PropertyName == sourceProperty); bool propertyFound = bindingPropertyInfo != null; // bindingPropertyInfo is null for primitive properties. if (!propertyFound) { clientProperty = BindingEntityInfo.GetClientType(sourceType, model) .GetProperty(sourceProperty, true); propertyFound = clientProperty != null; if (!propertyFound) { propertyValue = null; } else { propertyValue = clientProperty.GetValue(source); } } else { clientProperty = null; propertyValue = bindingPropertyInfo.PropertyInfo.GetValue(source); } return propertyFound; }
/// <summary> /// Gets or creates a collection property on the specified <paramref name="instance"/>. /// </summary> /// <param name="instance">Instance on which to get/create the collection.</param> /// <param name="property">Collection property on the <paramref name="instance"/>.</param> /// <param name="forLoadProperty">Is this collection being created for LoadProperty scenario.</param> /// <returns> /// The collection corresponding to the specified <paramref name="property"/>; /// never null. /// </returns> private object GetOrCreateCollectionProperty(object instance, ClientPropertyAnnotation property, bool forLoadProperty) { Debug.Assert(instance != null, "instance != null"); Debug.Assert(property != null, "property != null"); Debug.Assert(property.IsEntityCollection, "property.IsEntityCollection has to be true - otherwise property isn't a collection"); // NOTE: in V1, we would have instantiated nested objects before setting them. object result; result = property.GetValue(instance); if (result == null) { Type collectionType = property.PropertyType; // For backward compatiblity we need to have different strategy of collection creation b/w // LoadProperty scenario versus regular collection creation scenario. if (forLoadProperty) { if (BindingEntityInfo.IsDataServiceCollection(collectionType, this.MaterializerContext.Model)) { Debug.Assert(WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType) != null, "DataServiceCollection<> must be available here."); // new DataServiceCollection<property.EntityCollectionItemType>(null, TrackingMode.None) result = Activator.CreateInstance( WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType), null, TrackingMode.None); } else { // Try List<> first because that's what we did in V1/V2, but use the actual property type if it doesn't support List<> Type listCollectionType = typeof(List<>).MakeGenericType(property.EntityCollectionItemType); if (collectionType.IsAssignableFrom(listCollectionType)) { collectionType = listCollectionType; } result = Activator.CreateInstance(collectionType); } } else { if (DSClient.PlatformHelper.IsInterface(collectionType)) { collectionType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(property.EntityCollectionItemType); } result = this.CreateNewInstance(property.EdmProperty.Type, collectionType); } // Update the property value on the instance. property.SetValue(instance, result, property.PropertyName, false /* add */); } Debug.Assert(result != null, "result != null -- otherwise GetOrCreateCollectionProperty didn't fall back to creation"); return result; }
/// <summary> /// Builds an edm property value from the given annotation. /// </summary> /// <param name="propertyAnnotation">The property annotation.</param> /// <returns>The property value</returns> private IEdmPropertyValue BuildEdmPropertyValue(ClientPropertyAnnotation propertyAnnotation) { var propertyValue = propertyAnnotation.GetValue(this.structuredValue); var edmValue = this.ConvertToEdmValue(propertyValue, propertyAnnotation.EdmProperty.Type); return new EdmPropertyValue(propertyAnnotation.EdmProperty.Name, edmValue); }
/// <summary> /// Populates the collection property on the entry's resolved object with the given items enumerator. /// </summary> /// <param name="entry">Entry with collection to be modified.</param> /// <param name="property">Collection property on the entry.</param> /// <param name="items">Values to apply onto the collection.</param> /// <param name="nextLink">Next link for collection continuation.</param> /// <param name="continuationPlan">Projection plan for collection continuation.</param> /// <returns>Collection instance that was populated.</returns> private object PopulateCollectionProperty( MaterializerEntry entry, ClientPropertyAnnotation property, IEnumerable<object> items, Uri nextLink, ProjectionPlan continuationPlan) { Debug.Assert(entry.Entry != null || entry.ForLoadProperty, "ODataEntry should be non-null except for LoadProperty"); Debug.Assert(property != null, "property != null"); Debug.Assert(items != null, "items != null"); object collection = null; ClientEdmModel edmModel = this.MaterializerContext.Model; ClientTypeAnnotation collectionType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(property.EntityCollectionItemType)); if (entry.ShouldUpdateFromPayload) { collection = this.GetOrCreateCollectionProperty(entry.ResolvedObject, property, entry.ForLoadProperty); foreach (object item in items) { // Validate that item can be inserted into collection. ValidateCollectionElementTypeIsItemType(item.GetType(), collectionType.ElementType); property.SetValue(collection, item, property.PropertyName, true /* allowAdd? */); this.EntityTrackingAdapter.MaterializationLog.AddedLink(entry, property.PropertyName, item); } this.FoundNextLinkForCollection(collection as IEnumerable, nextLink, continuationPlan); } else { Debug.Assert(!entry.ForLoadProperty, "LoadProperty should always have ShouldUpateForPayload set to true."); foreach (object item in items) { // Validate that item can be inserted into collection. ValidateCollectionElementTypeIsItemType(item.GetType(), collectionType.ElementType); } this.FoundNextLinkForUnmodifiedCollection(property.GetValue(entry.ResolvedObject) as IEnumerable); } return collection; }
private MaterializeAtom ReadPropertyFromAtom(ClientPropertyAnnotation property) { DataServiceContext context = (DataServiceContext)this.Source; bool merging = context.ApplyingChanges; try { context.ApplyingChanges = true; // store the results so that they can be there in the response body. Type elementType = property.IsEntityCollection ? property.EntityCollectionItemType : property.NullablePropertyType; IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); DataServiceQueryContinuation continuation = null; // elementType.ElementType has Nullable stripped away, use nestedType for materializer using (MaterializeAtom materializer = this.GetMaterializer(this.plan)) { Debug.Assert(materializer != null, "materializer != null -- otherwise GetMaterializer() returned null rather than empty"); #if ASTORIA_OPEN_OBJECT object openProperties = null; #endif // when SetLink to null, we cannot get materializer because have no-content response. if (materializer.IsNoContentResponse() && property.GetValue(entity) != null && context.MergeOption != MergeOption.AppendOnly && context.MergeOption != MergeOption.NoTracking) { property.SetValue(this.entity, null, propertyName, false); } else { foreach (object child in materializer) { if (property.IsEntityCollection) { results.Add(child); } else if (property.IsPrimitiveOrEnumOrComplexCollection) { Debug.Assert(property.PropertyType.IsAssignableFrom(child.GetType()), "Created instance for storing collection items has to be compatible with the actual one."); // Collection materialization rules requires to clear the collection if not null or set the property first and then add the collection items object collectionInstance = property.GetValue(this.entity); if (collectionInstance == null) { // type of child has been resolved as per rules for collections so it is the correct type to instantiate collectionInstance = Activator.CreateInstance(child.GetType()); // allowAdd is false - we need to assign instance as the new property value property.SetValue(this.entity, collectionInstance, this.propertyName, false /* allowAdd? */); } else { // Clear existing collection property.ClearBackingICollectionInstance(collectionInstance); } foreach (var collectionItem in (IEnumerable)child) { Debug.Assert(property.PrimitiveOrComplexCollectionItemType.IsAssignableFrom(collectionItem.GetType()), "Type of materialized collection items have to be compatible with the type of collection items in the actual collection property."); property.AddValueToBackingICollectionInstance(collectionInstance, collectionItem); } results.Add(collectionInstance); } else { #if ASTORIA_OPEN_OBJECT property.SetValue(this.entity, child, this.propertyName, ref openProperties, false); #else // it is either primitive type, complex type or 1..1 navigation property so we just allow setting the value but not adding. property.SetValue(this.entity, child, this.propertyName, false); results.Add(child); #endif } } } continuation = materializer.GetContinuation(null); } return MaterializeAtom.CreateWrapper(context, results, continuation); } finally { context.ApplyingChanges = merging; } }