/// <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 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); }
private void ApplyInnerProperty(ODataResource innerResource, ComplexTypeWithChildComplexType parentInstance, TestMaterializerContext context = null) { context = context ?? new TestMaterializerContext(); var resource = new ODataResource() { TypeName = "ComplexTypeWithChildComplexType", Properties = new ODataProperty[0] }; var clientEdmModel = new ClientEdmModel(ODataProtocolVersion.V4); var materializerEntry = MaterializerEntry.CreateEntry(resource, ODataFormat.Json, false, clientEdmModel); materializerEntry.ResolvedObject = parentInstance; ODataNestedResourceInfo innerComplexP = new ODataNestedResourceInfo() { Name = "InnerComplexProperty", IsCollection = false }; MaterializerEntry innerMaterializerEntry; if (innerResource != null) { innerMaterializerEntry = MaterializerEntry.CreateEntry(innerResource, ODataFormat.Json, true, clientEdmModel); } else { innerMaterializerEntry = MaterializerEntry.CreateEmpty(); } MaterializerNavigationLink.CreateLink(innerComplexP, innerMaterializerEntry); materializerEntry.AddNestedResourceInfo(innerComplexP); var policy = this.CreateEntryMaterializationPolicy(context); policy.EntityTrackingAdapter.TargetInstance = parentInstance; policy.Materialize(materializerEntry, typeof(ComplexTypeWithChildComplexType), true); }
/// <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; if (!entry.IsTracking) { int? streamDescriptorsCount = entry.EntityDescriptor.StreamDescriptors?.Count; IEdmEntityType entityType = this.EntityTrackingAdapter.Model.FindDeclaredType(entry.Entry.TypeName) as IEdmEntityType; if (streamDescriptorsCount > 0 || entityType?.HasStream == true) { entity.EntityDescriptor = entry.EntityDescriptor; } } } this.MaterializerContext.ResponsePipeline.FireEndEntryEvents(entry); }
public void MaterializeEntityShouldWork() { var odataEntry = new OData.ODataResource() { Id = new Uri("http://www.odata.org/service.svc/entitySet(1)") }; odataEntry.Properties = new OData.ODataProperty[] { new OData.ODataProperty { Name = "keyProp", Value = 1 }, new OData.ODataProperty { Name = "colorProp", Value = new OData.ODataEnumValue("blue") }, new OData.ODataProperty { Name = "primitiveCollection", Value = new OData.ODataCollectionValue { TypeName = "Edm.Int32", Items = new List <object> { 1, 2, 3 } } }, new OData.ODataProperty { Name = "enumCollection", Value = new OData.ODataCollectionValue { TypeName = "color", Items = new List <OData.ODataEnumValue> { new OData.ODataEnumValue("white"), new OData.ODataEnumValue("blue") } } } }; var complexP = new OData.ODataNestedResourceInfo() { Name = "complexProp", IsCollection = false }; var complexResource = new OData.ODataResource { Properties = new OData.ODataProperty[] { new OData.ODataProperty { Name = "age", Value = 11 }, new OData.ODataProperty { Name = "name", Value = "June" } } }; var complexColP = new OData.ODataNestedResourceInfo { Name = "complexCollection", IsCollection = true }; var complexColResourceSet = new OData.ODataResourceSet(); var items = new List <OData.ODataResource> { new OData.ODataResource { Properties = new OData.ODataProperty[] { new OData.ODataProperty { Name = "name", Value = "Aug" }, new OData.ODataProperty { Name = "age", Value = 8 }, new OData.ODataProperty { Name = "color", Value = new OData.ODataEnumValue("white") } } }, new OData.ODataResource { Properties = new OData.ODataProperty[] { new OData.ODataProperty { Name = "name", Value = "Sep" }, new OData.ODataProperty { Name = "age", Value = 9 }, new OData.ODataProperty { Name = "color", Value = new OData.ODataEnumValue("blue") } } } }; var clientEdmModel = new ClientEdmModel(ODataProtocolVersion.V4); var context = new DataServiceContext(); var materializerEntry = MaterializerEntry.CreateEntry(odataEntry, OData.ODataFormat.Json, true, clientEdmModel); MaterializerNavigationLink.CreateLink(complexP, MaterializerEntry.CreateEntry(complexResource, OData.ODataFormat.Json, true, clientEdmModel)); MaterializerFeed.CreateFeed(complexColResourceSet, items); MaterializerNavigationLink.CreateLink(complexColP, complexColResourceSet); var materializerContext = new TestMaterializerContext() { Model = clientEdmModel, Context = context }; var adapter = new EntityTrackingAdapter(new TestEntityTracker(), MergeOption.OverwriteChanges, clientEdmModel, context); QueryComponents components = new QueryComponents(new Uri("http://foo.com/Service"), new Version(4, 0), typeof(EntityType), null, new Dictionary <Expression, Expression>()); var entriesMaterializer = new ODataEntriesEntityMaterializer(new OData.ODataResource[] { odataEntry }, materializerContext, adapter, components, typeof(EntityType), null, OData.ODataFormat.Json); var customersRead = new List <EntityType>(); while (entriesMaterializer.Read()) { customersRead.Add(entriesMaterializer.CurrentValue as EntityType); } customersRead.Should().HaveCount(1); customersRead[0].KeyProp.Should().Be(1); customersRead[0].ComplexProp.Should().Equals(new ComplexType { Age = 11, Name = "June" }); customersRead[0].ColorProp.Should().Equals(Color.Blue); customersRead[0].PrimitiveCollection.Should().Equals(new List <int> { 1, 2, 3 }); ComplexType complex1 = new ComplexType { Name = "Aug", Age = 8, Color = Color.White }; ComplexType complex2 = new ComplexType { Name = "Sep", Age = 9, Color = Color.Blue }; customersRead[0].ComplexCollection.Should().Equals(new List <ComplexType> { complex1, complex2 }); customersRead[0].EnumCollection.Should().Equals(new List <Color> { Color.White, Color.Blue }); }