diff --git a/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs b/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs index 4cfa6b89f..ccd901a30 100644 --- a/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs +++ b/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs @@ -144,7 +144,7 @@ public override bool TrySetPropertyValue(string name, object value) } } - if (value is IDelta) + if (value is IDelta || value is IDeltaSet) { return TrySetNestedResourceInternal(name, value); } @@ -225,6 +225,14 @@ internal bool TryGetNestedPropertyValue(string name, out object value) object deltaNestedResource = _deltaNestedResources[name]; Contract.Assert(deltaNestedResource != null, "deltaNestedResource != null"); + + //If DeltaSet collection, we are handling delta collections so the value will be that itself and no need to get instance value + if (deltaNestedResource is IDeltaSet) + { + value = deltaNestedResource; + return true; + } + Contract.Assert(DeltaHelper.IsDeltaOfT(deltaNestedResource.GetType())); value = deltaNestedResource; @@ -339,6 +347,15 @@ public void CopyChangedValues(T original) { // Patch for each nested resource changed under this T. dynamic deltaNestedResource = _deltaNestedResources[nestedResourceName]; + + if (deltaNestedResource is IDeltaSet) + { + // TODO: That's the bulk insert OData Path handler feature, + // See the comments in https://github.com/OData/AspNetCoreOData/issues/748 + // So far, Let's skip DeltaSet and figure it out later. + continue; + } + dynamic originalNestedResource = null; if (!TryGetPropertyRef(original, nestedResourceName, out originalNestedResource)) { @@ -705,11 +722,14 @@ private bool TrySetNestedResourceInternal(string name, object deltaNestedResourc return false; } - PropertyAccessor cacheHit = _allProperties[name]; - // Get the Delta<{NestedResourceType}>._instance using Reflection. - FieldInfo field = deltaNestedResource.GetType().GetField("_instance", BindingFlags.NonPublic | BindingFlags.Instance); - Contract.Assert(field != null, "field != null"); - cacheHit.SetValue(_instance, field.GetValue(deltaNestedResource)); + if (!(deltaNestedResource is IDeltaSet)) + { + PropertyAccessor cacheHit = _allProperties[name]; + // Get the Delta<{NestedResourceType}>._instance using Reflection. + FieldInfo field = deltaNestedResource.GetType().GetField("_instance", BindingFlags.NonPublic | BindingFlags.Instance); + Contract.Assert(field != null, "field != null"); + cacheHit.SetValue(_instance, field.GetValue(deltaNestedResource)); + } // Add the nested resource in the hierarchy. // Note: We shouldn't add the structural properties to the _changedProperties, which diff --git a/src/Microsoft.AspNetCore.OData/Edm/EdmModelExtensions.cs b/src/Microsoft.AspNetCore.OData/Edm/EdmModelExtensions.cs index 281c41480..15c98a306 100644 --- a/src/Microsoft.AspNetCore.OData/Edm/EdmModelExtensions.cs +++ b/src/Microsoft.AspNetCore.OData/Edm/EdmModelExtensions.cs @@ -18,6 +18,38 @@ namespace Microsoft.AspNetCore.OData.Edm { internal static class EdmModelExtensions { + /// + /// Get all property names for the given structured type. + /// + /// The Edm model. + /// The given structured type. + /// All property names. + public static ICollection GetAllProperties(this IEdmModel model, IEdmStructuredType structuredType) + { + if (model == null) + { + throw Error.ArgumentNull(nameof(model)); + } + + if (structuredType == null) + { + throw Error.ArgumentNull(nameof(structuredType)); + } + + IList allProperties = new List(); + foreach (var property in structuredType.StructuralProperties()) + { + allProperties.Add(model.GetClrPropertyName(property)); + } + + foreach (var property in structuredType.NavigationProperties()) + { + allProperties.Add(model.GetClrPropertyName(property)); + } + + return allProperties; + } + /// /// Resolve the alternate key properties. /// diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs index a020f9152..c2832715b 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs @@ -115,6 +115,13 @@ internal static void SetCollectionProperty(object resource, string propertyName, { if (value != null) { + // If the setting value is a delta set, we don't need to create a new collection, just use it. + if (value is IDeltaSet set) + { + SetProperty(resource, propertyName, set); + return; + } + IEnumerable collection = value as IEnumerable; Contract.Assert(collection != null, "SetCollectionProperty is always passed the result of ODataFeedDeserializer or ODataCollectionDeserializer"); diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs index ce0cc6b6f..fe7e96471 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataResourceDeserializer.cs @@ -263,20 +263,19 @@ public virtual object CreateResourceInstance(IEdmStructuredTypeReference structu if (readContext.IsDeltaOfT || readContext.IsDeltaDeleted) { - IEnumerable structuralProperties = structuredType.StructuralProperties() - .Select(edmProperty => model.GetClrPropertyName(edmProperty)); + IEnumerable updatablePoperties = model.GetAllProperties(structuredType.StructuredDefinition()); if (structuredType.IsOpen()) { PropertyInfo dynamicDictionaryPropertyInfo = model.GetDynamicPropertyDictionary( structuredType.StructuredDefinition()); - return Activator.CreateInstance(readContext.ResourceType, clrType, structuralProperties, + return Activator.CreateInstance(readContext.ResourceType, clrType, updatablePoperties, dynamicDictionaryPropertyInfo); } else { - return Activator.CreateInstance(readContext.ResourceType, clrType, structuralProperties); + return Activator.CreateInstance(readContext.ResourceType, clrType, updatablePoperties); } } else diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml index aeeedf9eb..52d40482c 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml @@ -2221,6 +2221,14 @@ The Edm entity type. Alternate Keys of this type. + + + Get all property names for the given structured type. + + The Edm model. + The given structured type. + All property names. + Resolve the alternate key properties. diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs index 47bf3b26b..79c826906 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/ODataResourceDeserializerTests.cs @@ -715,7 +715,7 @@ public void CreateResourceInstance_CreatesDeltaWith_ExpectedUpdatableProperties( Model = _readContext.Model, ResourceType = typeof(Delta) }; - var structuralProperties = _productEdmType.StructuralProperties().Select(p => p.Name); + var structuralProperties = _readContext.Model.GetAllProperties(_productEdmType.StructuredDefinition()); // Act Delta resource = deserializer.CreateResourceInstance(_productEdmType, readContext) as Delta;