diff --git a/v7.x/DeepUpdateTests/DeepUpdateTests/Controllers/HistoryController.cs b/v7.x/DeepUpdateTests/DeepUpdateTests/Controllers/HistoryController.cs new file mode 100644 index 0000000..a6704b7 --- /dev/null +++ b/v7.x/DeepUpdateTests/DeepUpdateTests/Controllers/HistoryController.cs @@ -0,0 +1,81 @@ +using DeepUpdateTests.Models; +using Microsoft.AspNet.OData; +using Microsoft.AspNet.OData.Routing; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OData.Edm; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Linq; + +namespace DeepUpdateTests.Controllers +{ + public class HistoryController : ODataController + { + // In-memory static list for demonstration. Replace with persistent storage as needed. + private static readonly IList _history; + private static readonly IEdmModel _edmModel = EdmModelBuilder.GetEdmModel(); + private static readonly IEdmEntityType _customerEdmType = _edmModel.SchemaElements + .OfType() + .First(e => e.Name == nameof(Customer)); + + static HistoryController() + { + var delta = GetDeltaObject(); + + _history = new List + { + new History + { + Id = 1, + CustomerDelta = DeltaObjectSerializer.AsSerializedDeltaObject(delta) + } + }; + } + + [EnableQuery] + public IActionResult Get() + { + return Ok(_history); + } + + private static EdmDeltaEntityObject GetDeltaObject() + { + IEdmModel model = EdmModelBuilder.GetEdmModel(); + var customerType = model.SchemaElements.OfType().First(c => c.Name == "Customer"); + + var changedCustomerObject = new EdmDeltaEntityObject(customerType); + changedCustomerObject.TrySetPropertyValue("Id", 1); + changedCustomerObject.TrySetPropertyValue("Name", "customer old name"); + + var orderType = model.SchemaElements.OfType().First(c => c.Name == "Order"); + + var changedOrders = new EdmChangedObjectCollection(orderType); + + var changeOrder1Object = new EdmDeltaEntityObject(orderType); + changeOrder1Object.TrySetPropertyValue("Id", 1); + changedOrders.Add(changeOrder1Object); + + var changedOrder2Object = new EdmDeltaDeletedEntityObject(orderType); + changedOrder2Object.Id = "http://tempuri.org/Orders(2)"; + changedOrder2Object.TrySetPropertyValue("Id", 2); + changedOrders.Add(changedOrder2Object); + + var changedOrderObject = new EdmDeltaEntityObject(orderType); + changedOrderObject.TrySetPropertyValue("Id", 1); + changedOrderObject.TrySetPropertyValue("Amount", 8); + + // Have nested + var listItemType = model.SchemaElements.OfType().First(c => c.Name == "ListItem"); + var changedLists = new EdmChangedObjectCollection(listItemType); + var changeList1Object = new EdmDeltaEntityObject(listItemType); + changeList1Object.TrySetPropertyValue("Id", 1); + changedLists.Add(changeList1Object); + + changedOrderObject.TrySetPropertyValue("Items", changedLists); + + changedOrders.Add(changedOrderObject); + changedCustomerObject.TrySetPropertyValue("Orders", changedOrders); + return changedCustomerObject; + } + } +} \ No newline at end of file diff --git a/v7.x/DeepUpdateTests/DeepUpdateTests/DeepUpdateTests.http b/v7.x/DeepUpdateTests/DeepUpdateTests/DeepUpdateTests.http index b8c1691..050b036 100644 --- a/v7.x/DeepUpdateTests/DeepUpdateTests/DeepUpdateTests.http +++ b/v7.x/DeepUpdateTests/DeepUpdateTests/DeepUpdateTests.http @@ -10,6 +10,12 @@ GET {{DeepUpdateTests_HostAddress}}/weatherforecast ### GET {{DeepUpdateTests_HostAddress}}/odata/Customers +### +GET {{DeepUpdateTests_HostAddress}}/odata/History +OData-Version: 4.01 +Accept: application/json + + ### GET {{DeepUpdateTests_HostAddress}}/odata/Customers(1)/orders?$expand=list&$deltaToken=abcd OData-Version: 4.01 diff --git a/v7.x/DeepUpdateTests/DeepUpdateTests/Models/DeltaObjectSerializer.cs b/v7.x/DeepUpdateTests/DeepUpdateTests/Models/DeltaObjectSerializer.cs new file mode 100644 index 0000000..1d83f54 --- /dev/null +++ b/v7.x/DeepUpdateTests/DeepUpdateTests/Models/DeltaObjectSerializer.cs @@ -0,0 +1,116 @@ +//------------------------------------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//------------------------------------------------------------------------------------------------- + +using Microsoft.AspNet.OData; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Reflection; +namespace DeepUpdateTests.Models +{ + + /// + /// Provides serialization capabilities for EdmDelta objects + /// + public static class DeltaObjectSerializer + { + /// + /// Serializes an EdmDeltaEntityObject to a JSON string + /// + /// The delta object to serialize + /// A JSON string representation of the delta object + public static string AsSerializedDeltaObject(EdmDeltaEntityObject deltaObject) + { + if (deltaObject == null) + { + throw new ArgumentNullException(nameof(deltaObject)); + } + + var jsonObject = new JObject(); + SerializeDeltaObject(deltaObject, jsonObject); + return jsonObject.ToString(Formatting.None); + } + + /// + /// Serializes an EdmDeltaComplexObject to a JSON string + /// + /// The delta complex object to serialize + /// A JSON string representation of the complex object + public static string AsSerializedDeltaObject(EdmDeltaComplexObject deltaObject) + { + if (deltaObject == null) + { + throw new ArgumentNullException(nameof(deltaObject)); + } + + var jsonObject = new JObject(); + SerializeDeltaObject(deltaObject, jsonObject); + return jsonObject.ToString(Formatting.None); + } + + private static void SerializeDeltaObject(IDelta deltaObject, JObject jsonObject) + { + // Serialize properties + foreach (var propertyName in deltaObject.GetChangedPropertyNames()) + { + object propertyValue; + if (deltaObject.TryGetPropertyValue(propertyName, out propertyValue)) + { + JToken valueToken = SerializePropertyValue(propertyValue); + jsonObject.Add(propertyName, valueToken); + } + } + } + + private static JToken SerializePropertyValue(object value) + { + if (value == null) + { + return JValue.CreateNull(); + } + + // Handle any IDelta (both EdmDeltaEntityObject and EdmDeltaComplexObject) recursively + if (value is IDelta delta) + { + var deltaJson = new JObject(); + SerializeDeltaObject(delta, deltaJson); + return deltaJson; + } + + // Handle collections + if (value is IEnumerable enumerable && !(value is string)) + { + var arrayJson = new JArray(); + foreach (var item in enumerable) + { + arrayJson.Add(SerializePropertyValue(item)); + } + + return arrayJson; + } + + // Handle primitive types with fallback + return ConvertPrimitiveWithFallback(value); + } + + private static JToken ConvertPrimitiveWithFallback(object value) + { + if (value == null) + { + return JValue.CreateNull(); + } + + switch (value) + { + case Guid g: + return new JValue(g.ToString()); + default: + return JToken.FromObject(value); + } + } + } +} \ No newline at end of file diff --git a/v7.x/DeepUpdateTests/DeepUpdateTests/Models/EdmModelBuilder.cs b/v7.x/DeepUpdateTests/DeepUpdateTests/Models/EdmModelBuilder.cs index d4527f3..31f9f87 100644 --- a/v7.x/DeepUpdateTests/DeepUpdateTests/Models/EdmModelBuilder.cs +++ b/v7.x/DeepUpdateTests/DeepUpdateTests/Models/EdmModelBuilder.cs @@ -13,6 +13,7 @@ public static IEdmModel GetEdmModel() { var builder = new ODataConventionModelBuilder(); builder.EntitySet("Customers"); + builder.EntitySet("History"); builder.EntitySet("Orders"); builder.EntityType(); @@ -32,6 +33,13 @@ public class Customer public virtual Order[] Orders { get; set; } } + public class History + { + public int Id { get; set; } + + public string CustomerDelta { get; set; } + } + public class Order { public int Id { get; set; }