/// <summary> /// Reads a navigation link. /// </summary> /// <returns>A navigation link.</returns> private ODataNestedResourceInfo ReadNestedResourceInfo() { Debug.Assert(this.reader.State == ODataReaderState.NestedResourceInfoStart, "this.reader.State == ODataReaderState.NestedResourceInfoStart"); ODataNestedResourceInfo link = (ODataNestedResourceInfo)this.reader.Item; MaterializerEntry entry; ODataResourceSet feed; if (this.TryReadFeedOrEntry(false, out feed, out entry)) { if (feed != null) { MaterializerNavigationLink.CreateLink(link, feed); } else { Debug.Assert(entry != null, "entry != null"); MaterializerNavigationLink.CreateLink(link, entry); } this.ReadAndExpectState(ODataReaderState.NestedResourceInfoEnd); } this.ExpectState(ODataReaderState.NestedResourceInfoEnd); return(link); }
/// <summary> /// Creates the materializer link with a feed. /// </summary> /// <param name="link">The link.</param> /// <param name="feed">The feed.</param> /// <returns>The materializer link.</returns> public static MaterializerNavigationLink CreateLink(ODataNavigationLink link, ODataFeed feed) { Debug.Assert(link.GetAnnotation<MaterializerNavigationLink>() == null, "there should be no MaterializerNavigationLink annotation on the feed link yet"); MaterializerNavigationLink materializedNavigationLink = new MaterializerNavigationLink(link, feed); link.SetAnnotation<MaterializerNavigationLink>(materializedNavigationLink); return materializedNavigationLink; }
/// <summary> /// Creates the materializer link with a resource set. /// </summary> /// <param name="link">The link.</param> /// <param name="resourceSet">The resource set.</param> /// <returns>The materializer link.</returns> public static MaterializerNavigationLink CreateLink(ODataNestedResourceInfo link, ODataResourceSet resourceSet) { Debug.Assert(link.GetAnnotation <MaterializerNavigationLink>() == null, "there should be no MaterializerNestedResourceInfo annotation on the feed link yet"); MaterializerNavigationLink materializedNestedResourceInfo = new MaterializerNavigationLink(link, resourceSet); link.SetAnnotation <MaterializerNavigationLink>(materializedNestedResourceInfo); return(materializedNestedResourceInfo); }
/// <summary> /// Creates the materializer link with a feed. /// </summary> /// <param name="link">The link.</param> /// <param name="feed">The feed.</param> /// <returns>The materializer link.</returns> public static MaterializerNavigationLink CreateLink(ODataNavigationLink link, ODataFeed feed) { Debug.Assert(link.GetAnnotation <MaterializerNavigationLink>() == null, "there should be no MaterializerNavigationLink annotation on the feed link yet"); MaterializerNavigationLink materializedNavigationLink = new MaterializerNavigationLink(link, feed); link.SetAnnotation <MaterializerNavigationLink>(materializedNavigationLink); return(materializedNavigationLink); }
/// <summary>Materializes the specified <paramref name="entry"/> as dynamic property.</summary> /// <param name="entry">Entry with object to materialize.</param> /// <param name="link">Nested resource link as parsed.</param> private void MaterializeDynamicProperty(MaterializerEntry entry, ODataNestedResourceInfo link) { Debug.Assert(entry != null, "entry != null"); Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise not resolved/created!"); Debug.Assert(link != null, "link != null"); ClientEdmModel model = this.MaterializerContext.Model; IDictionary <string, object> containerProperty; // Stop if owning type is not an open type // Or container property is not found // Or key with matching name already exists in the dictionary if (!ClientTypeUtil.IsInstanceOfOpenType(entry.ResolvedObject, model) || !ClientTypeUtil.TryGetContainerProperty(entry.ResolvedObject, out containerProperty) || containerProperty.ContainsKey(link.Name)) { return; } MaterializerNavigationLink linkState = MaterializerNavigationLink.GetLink(link); if (linkState == null || (linkState.Entry == null && linkState.Feed == null)) { return; } // NOTE: ODL (and OData WebApi) support navigational property on complex types // That support has not yet been implemented in OData client // An entity or entity collection as a dynamic property currently doesn't work as expected // due to the absence of a navigational property definition in the metadata // to express the relationship between that entity and the parent entity - unless they're the same type! // Only materialize a nested resource if its a complex or complex collection if (linkState.Feed != null) { string collectionTypeName = linkState.Feed.TypeName; // TypeName represents a collection e.g. Collection(NS.Type) string collectionItemTypeName = CommonUtil.GetCollectionItemTypeName(collectionTypeName, false); // Highly unlikely, but the method return null if the typeName argument does not meet certain expectations if (collectionItemTypeName == null) { return; } Type collectionItemType = ResolveClientTypeForDynamicProperty(collectionItemTypeName, entry.ResolvedObject); if (collectionItemType != null && ClientTypeUtil.TypeIsComplex(collectionItemType, model)) { Type collectionType = typeof(System.Collections.ObjectModel.Collection <>).MakeGenericType(new Type[] { collectionItemType }); IList collection = (IList)Util.ActivatorCreateInstance(collectionType); IEnumerable <ODataResource> feedEntries = MaterializerFeed.GetFeed(linkState.Feed).Entries; foreach (ODataResource feedEntry in feedEntries) { MaterializerEntry linkEntry = MaterializerEntry.GetEntry(feedEntry); this.Materialize(linkEntry, collectionItemType, false /*includeLinks*/); collection.Add(linkEntry.ResolvedObject); } containerProperty.Add(link.Name, collection); } } else { MaterializerEntry linkEntry = linkState.Entry; Type itemType = ResolveClientTypeForDynamicProperty(linkEntry.Entry.TypeName, entry.ResolvedObject); if (itemType != null && ClientTypeUtil.TypeIsComplex(itemType, model)) { this.Materialize(linkEntry, itemType, false /*includeLinks*/); containerProperty.Add(link.Name, linkEntry.ResolvedObject); } } }
/// <summary>Materializes the specified <paramref name="entry"/>.</summary> /// <param name="entry">Entry with object to materialize.</param> /// <param name="includeLinks">Whether links that are expanded for navigation property should be materialized.</param> /// <remarks>This is a payload-driven materialization process.</remarks> private void MaterializeResolvedEntry(MaterializerEntry entry, bool includeLinks) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise not resolved/created!"); ClientTypeAnnotation actualType = entry.ActualType; // While materializing entities, we need to make sure the payload that came in the wire is also an entity. // Otherwise we need to fail. // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa if (!actualType.IsStructuredType) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(actualType.ElementTypeName)); } // Note that even if ShouldUpdateFromPayload is false, we will still be creating // nested instances (but not their links), so they show up in the data context // entries. This keeps this code compatible with V1 behavior. this.MaterializeDataValues(actualType, entry.Properties, this.MaterializerContext.UndeclaredPropertyBehavior); if (entry.NestedResourceInfos?.Any() == true) { foreach (ODataNestedResourceInfo link in entry.NestedResourceInfos) { var prop = actualType.GetProperty(link.Name, UndeclaredPropertyBehavior.Support); if (prop != null) { ValidatePropertyMatch(prop, link, this.MaterializerContext.Model, true /*performEntityCheck*/); } } foreach (ODataNestedResourceInfo link in entry.NestedResourceInfos) { MaterializerNavigationLink linkState = MaterializerNavigationLink.GetLink(link); if (linkState == null) { continue; } var prop = actualType.GetProperty(link.Name, this.MaterializerContext.UndeclaredPropertyBehavior); if (prop == null) { if (entry.ShouldUpdateFromPayload) { this.MaterializeDynamicProperty(entry, link); } continue; } // includeLinks is for Navigation property, so we should handle complex property when includeLinks equals false; if (!includeLinks && (prop.IsEntityCollection || prop.EntityCollectionItemType != null)) { continue; } if (linkState.Feed != null) { this.ApplyFeedToCollection(entry, prop, linkState.Feed, includeLinks); } else if (linkState.Entry != null) { MaterializerEntry linkEntry = linkState.Entry; if (linkEntry.Entry != null) { this.Materialize(linkEntry, prop.PropertyType, includeLinks); } if (entry.ShouldUpdateFromPayload) { prop.SetValue(entry.ResolvedObject, linkEntry.ResolvedObject, link.Name, true /* allowAdd? */); if (!this.MaterializerContext.Context.DisableInstanceAnnotationMaterialization && linkEntry.ShouldUpdateFromPayload) { // Apply instance annotation for navigation property this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations( prop.PropertyName, linkEntry.Entry, entry.ActualType.ElementType, entry.ResolvedObject); // Apply instance annotation for entity of the navigation property this.InstanceAnnotationMaterializationPolicy.SetInstanceAnnotations(linkEntry.Entry, linkEntry.ResolvedObject); } this.EntityTrackingAdapter.MaterializationLog.SetLink(entry, prop.PropertyName, linkEntry.ResolvedObject); } } } } foreach (var e in entry.Properties) { if (e.Value is ODataStreamReferenceValue) { continue; } var prop = actualType.GetProperty(e.Name, this.MaterializerContext.UndeclaredPropertyBehavior); if (prop == null) { if (entry.ShouldUpdateFromPayload) { this.MaterializeDynamicProperty(e, entry.ResolvedObject); } continue; } if (entry.ShouldUpdateFromPayload) { ValidatePropertyMatch(prop, e, this.MaterializerContext.Model, true /*performEntityCheck*/); this.ApplyDataValue(actualType, e, entry.ResolvedObject); } } // apply link values if present ApplyLinkProperties(actualType, entry); Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise we didn't do any useful work"); BaseEntityType entity = entry.ResolvedObject as BaseEntityType; if (entity != null) { entity.Context = this.EntityTrackingAdapter.Context; } this.MaterializerContext.ResponsePipeline.FireEndEntryEvents(entry); }
/// <summary>Materializes the specified <paramref name="entry"/>.</summary> /// <param name="entry">Entry with object to materialize.</param> /// <param name="includeLinks">Whether links that are expanded should be materialized.</param> /// <remarks>This is a payload-driven materialization process.</remarks> private void MaterializeResolvedEntry(MaterializerEntry entry, bool includeLinks) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise not resolved/created!"); ClientTypeAnnotation actualType = entry.ActualType; // While materializing entities, we need to make sure the payload that came in the wire is also an entity. // Otherwise we need to fail. if (!actualType.IsEntityType) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(actualType.ElementTypeName)); } // Note that even if ShouldUpdateFromPayload is false, we will still be creating // nested instances (but not their links), so they show up in the data context // entries. This keeps this code compatible with V1 behavior. this.MaterializeDataValues(actualType, entry.Properties, this.MaterializerContext.IgnoreMissingProperties); if (entry.NavigationLinks != null) { foreach (ODataNavigationLink link in entry.NavigationLinks) { var prop = actualType.GetProperty(link.Name, true); if (prop != null) { ValidatePropertyMatch(prop, link, this.MaterializerContext.Model, true /*performEntityCheck*/); } } if (includeLinks) { foreach (ODataNavigationLink link in entry.NavigationLinks) { MaterializerNavigationLink linkState = MaterializerNavigationLink.GetLink(link); // Ignore... if (linkState == null) { continue; } var prop = actualType.GetProperty(link.Name, this.MaterializerContext.IgnoreMissingProperties); if (prop == null) { continue; } if (linkState.Feed != null) { this.ApplyFeedToCollection(entry, prop, linkState.Feed, includeLinks); } else if (linkState.Entry != null) { MaterializerEntry linkEntry = linkState.Entry; if (linkEntry.Entry != null) { Debug.Assert(includeLinks, "includeLinks -- otherwise we shouldn't be materializing this entry"); this.Materialize(linkEntry, prop.PropertyType, includeLinks); } if (entry.ShouldUpdateFromPayload) { prop.SetValue(entry.ResolvedObject, linkEntry.ResolvedObject, link.Name, true /* allowAdd? */); this.EntityTrackingAdapter.MaterializationLog.SetLink(entry, prop.PropertyName, linkEntry.ResolvedObject); } } } } } foreach (var e in entry.Properties) { if (e.Value is ODataStreamReferenceValue) { continue; } var prop = actualType.GetProperty(e.Name, this.MaterializerContext.IgnoreMissingProperties); if (prop == null) { continue; } if (entry.ShouldUpdateFromPayload) { ValidatePropertyMatch(prop, e, this.MaterializerContext.Model, true /*performEntityCheck*/); this.ApplyDataValue(actualType, e, entry.ResolvedObject); } } // apply link values if present ApplyLinkProperties(actualType, entry); Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise we didn't do any useful work"); BaseEntityType entity = entry.ResolvedObject as BaseEntityType; if (entity != null) { entity.Context = this.EntityTrackingAdapter.Context; } this.MaterializerContext.ResponsePipeline.FireEndEntryEvents(entry); }