/// <summary> /// Computes the metadata version of the property. /// </summary> /// <param name="propertyCollection">List of properties for which metadata version needs to be computed.</param> /// <param name="visitedComplexTypes">List of complex type already visited.</param> /// <returns>the metadata version of the property collection.</returns> private Version ComputeVersionForPropertyCollection(IEnumerable <IEdmProperty> propertyCollection, HashSet <IEdmType> visitedComplexTypes) { Version propertyMetadataVersion = Util.ODataVersion4; foreach (IEdmProperty property in propertyCollection) { ClientPropertyAnnotation propertyAnnotation = this.model.GetClientPropertyAnnotation(property); if (property.Type.TypeKind() == EdmTypeKind.Complex && !propertyAnnotation.IsDictionary) { if (visitedComplexTypes == null) { visitedComplexTypes = new HashSet <IEdmType>(EqualityComparer <IEdmType> .Default); } else if (visitedComplexTypes.Contains(property.Type.Definition)) { continue; } visitedComplexTypes.Add(property.Type.Definition); WebUtil.RaiseVersion(ref propertyMetadataVersion, this.ComputeVersionForPropertyCollection(this.model.GetClientTypeAnnotation(property).EdmProperties(), visitedComplexTypes)); } } return(propertyMetadataVersion); }
/// <summary> /// Check if this type represents an ATOM-style media link entry and /// if so mark the ClientType as such /// </summary> private void CheckMediaLinkEntry() { this.isMediaLinkEntry = false; // MediaEntryAttribute does not allow multiples, so there can be at most 1 instance on the type. MediaEntryAttribute mediaEntryAttribute = (MediaEntryAttribute)this.ElementType.GetCustomAttributes(typeof(MediaEntryAttribute), true).SingleOrDefault(); if (mediaEntryAttribute != null) { this.isMediaLinkEntry = true; ClientPropertyAnnotation mediaProperty = this.Properties().SingleOrDefault(p => p.PropertyName == mediaEntryAttribute.MediaMemberName); if (mediaProperty == null) { throw Microsoft.OData.Client.Error.InvalidOperation(Microsoft.OData.Client.Strings.ClientType_MissingMediaEntryProperty( this.ElementTypeName, mediaEntryAttribute.MediaMemberName)); } this.mediaDataMember = mediaProperty; } // HasStreamAttribute does not allow multiples, so there can be at most 1 instance on the type. bool hasStreamAttribute = this.ElementType.GetCustomAttributes(typeof(HasStreamAttribute), true).Any(); if (hasStreamAttribute) { this.isMediaLinkEntry = true; } }
public void Initialize() { this.clientEdmModel = new ClientEdmModel(ODataProtocolVersion.V4); this.clientEdmModel.GetOrCreateEdmType(typeof(TestCustomer)); this.clientEdmModel.GetOrCreateEdmType(typeof(TestOrder)); this.materializerContext = new TestMaterializerContext() { Model = this.clientEdmModel }; this.ordersProperty = this.clientEdmModel.GetClientTypeAnnotation(typeof(TestCustomer)).GetProperty("Orders", false); }
/// <summary> /// Determines whether a given property should be serialized into an insert or update payload. /// </summary> /// <param name="type">The declaring type of the property.</param> /// <param name="property">The property.</param> /// <returns>Whether or not the property should be serialized.</returns> private static bool ShouldSerializeProperty(ClientTypeAnnotation type, ClientPropertyAnnotation property) { // don't write property if it is a dictionary // don't write mime data member or the mime type member for it // link properties need to be ignored return(!property.IsDictionary && property != type.MediaDataMember && !property.IsStreamLinkProperty && (type.MediaDataMember == null || type.MediaDataMember.MimeTypeProperty != property)); }
/// <summary> /// Determines whether a given property should be serialized into an insert or update payload. /// </summary> /// <param name="type">The declaring type of the property.</param> /// <param name="property">The property.</param> /// <returns>Whether or not the property should be serialized.</returns> private static bool ShouldSerializeProperty(ClientTypeAnnotation type, ClientPropertyAnnotation property) { // don't write property if it is a dictionary // don't write mime data member or the mime type member for it // link properties need to be ignored // don't write property if it is tagged with IgnoreClientProperty attribute return(!property.IsDictionary && property != type.MediaDataMember && !property.IsStreamLinkProperty && (type.MediaDataMember == null || type.MediaDataMember.MimeTypeProperty != property) && property.PropertyInfo.GetCustomAttributes(typeof(IgnoreClientPropertyAttribute)).Count() == 0); }
/// <summary> /// Validates the specified <paramref name="property"/> matches /// the parsed <paramref name="link"/>. /// </summary> /// <param name="property">Property as understood by the type system.</param> /// <param name="link">Property as parsed.</param> /// <param name="model">Client Model.</param> /// <param name="performEntityCheck">whether to do the entity check or not.</param> /// <returns>The type</returns> internal static Type ValidatePropertyMatch(ClientPropertyAnnotation property, ODataNavigationLink link, ClientEdmModel model, bool performEntityCheck) { Debug.Assert(property != null, "property != null"); Debug.Assert(link != null, "link != null"); Type propertyType = null; if (link.IsCollection.HasValue) { if (link.IsCollection.Value) { // We need to fail if the payload states that the property is a navigation collection property // and in the client, the property is not a collection property. if (!property.IsEntityCollection) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(property.PropertyName)); } propertyType = property.EntityCollectionItemType; } else { if (property.IsEntityCollection) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkEntryPropertyIsCollection(property.PropertyName)); } propertyType = property.PropertyType; } } // If the server type and the client type does not match, we need to throw. // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa if (propertyType != null && performEntityCheck) { if (!ClientTypeUtil.TypeIsEntity(propertyType, model)) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(propertyType.ToString())); } } return propertyType; }
/// <summary> /// Determines whether a given property should be serialized into an insert or update payload. /// </summary> /// <param name="type">The declaring type of the property.</param> /// <param name="property">The property.</param> /// <returns>Whether or not the property should be serialized.</returns> private static bool ShouldSerializeProperty(ClientTypeAnnotation type, ClientPropertyAnnotation property) { // don't write property if it is a dictionary // don't write mime data member or the mime type member for it // link properties need to be ignored return !property.IsDictionary && property != type.MediaDataMember && !property.IsStreamLinkProperty && (type.MediaDataMember == null || type.MediaDataMember.MimeTypeProperty != property); }
/// <summary> /// Validates the specified <paramref name="property"/> matches /// the parsed <paramref name="link"/>. /// </summary> /// <param name="property">Property as understood by the type system.</param> /// <param name="link">Property as parsed.</param> internal static void ValidatePropertyMatch(ClientPropertyAnnotation property, ODataNavigationLink link) { ValidatePropertyMatch(property, link, null, false /*performEntityCheck*/); }
/// <summary> /// Validates the specified <paramref name="property"/> matches /// the parsed <paramref name="atomProperty"/>. /// </summary> /// <param name="property">Property as understood by the type system.</param> /// <param name="atomProperty">Property as parsed.</param> internal static void ValidatePropertyMatch(ClientPropertyAnnotation property, ODataProperty atomProperty) { ValidatePropertyMatch(property, atomProperty, null, false /*performEntityCheck*/); }
/// <summary> /// Gets or creates a collection property on the specified <paramref name="instance"/>. /// </summary> /// <param name="instance">Instance on which to get/create the collection.</param> /// <param name="property">Collection property on the <paramref name="instance"/>.</param> /// <param name="forLoadProperty">Is this collection being created for LoadProperty scenario.</param> /// <returns> /// The collection corresponding to the specified <paramref name="property"/>; /// never null. /// </returns> private object GetOrCreateCollectionProperty(object instance, ClientPropertyAnnotation property, bool forLoadProperty) { Debug.Assert(instance != null, "instance != null"); Debug.Assert(property != null, "property != null"); Debug.Assert(property.IsEntityCollection, "property.IsEntityCollection has to be true - otherwise property isn't a collection"); // NOTE: in V1, we would have instantiated nested objects before setting them. object result; result = property.GetValue(instance); if (result == null) { Type collectionType = property.PropertyType; // For backward compatiblity we need to have different strategy of collection creation b/w // LoadProperty scenario versus regular collection creation scenario. if (forLoadProperty) { if (BindingEntityInfo.IsDataServiceCollection(collectionType, this.MaterializerContext.Model)) { Debug.Assert(WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType) != null, "DataServiceCollection<> must be available here."); // new DataServiceCollection<property.EntityCollectionItemType>(null, TrackingMode.None) result = Activator.CreateInstance( WebUtil.GetDataServiceCollectionOfT(property.EntityCollectionItemType), null, TrackingMode.None); } else { // Try List<> first because that's what we did in V1/V2, but use the actual property type if it doesn't support List<> Type listCollectionType = typeof(List<>).MakeGenericType(property.EntityCollectionItemType); if (collectionType.IsAssignableFrom(listCollectionType)) { collectionType = listCollectionType; } result = Activator.CreateInstance(collectionType); } } else { if (DSClient.PlatformHelper.IsInterface(collectionType)) { collectionType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(property.EntityCollectionItemType); } result = this.CreateNewInstance(property.EdmProperty.Type, collectionType); } // Update the property value on the instance. property.SetValue(instance, result, property.PropertyName, false /* add */); } Debug.Assert(result != null, "result != null -- otherwise GetOrCreateCollectionProperty didn't fall back to creation"); return result; }
/// <summary> /// 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); }
private MaterializeAtom ReadPropertyFromRawData(ClientPropertyAnnotation property) { DataServiceContext context = (DataServiceContext)this.Source; bool merging = context.ApplyingChanges; try { context.ApplyingChanges = true; // if this is the data property for a media entry, what comes back // is the raw value (no markup) #if ASTORIA_OPEN_OBJECT object openProps = null; #endif string mimeType = null; Encoding encoding = null; Type elementType = property.EntityCollectionItemType ?? property.NullablePropertyType; IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); ContentTypeUtil.ReadContentType(this.ContentType, out mimeType, out encoding); using (Stream responseStream = this.GetResponseStream()) { // special case byte[], and for everything else let std conversion kick-in if (property.PropertyType == typeof(byte[])) { int total = checked((int)this.ContentLength); byte[] buffer = null; if (total >= 0) { buffer = LoadPropertyResult.ReadByteArrayWithContentLength(responseStream, total); } else { buffer = LoadPropertyResult.ReadByteArrayChunked(responseStream); } results.Add(buffer); #if ASTORIA_OPEN_OBJECT property.SetValue(this.entity, buffer, this.propertyName, ref openProps, false); #else property.SetValue(this.entity, buffer, this.propertyName, false); #endif } else { // responseStream will disposed, StreamReader doesn't need to dispose of it. StreamReader reader = new StreamReader(responseStream, encoding); object convertedValue = property.PropertyType == typeof(string) ? reader.ReadToEnd() : ClientConvert.ChangeType(reader.ReadToEnd(), property.PropertyType); results.Add(convertedValue); #if ASTORIA_OPEN_OBJECT property.SetValue(this.entity, convertedValue, this.propertyName, ref openProps, false); #else property.SetValue(this.entity, convertedValue, this.propertyName, false); #endif } } #if ASTORIA_OPEN_OBJECT Debug.Assert(openProps == null, "These should not be set in this path"); #endif if (property.MimeTypeProperty != null) { // an implication of this 3rd-arg-null is that mime type properties cannot be open props #if ASTORIA_OPEN_OBJECT property.MimeTypeProperty.SetValue(this.entity, mimeType, null, ref openProps, false); Debug.Assert(openProps == null, "These should not be set in this path"); #else property.MimeTypeProperty.SetValue(this.entity, mimeType, null, false); #endif } return MaterializeAtom.CreateWrapper(context, results); } finally { context.ApplyingChanges = merging; } }
/// <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> /// Creates and returns an <see cref="ODataValue"/> for the given primitive value. /// </summary> /// <param name="property">The property being converted.</param> /// <param name="propertyValue">The property value to convert..</param> /// <returns>An ODataValue representing the given value.</returns> private static ODataValue CreateODataPrimitivePropertyValue(ClientPropertyAnnotation property, object propertyValue) { if (propertyValue == null) { return new ODataNullValue(); } propertyValue = ConvertPrimitiveValueToRecognizedODataType(propertyValue, property.PropertyType); return new ODataPrimitiveValue(propertyValue); }
/// <summary> /// Sets the given instance of <paramref name="annotation"/> to the given instance of <paramref name="edmProperty"/>. /// </summary> /// <param name="edmProperty">IEdmProperty instance to set the annotation.</param> /// <param name="annotation">Annotation instance to set.</param> internal static void SetClientPropertyAnnotation(this IEdmProperty edmProperty, ClientPropertyAnnotation annotation) { Debug.Assert(edmProperty != null, "edmProperty != null"); Debug.Assert(annotation != null, "annotation != null"); annotation.Model.SetAnnotationValue <ClientPropertyAnnotation>(edmProperty, annotation); }
/// <summary> /// Returns the value of the complex property. /// </summary> /// <param name="property">Property which contains name, type, is key (if false and null value, will throw).</param> /// <param name="propertyValue">property value</param> /// <param name="visitedComplexTypeObjects">List of instances of complex types encountered in the hierarchy. Used to detect cycles.</param> /// <returns>An instance of ODataComplexValue containing the value of the properties of the given complex type.</returns> private ODataComplexValue CreateODataComplexPropertyValue(ClientPropertyAnnotation property, object propertyValue, HashSet<object> visitedComplexTypeObjects) { Debug.Assert(propertyValue != null || !property.IsPrimitiveOrEnumOrComplexCollection, "Collection items must not be null"); Type propertyType = property.IsPrimitiveOrEnumOrComplexCollection ? property.PrimitiveOrComplexCollectionItemType : property.PropertyType; if (propertyValue != null && propertyType != propertyValue.GetType()) { Debug.Assert(propertyType.IsAssignableFrom(propertyValue.GetType()), "Type from value should equals to or derived from property type from model."); propertyType = propertyValue.GetType(); } return this.CreateODataComplexValue(propertyType, propertyValue, property.PropertyName, property.IsPrimitiveOrEnumOrComplexCollection, visitedComplexTypeObjects); }
/// <summary> /// Tries to convert the given value into an instance of <see cref="ODataValue"/>. /// </summary> /// <param name="property">The property being converted.</param> /// <param name="propertyValue">The property value to convert..</param> /// <param name="serverTypeName">The server type name of the entity whose properties are being populated.</param> /// <param name="visitedComplexTypeObjects">Set of instances of complex types encountered in the hierarchy. Used to detect cycles.</param> /// <param name="odataValue">The odata value if one was created.</param> /// <returns>Whether or not the value was converted.</returns> private bool TryConvertPropertyValue(ClientPropertyAnnotation property, object propertyValue, string serverTypeName, HashSet<object> visitedComplexTypeObjects, out ODataValue odataValue) { if (property.IsKnownType) { odataValue = CreateODataPrimitivePropertyValue(property, propertyValue); return true; } if (property.IsEnumType) { string enumValue; if (propertyValue == null) { enumValue = null; } else { enumValue = ClientTypeUtil.GetEnumValuesString(propertyValue.ToString(), property.PropertyType); } string typeNameInMetadata = this.requestInfo.ResolveNameFromType(property.PropertyType); odataValue = new ODataEnumValue(enumValue, typeNameInMetadata); return true; } if (property.IsPrimitiveOrEnumOrComplexCollection) { odataValue = this.CreateODataCollectionPropertyValue(property, propertyValue, serverTypeName, visitedComplexTypeObjects); return true; } if (!property.IsEntityCollection && !ClientTypeUtil.TypeIsEntity(property.PropertyType, this.requestInfo.Model)) { odataValue = this.CreateODataComplexPropertyValue(property, propertyValue, visitedComplexTypeObjects); return true; } odataValue = null; return false; }
/// <summary> /// Returns the value of the collection property. /// </summary> /// <param name="property">Collection property details. Must not be null.</param> /// <param name="propertyValue">Collection instance.</param> /// <param name="serverTypeName">The server type name of the entity whose properties are being populated.</param> /// <param name="visitedComplexTypeObjects">List of instances of complex types encountered in the hierarchy. Used to detect cycles.</param> /// <returns>An instance of ODataCollectionValue representing the value of the property.</returns> private ODataCollectionValue CreateODataCollectionPropertyValue(ClientPropertyAnnotation property, object propertyValue, string serverTypeName, HashSet<object> visitedComplexTypeObjects) { Debug.Assert(property != null, "property != null"); Debug.Assert(property.IsPrimitiveOrEnumOrComplexCollection, "This method is supposed to be used only for writing collections"); Debug.Assert(property.PropertyName != null, "property.PropertyName != null"); bool isDynamic = this.requestInfo.TypeResolver.ShouldWriteClientTypeForOpenServerProperty(property.EdmProperty, serverTypeName); return this.CreateODataCollection(property.PrimitiveOrComplexCollectionItemType, property.PropertyName, propertyValue, visitedComplexTypeObjects, isDynamic); }
/// <summary> /// Sets the given instance of <paramref name="annotation"/> to the given instance of <paramref name="edmProperty"/>. /// </summary> /// <param name="edmProperty">IEdmProperty instance to set the annotation.</param> /// <param name="annotation">Annotation instance to set.</param> internal static void SetClientPropertyAnnotation(this IEdmProperty edmProperty, ClientPropertyAnnotation annotation) { Debug.Assert(edmProperty != null, "edmProperty != null"); Debug.Assert(annotation != null, "annotation != null"); annotation.Model.SetAnnotationValue<ClientPropertyAnnotation>(edmProperty, annotation); }
/// <summary> /// Builds an edm property value from the given annotation. /// </summary> /// <param name="propertyAnnotation">The property annotation.</param> /// <returns>The property value</returns> private IEdmPropertyValue BuildEdmPropertyValue(ClientPropertyAnnotation propertyAnnotation) { var propertyValue = propertyAnnotation.GetValue(this.structuredValue); var edmValue = this.ConvertToEdmValue(propertyValue, propertyAnnotation.EdmProperty.Type); return new EdmPropertyValue(propertyAnnotation.EdmProperty.Name, edmValue); }
/// <summary> /// Constructs a new instance. /// </summary> /// <param name="requestInfo">Information about the request.</param> /// <param name="mergeOption">Merge option.</param> /// <param name="entityDescriptor">Entity whose property is being loaded.</param> /// <param name="property">Property which is being loaded.</param> internal LoadPropertyResponseInfo( RequestInfo requestInfo, MergeOption mergeOption, EntityDescriptor entityDescriptor, ClientPropertyAnnotation property) : base(requestInfo, mergeOption) { this.EntityDescriptor = entityDescriptor; this.Property = property; }
/// <summary> /// Adds a type annotation to the value if it is primitive and not defined on the server. /// </summary> /// <param name="serverTypeName">The server type name of the entity whose properties are being populated.</param> /// <param name="property">The current property.</param> /// <param name="odataValue">The already converted value of the property.</param> private void AddTypeAnnotationNotDeclaredOnServer(string serverTypeName, ClientPropertyAnnotation property, ODataValue odataValue) { Debug.Assert(property != null, "property != null"); Debug.Assert(property.EdmProperty != null, "property.EdmProperty != null"); var primitiveValue = odataValue as ODataPrimitiveValue; if (primitiveValue == null) { return; } if (!this.requestInfo.Format.UsingAtom && this.requestInfo.TypeResolver.ShouldWriteClientTypeForOpenServerProperty(property.EdmProperty, serverTypeName) && !JsonSharedUtils.ValueTypeMatchesJsonType(primitiveValue, property.EdmProperty.Type.AsPrimitive())) { // DEVNOTE: it is safe to use the property type name for primitive types because they do not generally support inheritance, // and spatial values always contain their specific type inside the GeoJSON/GML representation. primitiveValue.SetAnnotation(new SerializationTypeNameAnnotation { TypeName = property.EdmProperty.Type.FullName() }); } }
/// <summary> /// Returns the instance of LoadPropertyResponseInfo class, which provides information for LoadProperty response handling. /// </summary> /// <param name="mergeOption">Merge option to use for conflict handling.</param> /// <param name="entityDescriptor">Entity whose property is being loaded.</param> /// <param name="property">Property which is being loaded.</param> /// <returns>Instance of the LoadPropertyResponseInfo class.</returns> internal ResponseInfo GetDeserializationInfoForLoadProperty(MergeOption? mergeOption, EntityDescriptor entityDescriptor, ClientPropertyAnnotation property) { return new LoadPropertyResponseInfo( this, mergeOption.HasValue ? mergeOption.Value : this.Context.MergeOption, entityDescriptor, property); }
/// <summary> /// Try and get the navigation link. If the navigation link is not specified, then its used the self link of the entity and /// appends the property name. /// </summary> /// <param name="baseUriResolver">retrieves the appropriate baseUri for a given entitySet.</param> /// <param name="property">ClientProperty instance representing the navigation property.</param> /// <returns>returns the uri for the given link. If the link is not present, its uses the self link of the current entity and appends the navigation property name.</returns> internal Uri GetNavigationLink(UriResolver baseUriResolver, ClientPropertyAnnotation property) { LinkInfo linkInfo = null; Uri uri = null; if (this.TryGetLinkInfo(property.PropertyName, out linkInfo)) { uri = linkInfo.NavigationLink; } if (uri == null) { Uri relativeUri = UriUtil.CreateUri(property.PropertyName, UriKind.Relative); uri = UriUtil.CreateUri(this.GetResourceUri(baseUriResolver, true /*queryLink*/), relativeUri); } return uri; }
/// <summary> /// Validates the specified <paramref name="property"/> matches /// the parsed <paramref name="atomProperty"/>. /// </summary> /// <param name="property">Property as understood by the type system.</param> /// <param name="atomProperty">Property as parsed.</param> /// <param name="model">Client model.</param> /// <param name="performEntityCheck">whether to do the entity check or not.</param> internal static void ValidatePropertyMatch(ClientPropertyAnnotation property, ODataProperty atomProperty, ClientEdmModel model, bool performEntityCheck) { Debug.Assert(property != null, "property != null"); Debug.Assert(atomProperty != null, "atomProperty != null"); ODataFeed feed = atomProperty.Value as ODataFeed; ODataEntry entry = atomProperty.Value as ODataEntry; if (property.IsKnownType && (feed != null || entry != null)) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkLocalSimple); } Type propertyType = null; if (feed != null) { // We need to fail if the payload states that the property is a navigation collection property // and in the client, the property is not a collection property. if (!property.IsEntityCollection) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkFeedPropertyNotCollection(property.PropertyName)); } propertyType = property.EntityCollectionItemType; } if (entry != null) { if (property.IsEntityCollection) { throw DSClient.Error.InvalidOperation(DSClient.Strings.Deserialize_MismatchAtomLinkEntryPropertyIsCollection(property.PropertyName)); } propertyType = property.PropertyType; } // If the server type and the client type does not match, we need to throw. // This is a breaking change from V1/V2 where we allowed materialization of entities into non-entities and vice versa if (propertyType != null && performEntityCheck) { if (!ClientTypeUtil.TypeIsEntity(propertyType, model)) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(propertyType.ToString())); } } }
/// <summary> /// Tries to get the value of a property and corresponding BindingPropertyInfo or ClientPropertyAnnotation if the property exists /// </summary> /// <param name="source">Source object whose property needs to be read</param> /// <param name="sourceProperty">Name of the source object property</param> /// <param name="model">The client model.</param> /// <param name="bindingPropertyInfo">BindingPropertyInfo corresponding to <paramref name="sourceProperty"/></param> /// <param name="clientProperty">Instance of ClientProperty corresponding to <paramref name="sourceProperty"/></param> /// <param name="propertyValue">Value of the property</param> /// <returns>true if the property exists and the value was read; otherwise false.</returns> internal static bool TryGetPropertyValue(object source, string sourceProperty, ClientEdmModel model, out BindingPropertyInfo bindingPropertyInfo, out ClientPropertyAnnotation clientProperty, out object propertyValue) { Type sourceType = source.GetType(); bindingPropertyInfo = BindingEntityInfo.GetObservableProperties(sourceType, model) .SingleOrDefault(x => x.PropertyInfo.PropertyName == sourceProperty); bool propertyFound = bindingPropertyInfo != null; // bindingPropertyInfo is null for primitive properties. if (!propertyFound) { clientProperty = BindingEntityInfo.GetClientType(sourceType, model) .GetProperty(sourceProperty, true); propertyFound = clientProperty != null; if (!propertyFound) { propertyValue = null; } else { propertyValue = clientProperty.GetValue(source); } } else { clientProperty = null; propertyValue = bindingPropertyInfo.PropertyInfo.GetValue(source); } return propertyFound; }
/// <summary> /// 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> /// Creates an ODataEntry using some properties extracted from an entity operation parameter. /// </summary> /// <param name="clientTypeAnnotation">The client type annotation of the entity.</param> /// <param name="parameterValue">The Clr value of the entity.</param> /// <returns>The ODataEntry created.</returns> private ODataEntry CreateODataEntryFromEntityOperationParameter(ClientTypeAnnotation clientTypeAnnotation, object parameterValue) { ClientPropertyAnnotation[] properties = new ClientPropertyAnnotation[0]; if (sendOption == EntityParameterSendOption.SendOnlySetProperties) { try { var descripter = this.requestInfo.Context.EntityTracker.GetEntityDescriptor(parameterValue); properties = clientTypeAnnotation.PropertiesToSerialize().Where(p => descripter.PropertiesToSerialize.Contains(p.PropertyName)).ToArray(); } catch (InvalidOperationException) { throw Error.InvalidOperation(Strings.Context_MustBeUsedWith("EntityParameterSendOption.SendOnlySetProperties", "DataServiceCollection")); } } return this.propertyConverter.CreateODataEntry(clientTypeAnnotation.ElementType, parameterValue, properties); }
private MaterializeAtom ReadPropertyFromAtom(ClientPropertyAnnotation property) { DataServiceContext context = (DataServiceContext)this.Source; bool merging = context.ApplyingChanges; try { context.ApplyingChanges = true; // store the results so that they can be there in the response body. Type elementType = property.IsEntityCollection ? property.EntityCollectionItemType : property.NullablePropertyType; IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType)); DataServiceQueryContinuation continuation = null; // elementType.ElementType has Nullable stripped away, use nestedType for materializer using (MaterializeAtom materializer = this.GetMaterializer(this.plan)) { Debug.Assert(materializer != null, "materializer != null -- otherwise GetMaterializer() returned null rather than empty"); #if ASTORIA_OPEN_OBJECT object openProperties = null; #endif // when SetLink to null, we cannot get materializer because have no-content response. if (materializer.IsNoContentResponse() && property.GetValue(entity) != null && context.MergeOption != MergeOption.AppendOnly && context.MergeOption != MergeOption.NoTracking) { property.SetValue(this.entity, null, propertyName, false); } else { foreach (object child in materializer) { if (property.IsEntityCollection) { results.Add(child); } else if (property.IsPrimitiveOrEnumOrComplexCollection) { Debug.Assert(property.PropertyType.IsAssignableFrom(child.GetType()), "Created instance for storing collection items has to be compatible with the actual one."); // Collection materialization rules requires to clear the collection if not null or set the property first and then add the collection items object collectionInstance = property.GetValue(this.entity); if (collectionInstance == null) { // type of child has been resolved as per rules for collections so it is the correct type to instantiate collectionInstance = Activator.CreateInstance(child.GetType()); // allowAdd is false - we need to assign instance as the new property value property.SetValue(this.entity, collectionInstance, this.propertyName, false /* allowAdd? */); } else { // Clear existing collection property.ClearBackingICollectionInstance(collectionInstance); } foreach (var collectionItem in (IEnumerable)child) { Debug.Assert(property.PrimitiveOrComplexCollectionItemType.IsAssignableFrom(collectionItem.GetType()), "Type of materialized collection items have to be compatible with the type of collection items in the actual collection property."); property.AddValueToBackingICollectionInstance(collectionInstance, collectionItem); } results.Add(collectionInstance); } else { #if ASTORIA_OPEN_OBJECT property.SetValue(this.entity, child, this.propertyName, ref openProperties, false); #else // it is either primitive type, complex type or 1..1 navigation property so we just allow setting the value but not adding. property.SetValue(this.entity, child, this.propertyName, false); results.Add(child); #endif } } } continuation = materializer.GetContinuation(null); } return MaterializeAtom.CreateWrapper(context, results, continuation); } finally { context.ApplyingChanges = merging; } }