From c1c1a27c83f09e4a93c63f6d3c1880ed63cb29a5 Mon Sep 17 00:00:00 2001 From: Rami Date: Mon, 11 May 2026 20:19:29 -0700 Subject: [PATCH] Support type coercion in Delta.TrySetPropertyValue Add TryConvertPropertyValue to enable value type coercion (e.g., Int64 to Int32) when setting properties in Delta. Update TrySetPropertyValueInternal to use this logic. Add tests to verify correct handling and change tracking for coerced values. Fixes #1572 --- .../Deltas/DeltaOfT.cs | 39 ++++++++++++++++++- .../Deltas/DeltaTests.cs | 34 ++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) 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() {