/// <summary> /// Creates the materializer link with an entry. /// </summary> /// <param name="link">The link.</param> /// <param name="entry">The entry.</param> /// <returns>The materializer link.</returns> public static MaterializerNavigationLink CreateLink(ODataNavigationLink link, MaterializerEntry entry) { Debug.Assert(link.GetAnnotation<MaterializerNavigationLink>() == null, "there should be no MaterializerNavigationLink annotation on the entry link yet"); MaterializerNavigationLink materializedNavigationLink = new MaterializerNavigationLink(link, entry); link.SetAnnotation<MaterializerNavigationLink>(materializedNavigationLink); return materializedNavigationLink; }
/// <summary>Tries to resolve the object as one from the context (only if tracking is enabled).</summary> /// <param name="entry">Entry to resolve.</param> /// <param name="expectedEntryType">Expected entry type for the specified <paramref name="entry"/>.</param> /// <returns>true if the entity was resolved; false otherwise.</returns> private bool TryResolveFromContext(MaterializerEntry entry, Type expectedEntryType) { Debug.Assert(entry.IsTracking, "Should not be trying to resolve the entry from the context if entry.isTracking is false."); // We should either create a new instance or grab one from the context. if (this.MergeOption != MergeOption.NoTracking) { EntityStates state; entry.ResolvedObject = this.EntityTracker.TryGetEntity(entry.Id, out state); if (entry.ResolvedObject != null) { if (!expectedEntryType.IsInstanceOfType(entry.ResolvedObject)) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_Current(expectedEntryType, entry.ResolvedObject.GetType())); } ClientEdmModel edmModel = this.Model; entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(entry.ResolvedObject.GetType())); entry.EntityHasBeenResolved = true; // Note that deleted items will have their properties overwritten even // if PreserveChanges is used as a merge option. entry.ShouldUpdateFromPayload = this.MergeOption == MergeOption.OverwriteChanges || (this.MergeOption == MergeOption.PreserveChanges && state == EntityStates.Unchanged) || (this.MergeOption == MergeOption.PreserveChanges && state == EntityStates.Deleted); this.MaterializationLog.FoundExistingInstance(entry); return(true); } } return(false); }
/// <summary> /// Applies the values of a nested <paramref name="feed"/> to the collection /// <paramref name="property"/> of the specified <paramref name="entry"/>. /// </summary> /// <param name="entry">Entry with collection to be modified.</param> /// <param name="property">Collection property on the entry.</param> /// <param name="feed">Values to apply onto the collection.</param> /// <param name="includeLinks">Whether links that are expanded should be materialized.</param> private void ApplyFeedToCollection( MaterializerEntry entry, ClientPropertyAnnotation property, ODataResourceSet feed, bool includeLinks) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(property != null, "property != null"); Debug.Assert(feed != null, "feed != null"); ClientEdmModel edmModel = this.MaterializerContext.Model; ClientTypeAnnotation collectionType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(property.ResourceSetItemType)); IEnumerable <ODataResource> entries = MaterializerFeed.GetFeed(feed).Entries; foreach (ODataResource feedEntry in entries) { this.Materialize(MaterializerEntry.GetEntry(feedEntry), collectionType.ElementType, includeLinks); } ProjectionPlan continuationPlan = includeLinks ? ODataEntityMaterializer.CreatePlanForDirectMaterialization(property.ResourceSetItemType) : ODataEntityMaterializer.CreatePlanForShallowMaterialization(property.ResourceSetItemType); this.ApplyItemsToCollection( entry, property, entries.Select(e => MaterializerEntry.GetEntry(e).ResolvedObject), feed.NextPageLink, continuationPlan, false); }
/// <summary>Projects a simple value from the specified <paramref name="path"/>.</summary> /// <param name="materializer">Materializer under which projection is taking place.</param> /// <param name="entry">Root entry for paths.</param> /// <param name="expectedType">Expected type for <paramref name="entry"/>.</param> /// <param name="path">Path to pull value for.</param> /// <returns>The value for the specified <paramref name="path"/>.</returns> /// <remarks> /// This method will not instantiate entity types, except to satisfy requests /// for payload-driven feeds or leaf entities. /// </remarks> internal static object ProjectionValueForPath(object materializer, object entry, Type expectedType, object path) { Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); Debug.Assert(entry.GetType() == typeof(ODataEntry), "entry.GetType() == typeof(ODataEntry)"); Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)"); return(((ODataEntityMaterializer)materializer).ProjectionValueForPath(MaterializerEntry.GetEntry((ODataEntry)entry), expectedType, (ProjectionPath)path)); }
/// <summary>Checks whether the entity on the specified <paramref name="path"/> is null.</summary> /// <param name="entry">Root entry for paths.</param> /// <param name="expectedType">Expected type for <paramref name="entry"/>.</param> /// <param name="path">Path to pull value for.</param> /// <returns>Whether the specified <paramref name="path"/> is null.</returns> /// <remarks> /// This method will not instantiate entity types on the path. /// </remarks> internal static bool ProjectionCheckValueForPathIsNull( object entry, Type expectedType, object path) { Debug.Assert(entry.GetType() == typeof(ODataEntry), "entry.GetType() == typeof(ODataEntry)"); Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)"); return(ODataEntityMaterializer.ProjectionCheckValueForPathIsNull(MaterializerEntry.GetEntry((ODataEntry)entry), expectedType, (ProjectionPath)path)); }
/// <summary> /// Creates the materializer entry. /// </summary> /// <param name="entry">The entry.</param> /// <param name="format">The format the entry was read in.</param> /// <param name="isTracking">True if the contents of the entry will be tracked in the context, otherwise False.</param> /// <param name="model">The client model.</param> /// <returns>A new materializer entry.</returns> public static MaterializerEntry CreateEntry(ODataEntry entry, ODataFormat format, bool isTracking, ClientEdmModel model) { Debug.Assert(entry.GetAnnotation <MaterializerEntry>() == null, "MaterializerEntry has already been created."); MaterializerEntry materializerEntry = new MaterializerEntry(entry, format, isTracking, model); entry.SetAnnotation <MaterializerEntry>(materializerEntry); return(materializerEntry); }
/// <summary> /// Reads the remainder of an entry. /// </summary> /// <returns>An entry.</returns> private MaterializerEntry ReadEntryCore() { this.ExpectState(ODataReaderState.ResourceStart); ODataResource result = (ODataResource)this.reader.Item; MaterializerEntry entry; List <ODataNestedResourceInfo> navigationLinks = new List <ODataNestedResourceInfo>(); if (result != null) { entry = MaterializerEntry.CreateEntry( result, this.readODataFormat, this.mergeOption != MergeOption.NoTracking, this.clientEdmModel); do { this.AssertRead(); switch (this.reader.State) { case ODataReaderState.NestedResourceInfoStart: // Cache the list of navigation links here but don't add them to the entry because all of the key properties may not be available yet. navigationLinks.Add(this.ReadNestedResourceInfo()); break; case ODataReaderState.ResourceEnd: break; default: throw DSClient.Error.InternalError(InternalError.UnexpectedReadState); } }while (this.reader.State != ODataReaderState.ResourceEnd); if (!entry.Entry.IsTransient) { entry.UpdateEntityDescriptor(); } } else { entry = MaterializerEntry.CreateEmpty(); this.ReadAndExpectState(ODataReaderState.ResourceEnd); } // Add the navigation links here now that all of the property values have been read and are available to build the links. foreach (ODataNestedResourceInfo navigationLink in navigationLinks) { entry.AddNestedResourceInfo(navigationLink); } return(entry); }
/// <summary> /// Tries to read an entry. /// </summary> /// <param name="entry">The entry.</param> /// <returns>true if a value was read, otherwise false</returns> private bool TryReadEntry(out MaterializerEntry entry) { if (this.TryStartReadFeedOrEntry()) { this.ExpectState(ODataReaderState.ResourceStart); entry = this.ReadEntryCore(); return(true); } else { entry = default(MaterializerEntry); return(false); } }
/// <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, "ODataResource 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.ResourceSetItemType)); 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 ShouldUpdateForPayload 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); }
/// <summary>"Resolved" the entity in the <paramref name="entry"/> by instantiating it.</summary> /// <param name="entry">Entry to resolve.</param> /// <param name="type">Type to create.</param> /// <remarks> /// After invocation, entry.ResolvedObject is exactly of type <paramref name="type"/>. /// </remarks> internal void ResolveByCreatingWithType(MaterializerEntry entry, Type type) { // TODO: CreateNewInstance needs to do all of these operations otherwise an inadvertent call to CreateNewInstance // will create a new entity instance that is not tracked in the context or materialization log. Will need to change this // prior to shipping if public Debug.Assert( entry.ResolvedObject == null, "entry.ResolvedObject == null -- otherwise we're about to overwrite - should never be called"); ClientEdmModel edmModel = this.MaterializerContext.Model; entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(type)); entry.ResolvedObject = this.CreateNewInstance(entry.ActualType.EdmTypeReference, type); entry.CreatedByMaterializer = true; entry.ShouldUpdateFromPayload = true; entry.EntityHasBeenResolved = true; this.EntityTrackingAdapter.MaterializationLog.CreatedInstance(entry); }
/// <summary>Resolved or creates an instance on the specified <paramref name="entry"/>.</summary> /// <param name="entry">Entry on which to resolve or create an instance.</param> /// <param name="expectedEntryType">Expected type for the <paramref name="entry"/>.</param> /// <remarks> /// After invocation, the ResolvedObject value of the <paramref name="entry"/> /// will be assigned, along with the ActualType value. /// </remarks> /// <returns>True if an existing entity is found.</returns> internal virtual bool TryResolveExistingEntity(MaterializerEntry entry, Type expectedEntryType) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(expectedEntryType != null, "expectedEntryType != null"); Debug.Assert(entry.EntityHasBeenResolved == false, "entry.EntityHasBeenResolved == false"); // This will be the case when TargetInstance has been set. if (this.TryResolveAsTarget(entry)) { return(true); } if (this.TryResolveAsExistingEntry(entry, expectedEntryType)) { return(true); } return(false); }
/// <summary> /// This method is for parsing CUD operation payloads which should contain /// 1 a single entry /// 2 An Error /// </summary> /// <param name="message">the message for the payload</param> /// <param name="responseInfo">The current ResponseInfo object</param> /// <param name="expectedType">The expected type</param> /// <returns>the MaterializerEntry that was read</returns> internal static MaterializerEntry ParseSingleEntityPayload(IODataResponseMessage message, ResponseInfo responseInfo, Type expectedType) { ODataPayloadKind messageType = ODataPayloadKind.Resource; using (ODataMessageReader messageReader = CreateODataMessageReader(message, responseInfo, ref messageType)) { IEdmType edmType = responseInfo.TypeResolver.ResolveExpectedTypeForReading(expectedType); ODataReaderWrapper reader = ODataReaderWrapper.Create(messageReader, messageType, edmType, responseInfo.ResponsePipeline); FeedAndEntryMaterializerAdapter parser = new FeedAndEntryMaterializerAdapter(messageReader, reader, responseInfo.Model, responseInfo.MergeOption); ODataResource entry = null; bool readFeed = false; while (parser.Read()) { readFeed |= parser.CurrentFeed != null; if (parser.CurrentEntry != null) { if (entry != null) { throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_MultipleFound); } entry = parser.CurrentEntry; } } if (entry == null) { if (readFeed) { throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_NoneFound); } else { throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_ExpectedFeedOrEntry); } } return(MaterializerEntry.GetEntry(entry)); } }
/// <summary> /// Tries to resolve the specified entry as an entry that has already been created in this materialization session or is already in the context. /// </summary> /// <param name="entry">Entry to resolve.</param> /// <param name="expectedEntryType">Expected type of the entry.</param> /// <returns>True if the entry was resolved, otherwise False.</returns> internal bool TryResolveAsExistingEntry(MaterializerEntry entry, Type expectedEntryType) { if (entry.Entry.IsTransient) { return(false); } // Resolution is based on the entry Id, so if we can't use that property, we don't even need to try to resolve it. if (entry.IsTracking) { // The resolver methods below will both need access to Id, so first ensure it's not null if (entry.Id == null) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MissingIdElement); } return(this.TryResolveAsCreated(entry) || this.TryResolveFromContext(entry, expectedEntryType)); } return(false); }
/// <summary>Tries to resolve the object from those created in this materialization session.</summary> /// <param name="entry">Entry to resolve.</param> /// <returns>true if the entity was resolved; false otherwise.</returns> private bool TryResolveAsCreated(MaterializerEntry entry) { Debug.Assert(entry.IsTracking, "Should not be trying to resolve the entry from the current materialization session if entry.isTracking is false."); MaterializerEntry existingEntry; if (!this.MaterializationLog.TryResolve(entry, out existingEntry)) { return(false); } Debug.Assert( existingEntry.ResolvedObject != null, "existingEntry.ResolvedObject != null -- how did it get there otherwise?"); entry.ActualType = existingEntry.ActualType; entry.ResolvedObject = existingEntry.ResolvedObject; entry.CreatedByMaterializer = existingEntry.CreatedByMaterializer; entry.ShouldUpdateFromPayload = existingEntry.ShouldUpdateFromPayload; entry.EntityHasBeenResolved = true; return(true); }
/// <summary> /// Materializes the link properties if any with the url in the response payload /// </summary> /// <param name="actualType">Actual client type that is getting materialized.</param> /// <param name="entry">MaterializerEntry instance containing all the links that came in the response.</param> private static void ApplyLinkProperties( ClientTypeAnnotation actualType, MaterializerEntry entry) { Debug.Assert(actualType != null, "actualType != null"); Debug.Assert(entry.Entry != null, "entry != null"); if (entry.ShouldUpdateFromPayload) { foreach (var linkProperty in actualType.Properties().Where(p => p.PropertyType == typeof(DataServiceStreamLink))) { string propertyName = linkProperty.PropertyName; StreamDescriptor streamDescriptor; if (entry.EntityDescriptor.TryGetNamedStreamInfo(propertyName, out streamDescriptor)) { // At this time we have materialized the stream link onto the stream descriptor object // we'll always make sure the stream link is the same instance on the entity and the descriptor linkProperty.SetValue(entry.ResolvedObject, streamDescriptor.StreamLink, propertyName, true /*allowAdd*/); } } } }
/// <summary>Materializes the specified <paramref name="entry"/>.</summary> /// <param name="entry">Entry with object to materialize.</param> /// <param name="expectedEntryType">Expected type for the entry.</param> /// <param name="includeLinks">Whether links that are expanded should be materialized.</param> /// <remarks>This is a payload-driven materialization process.</remarks> internal void Materialize(MaterializerEntry entry, Type expectedEntryType, bool includeLinks) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert( entry.ResolvedObject == null || entry.ResolvedObject == this.EntityTrackingAdapter.TargetInstance, "entry.ResolvedObject == null || entry.ResolvedObject == this.targetInstance -- otherwise getting called twice"); Debug.Assert(expectedEntryType != null, "expectedType != null"); // ResolvedObject will already be assigned when we have a TargetInstance, for example. if (!this.EntityTrackingAdapter.TryResolveExistingEntity(entry, expectedEntryType)) { // If the type is a derived one this call will resolve to derived type, cannot put code in ResolveByCreatingWithType as this is used by projection and in this case // the type cannot be changed or updated ClientTypeAnnotation actualType = this.MaterializerContext.ResolveTypeForMaterialization(expectedEntryType, entry.Entry.TypeName); this.ResolveByCreatingWithType(entry, actualType.ElementType); } Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise ResolveOrCreateInstnace didn't do its job"); this.MaterializeResolvedEntry(entry, includeLinks); }
/// <summary> /// Tries to read a feed or entry. /// </summary> /// <param name="lazy">if set to <c>true</c> [lazy].</param> /// <param name="feed">The feed.</param> /// <param name="entry">The entry.</param> /// <returns>true if a value was read, otherwise false</returns> private bool TryReadFeedOrEntry(bool lazy, out ODataResourceSet feed, out MaterializerEntry entry) { if (this.TryStartReadFeedOrEntry()) { if (this.reader.State == ODataReaderState.ResourceStart) { entry = this.ReadEntryCore(); feed = null; } else { entry = null; feed = this.ReadFeedCore(lazy); } } else { feed = null; entry = null; } Debug.Assert(feed == null || entry == null, "feed == null || entry == null"); return(feed != null || entry != null); }
/// <summary>Tries to resolve the object as the target one in a POST refresh.</summary> /// <param name="entry">Entry to resolve.</param> /// <returns>true if the entity was resolved; false otherwise.</returns> private bool TryResolveAsTarget(MaterializerEntry entry) { if (entry.ResolvedObject == null) { return(false); } // The only case when the entity hasn't been resolved but // it has already been set is when the target instance // was set directly to refresh a POST. Debug.Assert( entry.ResolvedObject == this.TargetInstance, "entry.ResolvedObject == this.TargetInstance -- otherwise there we ResolveOrCreateInstance more than once on the same entry"); Debug.Assert( this.MergeOption == MergeOption.OverwriteChanges || this.MergeOption == MergeOption.PreserveChanges, "MergeOption.OverwriteChanges and MergeOption.PreserveChanges are the only expected values during SaveChanges"); ClientEdmModel edmModel = this.Model; entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(entry.ResolvedObject.GetType())); this.MaterializationLog.FoundTargetInstance(entry); entry.ShouldUpdateFromPayload = this.MergeOption != MergeOption.PreserveChanges; entry.EntityHasBeenResolved = true; return(true); }
/// <summary>Materializes an entry without including in-lined expanded links.</summary> /// <param name="materializer">Materializer under which materialization should take place.</param> /// <param name="entry">Entry with object to materialize.</param> /// <param name="expectedEntryType">Expected type for the entry.</param> /// <returns>The materialized instance.</returns> internal static object ShallowMaterializePlan(object materializer, object entry, Type expectedEntryType) { Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); Debug.Assert(entry.GetType() == typeof(ODataEntry), "entry.GetType() == typeof(ODataEntry)"); return(ODataEntityMaterializer.ShallowMaterializePlan((ODataEntityMaterializer)materializer, MaterializerEntry.GetEntry((ODataEntry)entry), expectedEntryType)); }
/// <summary>Initializes a projection-driven entry (with a specific type and specific properties).</summary> /// <param name="materializer">Materializer under which projection is taking place.</param> /// <param name="entry">Root entry for paths.</param> /// <param name="expectedType">Expected type for <paramref name="entry"/>.</param> /// <param name="resultType">Expected result type.</param> /// <param name="properties">Properties to materialize.</param> /// <param name="propertyValues">Functions to get values for functions.</param> /// <returns>The initialized entry.</returns> internal static object ProjectionInitializeEntity( object materializer, object entry, Type expectedType, Type resultType, string[] properties, Func <object, object, Type, object>[] propertyValues) { Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); Debug.Assert(entry.GetType() == typeof(ODataEntry), "entry.GetType() == typeof(ODataEntry)"); return(ODataEntityMaterializer.ProjectionInitializeEntity((ODataEntityMaterializer)materializer, MaterializerEntry.GetEntry((ODataEntry)entry), expectedType, resultType, properties, propertyValues)); }
/// <summary>Resolved or creates an instance on the specified <paramref name="entry"/>.</summary> /// <param name="entry">Entry on which to resolve or create an instance.</param> /// <param name="expectedEntryType">Expected type for the <paramref name="entry"/>.</param> /// <remarks> /// After invocation, the ResolvedObject value of the <paramref name="entry"/> /// will be assigned, along with the ActualType value. /// </remarks> /// <returns>True if an existing entity is found.</returns> internal virtual bool TryResolveExistingEntity(MaterializerEntry entry, Type expectedEntryType) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(expectedEntryType != null, "expectedEntryType != null"); Debug.Assert(entry.EntityHasBeenResolved == false, "entry.EntityHasBeenResolved == false"); // This will be the case when TargetInstance has been set. if (this.TryResolveAsTarget(entry)) { return true; } if (this.TryResolveAsExistingEntry(entry, expectedEntryType)) { return true; } return false; }
/// <summary> /// Invoke this method to notify the log that an existing /// instance was found while resolving an object. /// </summary> /// <param name="entry">Entry for instance.</param> internal void FoundExistingInstance(MaterializerEntry entry) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(IsEntity(entry), "Existing entries should be entity"); this.identityStack[entry.Id] = entry.Entry; }
/// <summary> /// Creates the materializer link with an entry. /// </summary> /// <param name="link">The link.</param> /// <param name="entry">The entry.</param> /// <returns>The materializer link.</returns> public static MaterializerNavigationLink CreateLink(ODataNestedResourceInfo link, MaterializerEntry entry) { Debug.Assert(link.GetAnnotation <MaterializerNavigationLink>() == null, "there should be no MaterializerNestedResourceInfo annotation on the entry link yet"); MaterializerNavigationLink materializedNestedResourceInfo = new MaterializerNavigationLink(link, entry); link.SetAnnotation <MaterializerNavigationLink>(materializedNestedResourceInfo); return(materializedNestedResourceInfo); }
/// <summary> /// Invoke this method to notify the log that a new link was /// added to a collection. /// </summary> /// <param name="source"> /// Instance with the collection to which <paramref name="target"/> /// was added. /// </param> /// <param name="propertyName">Property name for collection.</param> /// <param name="target">Object which was added.</param> internal void AddedLink(MaterializerEntry source, string propertyName, object target) { Debug.Assert(source.Entry != null || source.ForLoadProperty, "source != null || source.ForLoadProperty"); Debug.Assert(propertyName != null, "propertyName != null"); if (!this.Tracking) { return; } if (IsEntity(source) && IsEntity(target, this.clientEdmModel)) { LinkDescriptor item = new LinkDescriptor(source.ResolvedObject, propertyName, target, EntityStates.Added); this.links.Add(item); } }
/// <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> /// Fires the end entry events. /// </summary> /// <param name="entry">The entry.</param> internal void FireEndEntryEvents(MaterializerEntry entry) { Debug.Assert(entry != null, "entry!=null"); if (this.HasReadingEntityHandlers) { this.ExecuteEntityMaterializedActions(entry.Entry, entry.ResolvedObject); } }
/// <summary> /// Constructor /// </summary> /// <param name="descriptor">descriptor whose response is getting processed.</param> /// <param name="headers">headers</param> /// <param name="statusCode">status code</param> /// <param name="responseVersion">Parsed response OData-Version header.</param> /// <param name="entry">atom entry, if there is a non-error response payload.</param> /// <param name="exception">exception, if the request threw an exception.</param> internal CachedResponse(Descriptor descriptor, HeaderCollection headers, HttpStatusCode statusCode, Version responseVersion, MaterializerEntry entry, Exception exception) { Debug.Assert(descriptor != null, "descriptor != null"); Debug.Assert(headers != null, "headers != null"); Debug.Assert(entry == null || (exception == null && descriptor.DescriptorKind == DescriptorKind.Entity), "if entry is specified, exception cannot be specified and entry must be a resource, since we expect responses only for entities"); this.Descriptor = descriptor; this.MaterializerEntry = entry; this.Exception = exception; this.Headers = headers; this.StatusCode = statusCode; this.Version = responseVersion; }
/// <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); }
/// <summary> /// Implementation of Read/>. /// </summary> /// <returns> /// Return value of Read/> /// </returns> protected override bool ReadImplementation() { // Eagerly materialize the entire collection of objects into the items cache in LoadProperty scenario. if (this.iteration == 0) { while (base.ReadImplementation()) { this.items.Add(this.currentValue); } ClientPropertyAnnotation property = this.responseInfo.Property; EntityDescriptor entityDescriptor = this.responseInfo.EntityDescriptor; object entity = entityDescriptor.Entity; MaterializerEntry entry = MaterializerEntry.CreateEntryForLoadProperty( entityDescriptor, this.Format, this.responseInfo.MergeOption != MergeOption.NoTracking); entry.ActualType = this.responseInfo.Model.GetClientTypeAnnotation(this.responseInfo.Model.GetOrCreateEdmType(entity.GetType())); if (property.IsEntityCollection) { this.EntryValueMaterializationPolicy.ApplyItemsToCollection( entry, property, this.items, this.CurrentFeed != null ? this.CurrentFeed.NextPageLink : null, this.MaterializeEntryPlan, this.responseInfo.IsContinuation); } else { Debug.Assert(this.items.Count <= 1, "Expecting 0 or 1 element."); object target = this.items.Count > 0 ? this.items[0] : null; Debug.Assert(property.EdmProperty.Type.TypeKind() == EdmTypeKind.Entity, "Must be entity typed property if not an entity collection property."); this.EntityTrackingAdapter.MaterializationLog.SetLink(entry, property.PropertyName, target); // Singleton entity property property.SetValue(entity, target, property.PropertyName, false); } // Apply the materialization log. this.ApplyLogToContext(); // Clear the log after applying it. this.ClearLog(); } // Read object from the already loaded items cache. if (this.items.Count > this.iteration) { this.currentValue = this.items[this.iteration]; this.iteration++; return(true); } else { this.currentValue = null; return(false); } }
/// <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. // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa 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? */); 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.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); }
/// <summary> /// Applies the values of a nested <paramref name="feed"/> to the collection /// <paramref name="property"/> of the specified <paramref name="entry"/>. /// </summary> /// <param name="entry">Entry with collection to be modified.</param> /// <param name="property">Collection property on the entry.</param> /// <param name="feed">Values to apply onto the collection.</param> /// <param name="includeLinks">Whether links that are expanded should be materialized.</param> private void ApplyFeedToCollection( MaterializerEntry entry, ClientPropertyAnnotation property, ODataFeed feed, bool includeLinks) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(property != null, "property != null"); Debug.Assert(feed != null, "feed != null"); ClientEdmModel edmModel = this.MaterializerContext.Model; ClientTypeAnnotation collectionType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(property.EntityCollectionItemType)); IEnumerable<ODataEntry> entries = MaterializerFeed.GetFeed(feed).Entries; foreach (ODataEntry feedEntry in entries) { this.Materialize(MaterializerEntry.GetEntry(feedEntry), collectionType.ElementType, includeLinks); } ProjectionPlan continuationPlan = includeLinks ? ODataEntityMaterializer.CreatePlanForDirectMaterialization(property.EntityCollectionItemType) : ODataEntityMaterializer.CreatePlanForShallowMaterialization(property.EntityCollectionItemType); this.ApplyItemsToCollection( entry, property, entries.Select(e => MaterializerEntry.GetEntry(e).ResolvedObject), feed.NextPageLink, continuationPlan, false); }
/// <summary>Provides support for Select invocations for projections.</summary> /// <param name="materializer">Materializer under which projection is taking place.</param> /// <param name="entry">Root entry for paths.</param> /// <param name="expectedType">Expected type for <paramref name="entry"/>.</param> /// <param name="resultType">Expected result type.</param> /// <param name="path">Path to traverse.</param> /// <param name="selector">Selector callback.</param> /// <returns>An enumerable with the select results.</returns> internal static IEnumerable ProjectionSelect( object materializer, object entry, Type expectedType, Type resultType, object path, Func <object, object, Type, object> selector) { Debug.Assert(typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType()), "typeof(ODataEntityMaterializer).IsAssignableFrom(materializer.GetType())"); Debug.Assert(entry.GetType() == typeof(ODataEntry), "entry.GetType() == typeof(ODataEntry)"); Debug.Assert(path.GetType() == typeof(ProjectionPath), "path.GetType() == typeof(ProjectionPath)"); return(ODataEntityMaterializer.ProjectionSelect((ODataEntityMaterializer)materializer, MaterializerEntry.GetEntry((ODataEntry)entry), expectedType, resultType, (ProjectionPath)path, selector)); }
/// <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; }
/// <summary> /// Tries to read a feed or entry. /// </summary> /// <param name="lazy">if set to <c>true</c> [lazy].</param> /// <param name="feed">The feed.</param> /// <param name="entry">The entry.</param> /// <returns>true if a value was read, otherwise false</returns> private bool TryReadFeedOrEntry(bool lazy, out ODataFeed feed, out MaterializerEntry entry) { if (this.TryStartReadFeedOrEntry()) { if (this.reader.State == ODataReaderState.EntryStart) { entry = this.ReadEntryCore(); feed = null; } else { entry = null; feed = this.ReadFeedCore(lazy); } } else { feed = null; entry = null; } Debug.Assert(feed == null || entry == null, "feed == null || entry == null"); return feed != null || entry != null; }
/// <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> /// Tries to resolve the specified entry as an entry that has already been created in this materialization session or is already in the context. /// </summary> /// <param name="entry">Entry to resolve.</param> /// <param name="expectedEntryType">Expected type of the entry.</param> /// <returns>True if the entry was resolved, otherwise False.</returns> internal bool TryResolveAsExistingEntry(MaterializerEntry entry, Type expectedEntryType) { if (entry.Entry.IsTransient) { return false; } // Resolution is based on the entry Id, so if we can't use that property, we don't even need to try to resolve it. if (entry.IsAtomOrTracking) { // The resolver methods below will both need access to Id, so first ensure it's not null if (entry.Id == null) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MissingIdElement); } return this.TryResolveAsCreated(entry) || this.TryResolveFromContext(entry, expectedEntryType); } return false; }
/// <summary>Attempts to resolve an entry from those tracked in the log.</summary> /// <param name="entry">Entry to resolve.</param> /// <param name="existingEntry"> /// After invocation, an existing entry with the same identity as /// <paramref name="entry"/>; possibly null. /// </param> /// <returns>true if an existing entry was found; false otherwise.</returns> internal bool TryResolve(MaterializerEntry entry, out MaterializerEntry existingEntry) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(entry.Id != null, "entry.Id != null"); Debug.Assert(entry.IsAtomOrTracking, "Should not be trying to resolve the entry if entry.IsAtomOrTracking is false."); ODataEntry existingODataEntry; if (this.identityStack.TryGetValue(entry.Id, out existingODataEntry)) { existingEntry = MaterializerEntry.GetEntry(existingODataEntry); return true; } if (this.appendOnlyEntries.TryGetValue(entry.Id, out existingODataEntry)) { // The AppendOnly entries are valid only as long as they were not modified // between calls to .MoveNext(). EntityStates state; this.entityTracker.TryGetEntity(entry.Id, out state); if (state == EntityStates.Unchanged) { existingEntry = MaterializerEntry.GetEntry(existingODataEntry); return true; } else { this.appendOnlyEntries.Remove(entry.Id); } } existingEntry = default(MaterializerEntry); return false; }
/// <summary>"Resolved" the entity in the <paramref name="entry"/> by instantiating it.</summary> /// <param name="entry">Entry to resolve.</param> /// <param name="type">Type to create.</param> /// <remarks> /// After invocation, entry.ResolvedObject is exactly of type <paramref name="type"/>. /// </remarks> internal void ResolveByCreatingWithType(MaterializerEntry entry, Type type) { // TODO: CreateNewInstance needs to do all of these operations otherwise an inadvertent call to CreateNewInstance // will create a new entity instance that is not tracked in the context or materialization log. Will need to change this // prior to shipping if public Debug.Assert( entry.ResolvedObject == null, "entry.ResolvedObject == null -- otherwise we're about to overwrite - should never be called"); ClientEdmModel edmModel = this.MaterializerContext.Model; entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(type)); entry.ResolvedObject = this.CreateNewInstance(entry.ActualType.EdmTypeReference, type); entry.CreatedByMaterializer = true; entry.ShouldUpdateFromPayload = true; entry.EntityHasBeenResolved = true; this.EntityTrackingAdapter.MaterializationLog.CreatedInstance(entry); }
/// <summary> /// Invoke this method to notify the log that the /// target instance of a "directed" update was found. /// </summary> /// <param name="entry">Entry found.</param> /// <remarks> /// The target instance is typically the object that we /// expect will get refreshed by the response from a POST /// method. /// /// For example if a create a Customer and POST it to /// a service, the response of the POST will return the /// re-serialized instance, with (important!) server generated /// values and URIs. /// </remarks> internal void FoundTargetInstance(MaterializerEntry entry) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise this is not a target"); if (IsEntity(entry)) { Debug.Assert(entry.IsAtomOrTracking, "entry.IsAtomOrTracking == true, otherwise we should not be tracking this entry with the context."); this.entityTracker.AttachIdentity(entry.EntityDescriptor, this.mergeOption); this.identityStack.Add(entry.Id, entry.Entry); this.insertRefreshObject = entry.ResolvedObject; } }
/// <summary> /// Applies the values of the <paramref name="items"/> enumeration to the /// <paramref name="property"/> of the specified <paramref name="entry"/>. /// </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> /// <param name="isContinuation">Whether this is a continuation request.</param> internal void ApplyItemsToCollection( MaterializerEntry entry, ClientPropertyAnnotation property, IEnumerable items, Uri nextLink, ProjectionPlan continuationPlan, bool isContinuation) { 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"); IEnumerable<object> itemsEnumerable = ODataEntityMaterializer.EnumerateAsElementType<object>(items); // Populate the collection property with items collection. object collection = this.PopulateCollectionProperty(entry, property, itemsEnumerable, nextLink, continuationPlan); // Get collection of all non-linked elements in collection and remove them except for the ones that were obtained from the response. if (this.EntityTrackingAdapter.MergeOption == MergeOption.OverwriteChanges || this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges) { var linkedItemsInCollection = this.EntityTrackingAdapter .EntityTracker .GetLinks(entry.ResolvedObject, property.PropertyName) .Select(l => new { l.Target, l.IsModified }); if (collection != null && !property.IsDictionary) { var nonLinkedNonReceivedItemsInCollection = ODataEntityMaterializer.EnumerateAsElementType<object>((IEnumerable)collection) .Except(linkedItemsInCollection.Select(i => i.Target)) .Except(itemsEnumerable).ToArray(); // Since no link exists, we just remove the item from the collection foreach (var item in nonLinkedNonReceivedItemsInCollection) { property.RemoveValue(collection, item); } } // When the first time a property or collection is being loaded, we remove all items other than the ones that we receive. if (!isContinuation) { IEnumerable<object> itemsToRemove; if (this.EntityTrackingAdapter.MergeOption == MergeOption.OverwriteChanges) { itemsToRemove = linkedItemsInCollection.Select(i => i.Target); } else { Debug.Assert( this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges, "this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges"); itemsToRemove = linkedItemsInCollection .Where(i => !i.IsModified) .Select(i => i.Target); } itemsToRemove = itemsToRemove.Except(itemsEnumerable); foreach (var item in itemsToRemove) { if (collection != null) { property.RemoveValue(collection, item); } this.EntityTrackingAdapter.MaterializationLog.RemovedLink(entry, property.PropertyName, item); } } } }
/// <summary> /// Applies the values of the <paramref name="items"/> enumeration to the /// <paramref name="property"/> of the specified <paramref name="entry"/>. /// </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> /// <param name="isContinuation">Whether this is a continuation request.</param> internal void ApplyItemsToCollection( MaterializerEntry entry, ClientPropertyAnnotation property, IEnumerable items, Uri nextLink, ProjectionPlan continuationPlan, bool isContinuation) { Debug.Assert(entry.Entry != null || entry.ForLoadProperty, "ODataResource should be non-null except for LoadProperty"); Debug.Assert(property != null, "property != null"); Debug.Assert(items != null, "items != null"); IEnumerable <object> itemsEnumerable = ODataEntityMaterializer.EnumerateAsElementType <object>(items); // Populate the collection property with items collection. object collection = this.PopulateCollectionProperty(entry, property, itemsEnumerable, nextLink, continuationPlan); // Get collection of all non-linked elements in collection and remove them except for the ones that were obtained from the response. if (this.EntityTrackingAdapter.MergeOption == MergeOption.OverwriteChanges || this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges) { var linkedItemsInCollection = this.EntityTrackingAdapter .EntityTracker .GetLinks(entry.ResolvedObject, property.PropertyName) .Select(l => new { l.Target, l.IsModified }); if (collection != null && !property.IsDictionary) { var nonLinkedNonReceivedItemsInCollection = ODataEntityMaterializer.EnumerateAsElementType <object>((IEnumerable)collection) .Except(linkedItemsInCollection.Select(i => i.Target)) .Except(itemsEnumerable).ToArray(); // Since no link exists, we just remove the item from the collection foreach (var item in nonLinkedNonReceivedItemsInCollection) { property.RemoveValue(collection, item); } } // When the first time a property or collection is being loaded, we remove all items other than the ones that we receive. if (!isContinuation) { IEnumerable <object> itemsToRemove; if (this.EntityTrackingAdapter.MergeOption == MergeOption.OverwriteChanges) { itemsToRemove = linkedItemsInCollection.Select(i => i.Target); } else { Debug.Assert( this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges, "this.EntityTrackingAdapter.MergeOption == MergeOption.PreserveChanges"); itemsToRemove = linkedItemsInCollection .Where(i => !i.IsModified) .Select(i => i.Target); } itemsToRemove = itemsToRemove.Except(itemsEnumerable); foreach (var item in itemsToRemove) { if (collection != null) { property.RemoveValue(collection, item); } this.EntityTrackingAdapter.MaterializationLog.RemovedLink(entry, property.PropertyName, item); } } } }
/// <summary>Materializes the specified <paramref name="entry"/>.</summary> /// <param name="entry">Entry with object to materialize.</param> /// <param name="expectedEntryType">Expected type for the entry.</param> /// <param name="includeLinks">Whether links that are expanded should be materialized.</param> /// <remarks>This is a payload-driven materialization process.</remarks> internal void Materialize(MaterializerEntry entry, Type expectedEntryType, bool includeLinks) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert( entry.ResolvedObject == null || entry.ResolvedObject == this.EntityTrackingAdapter.TargetInstance, "entry.ResolvedObject == null || entry.ResolvedObject == this.targetInstance -- otherwise getting called twice"); Debug.Assert(expectedEntryType != null, "expectedType != null"); // ResolvedObject will already be assigned when we have a TargetInstance, for example. if (!this.EntityTrackingAdapter.TryResolveExistingEntity(entry, expectedEntryType)) { // If the type is a derived one this call will resolve to derived type, cannot put code in ResolveByCreatingWithType as this is used by projection and in this case // the type cannot be changed or updated ClientTypeAnnotation actualType = this.MaterializerContext.ResolveTypeForMaterialization(expectedEntryType, entry.Entry.TypeName); this.ResolveByCreatingWithType(entry, actualType.ElementType); } Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise ResolveOrCreateInstnace didn't do its job"); this.MaterializeResolvedEntry(entry, includeLinks); }
/// <summary>Tries to resolve the object from those created in this materialization session.</summary> /// <param name="entry">Entry to resolve.</param> /// <returns>true if the entity was resolved; false otherwise.</returns> private bool TryResolveAsCreated(MaterializerEntry entry) { Debug.Assert(entry.IsAtomOrTracking, "Should not be trying to resolve the entry from the current materialization session if entry.IsAtomOrTracking is false."); MaterializerEntry existingEntry; if (!this.MaterializationLog.TryResolve(entry, out existingEntry)) { return false; } Debug.Assert( existingEntry.ResolvedObject != null, "existingEntry.ResolvedObject != null -- how did it get there otherwise?"); entry.ActualType = existingEntry.ActualType; entry.ResolvedObject = existingEntry.ResolvedObject; entry.CreatedByMaterializer = existingEntry.CreatedByMaterializer; entry.ShouldUpdateFromPayload = existingEntry.ShouldUpdateFromPayload; entry.EntityHasBeenResolved = true; return true; }
/// <summary> /// Returns true the specified entry represents an entity. /// </summary> /// <param name="entry">The materializer entry</param> /// <returns>True if the entry represents an entity.</returns> private static bool IsEntity(MaterializerEntry entry) { Debug.Assert(entry.ActualType != null, "Entry with no type added to log"); return entry.ActualType.IsEntityType; }
/// <summary>Tries to resolve the object as one from the context (only if tracking is enabled).</summary> /// <param name="entry">Entry to resolve.</param> /// <param name="expectedEntryType">Expected entry type for the specified <paramref name="entry"/>.</param> /// <returns>true if the entity was resolved; false otherwise.</returns> private bool TryResolveFromContext(MaterializerEntry entry, Type expectedEntryType) { Debug.Assert(entry.IsAtomOrTracking, "Should not be trying to resolve the entry from the context if entry.IsAtomOrTracking is false."); // We should either create a new instance or grab one from the context. if (this.MergeOption != MergeOption.NoTracking) { EntityStates state; entry.ResolvedObject = this.EntityTracker.TryGetEntity(entry.Id, out state); if (entry.ResolvedObject != null) { if (!expectedEntryType.IsInstanceOfType(entry.ResolvedObject)) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_Current(expectedEntryType, entry.ResolvedObject.GetType())); } ClientEdmModel edmModel = this.Model; entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(entry.ResolvedObject.GetType())); entry.EntityHasBeenResolved = true; // Note that deleted items will have their properties overwritten even // if PreserveChanges is used as a merge option. entry.ShouldUpdateFromPayload = this.MergeOption == MergeOption.OverwriteChanges || (this.MergeOption == MergeOption.PreserveChanges && state == EntityStates.Unchanged) || (this.MergeOption == MergeOption.PreserveChanges && state == EntityStates.Deleted); this.MaterializationLog.FoundExistingInstance(entry); return true; } } return false; }
/// <summary> /// Invoke this method to notify the log that a link was set on /// a property. /// </summary> /// <param name="source">Entry for source object.</param> /// <param name="propertyName">Name of property set.</param> /// <param name="target">Target object.</param> internal void SetLink(MaterializerEntry source, string propertyName, object target) { Debug.Assert(source.Entry != null || source.ForLoadProperty, "source != null || source.ForLoadProperty"); Debug.Assert(propertyName != null, "propertyName != null"); if (!this.Tracking) { return; } if (IsEntity(source) && IsEntity(target, this.clientEdmModel)) { Debug.Assert(this.Tracking, "this.Tracking -- otherwise there's an 'if' missing (it happens to be that the assert holds for all current callers"); LinkDescriptor item = new LinkDescriptor(source.ResolvedObject, propertyName, target, EntityStates.Modified); this.links.Add(item); } }
/// <summary>Tries to resolve the object as the target one in a POST refresh.</summary> /// <param name="entry">Entry to resolve.</param> /// <returns>true if the entity was resolved; false otherwise.</returns> private bool TryResolveAsTarget(MaterializerEntry entry) { if (entry.ResolvedObject == null) { return false; } // The only case when the entity hasn't been resolved but // it has already been set is when the target instance // was set directly to refresh a POST. Debug.Assert( entry.ResolvedObject == this.TargetInstance, "entry.ResolvedObject == this.TargetInstance -- otherwise there we ResolveOrCreateInstance more than once on the same entry"); Debug.Assert( this.MergeOption == MergeOption.OverwriteChanges || this.MergeOption == MergeOption.PreserveChanges, "MergeOption.OverwriteChanges and MergeOption.PreserveChanges are the only expected values during SaveChanges"); ClientEdmModel edmModel = this.Model; entry.ActualType = edmModel.GetClientTypeAnnotation(edmModel.GetOrCreateEdmType(entry.ResolvedObject.GetType())); this.MaterializationLog.FoundTargetInstance(entry); entry.ShouldUpdateFromPayload = this.MergeOption != MergeOption.PreserveChanges; entry.EntityHasBeenResolved = true; return true; }
/// <summary> /// Invoke this method to notify the log that a new instance /// was created. /// </summary> /// <param name="entry">Entry for the created instance.</param> internal void CreatedInstance(MaterializerEntry entry) { Debug.Assert(entry.Entry != null, "entry != null"); Debug.Assert(entry.ResolvedObject != null, "entry.ResolvedObject != null -- otherwise, what did we create?"); Debug.Assert(entry.CreatedByMaterializer, "entry.CreatedByMaterializer -- otherwise we shouldn't be calling this"); if (IsEntity(entry) && entry.IsAtomOrTracking && !entry.Entry.IsTransient) { this.identityStack.Add(entry.Id, entry.Entry); if (this.mergeOption == MergeOption.AppendOnly) { this.appendOnlyEntries.Add(entry.Id, entry.Entry); } } }
/// <summary>Provides support for getting payload entries during projections.</summary> /// <param name="entry">Entry to get sub-entry from.</param> /// <param name="name">Name of sub-entry.</param> /// <returns>The sub-entry (never null).</returns> internal static object ProjectionGetEntry(object entry, string name) { Debug.Assert(entry.GetType() == typeof(ODataEntry), "entry.GetType() == typeof(ODataEntry)"); return(ODataEntityMaterializer.ProjectionGetEntry(MaterializerEntry.GetEntry((ODataEntry)entry), name)); }
/// <summary> /// Materializes the link properties if any with the url in the response payload /// </summary> /// <param name="actualType">Actual client type that is getting materialized.</param> /// <param name="entry">MaterializerEntry instance containing all the links that came in the response.</param> private static void ApplyLinkProperties( ClientTypeAnnotation actualType, MaterializerEntry entry) { Debug.Assert(actualType != null, "actualType != null"); Debug.Assert(entry.Entry != null, "entry != null"); if (entry.ShouldUpdateFromPayload) { foreach (var linkProperty in actualType.Properties().Where(p => p.PropertyType == typeof(DataServiceStreamLink))) { string propertyName = linkProperty.PropertyName; StreamDescriptor streamDescriptor; if (entry.EntityDescriptor.TryGetNamedStreamInfo(propertyName, out streamDescriptor)) { // At this time we have materialized the stream link onto the stream descriptor object // we'll always make sure the stream link is the same instance on the entity and the descriptor linkProperty.SetValue(entry.ResolvedObject, streamDescriptor.StreamLink, propertyName, true /*allowAdd*/); } } } }
/// <summary> /// Tries to read an entry. /// </summary> /// <param name="entry">The entry.</param> /// <returns>true if a value was read, otherwise false</returns> private bool TryReadEntry(out MaterializerEntry entry) { if (this.TryStartReadFeedOrEntry()) { this.ExpectState(ODataReaderState.EntryStart); entry = this.ReadEntryCore(); return true; } else { entry = default(MaterializerEntry); return false; } }
/// <summary> /// Creates the materializer entry. /// </summary> /// <param name="entry">The entry.</param> /// <param name="format">The format the entry was read in.</param> /// <param name="isTracking">True if the contents of the entry will be tracked in the context, otherwise False.</param> /// <param name="model">The client model.</param> /// <returns>A new materializer entry.</returns> public static MaterializerEntry CreateEntry(ODataEntry entry, ODataFormat format, bool isTracking, ClientEdmModel model) { Debug.Assert(entry.GetAnnotation<MaterializerEntry>() == null, "MaterializerEntry has already been created."); MaterializerEntry materializerEntry = new MaterializerEntry(entry, format, isTracking, model); entry.SetAnnotation<MaterializerEntry>(materializerEntry); return materializerEntry; }