diff --git a/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs b/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs index 61e6b230a..f485cc739 100644 --- a/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs +++ b/src/Microsoft.AspNetCore.OData/Deltas/DeltaOfT.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; using System.Diagnostics.Contracts; @@ -19,6 +20,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.OData.Abstracts; using Microsoft.AspNetCore.OData.Common; +using Microsoft.AspNetCore.OData.Edm; using Microsoft.AspNetCore.OData.Extensions; namespace Microsoft.AspNetCore.OData.Deltas; @@ -817,7 +819,12 @@ private bool TrySetPropertyValueInternal(string name, object value) Type propertyType = cacheHit.Property.PropertyType; if (value != null && !TypeHelper.IsCollection(propertyType) && !propertyType.IsAssignableFrom(value.GetType())) { - return false; + if (!TryConvertPropertyValue(propertyType, value, out object convertedValue)) + { + return false; + } + + value = convertedValue; } cacheHit.SetValue(_instance, value); @@ -825,6 +832,36 @@ private bool TrySetPropertyValueInternal(string name, object value) return true; } + private static bool TryConvertPropertyValue(Type propertyType, object value, out object convertedValue) + { + Debug.Assert(propertyType != null, "Argument propertyType is null"); + Debug.Assert(value != null, "Argument value is null"); + + convertedValue = value; + + Type targetType = Nullable.GetUnderlyingType(propertyType) ?? propertyType; + if (!targetType.IsValueType) + { + return false; + } + + try + { + convertedValue = EdmPrimitiveHelper.ConvertPrimitiveValue(value, propertyType); + } + catch (ValidationException) + { + return false; + } + + if (convertedValue == null) + { + return propertyType.IsNullable(); + } + + return propertyType.IsAssignableFrom(convertedValue.GetType()); + } + private bool TrySetNestedResourceInternal(string name, object deltaNestedResource) { Debug.Assert(name != null, "Argument name is null"); diff --git a/test/Microsoft.AspNetCore.OData.Tests/Deltas/DeltaTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Deltas/DeltaTests.cs index 44f89f5d4..af9845c5e 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Deltas/DeltaTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Deltas/DeltaTests.cs @@ -146,6 +146,40 @@ public void TrySetPropertyValue_ThrowsArgumentNull_name() ExceptionAssert.ThrowsArgumentNull(() => delta.TrySetPropertyValue(null, "Invalid"), "name"); } + [Fact] + public void TrySetPropertyValue_ReturnsTrue_AndTracksChange_WhenInt64ValueSetOnInt32Property() + { + // Arrange - simulates a boxed Int64 value arriving for an Int32 property (e.g. from JSON deserialization) + Delta delta = new Delta(); + long int64Value = 42L; + + // Act + bool result = delta.TrySetPropertyValue("IntProperty", int64Value); + + // Assert + Assert.True(result, "TrySetPropertyValue should return true when an Int64 value can be coerced to the Int32 property type."); + Assert.Contains("IntProperty", delta.GetChangedPropertyNames()); + delta.TryGetPropertyValue("IntProperty", out object retrievedValue); + Assert.Equal(42, retrievedValue); + } + + [Fact] + public void TrySetPropertyValue_ReturnsTrue_AndTracksChange_WhenInt64ValueSetOnNullableInt32Property() + { + // Arrange - simulates a boxed Int64 value arriving for a nullable Int32 property + Delta delta = new Delta(); + long int64Value = 42L; + + // Act + bool result = delta.TrySetPropertyValue("NullableIntProperty", int64Value); + + // Assert + Assert.True(result, "TrySetPropertyValue should return true when an Int64 value can be coerced to the nullable Int32 property type."); + Assert.Contains("NullableIntProperty", delta.GetChangedPropertyNames()); + delta.TryGetPropertyValue("NullableIntProperty", out object retrievedValue); + Assert.Equal((int?)42, retrievedValue); + } + [Fact] public void TrySetPropertyValue_ThrowsInvalidOperation_IfDynamicContainerWithoutSetter() {