Skip to content

Commit

Permalink
Fixes #1064, enable to write dynamic properties for changed/delta obj…
Browse files Browse the repository at this point in the history
…ect (#1065)

* Fixes #1064, enable to write dynamic properties for changed/delta objects

* Change the copy right
  • Loading branch information
xuzhg authored Oct 11, 2023
1 parent 89a1848 commit 0ccf968
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ private async Task WriteDeltaResourceAsync(object graph, ODataWriter writer, ODa
{
await writer.WriteStartAsync(resource).ConfigureAwait(false);
await WriteDeltaComplexPropertiesAsync(selectExpandNode, resourceContext, writer).ConfigureAwait(false);
await WriteDeltaNavigationPropertiesAsync(selectExpandNode, resourceContext, writer);
await WriteDynamicComplexPropertiesAsync(resourceContext, writer).ConfigureAwait(false);
await WriteDeltaNavigationPropertiesAsync(selectExpandNode, resourceContext, writer).ConfigureAwait(false);
await writer.WriteEndAsync().ConfigureAwait(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//-----------------------------------------------------------------------------
// <copyright file="DeltaTokenControllers.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Extensions;
using Microsoft.AspNetCore.OData.Formatter.Value;
using Microsoft.AspNetCore.OData.Routing.Controllers;
using Microsoft.OData;
using Microsoft.OData.Edm;

namespace Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken
{
public class TestCustomersController : ODataController
{
public IActionResult Get()
{
IEdmModel model = Request.GetModel();

IEdmEntityType customerType = model.FindDeclaredType("Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestCustomer") as IEdmEntityType;
IEdmEntityType customerWithAddressType = model.FindDeclaredType("Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestCustomerWithAddress") as IEdmEntityType;
IEdmComplexType addressType = model.FindDeclaredType("Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestAddress") as IEdmComplexType;
IEdmEntityType orderType = model.FindDeclaredType("Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestOrder") as IEdmEntityType;
IEdmEntitySet ordersSet = model.FindDeclaredEntitySet("TestOrders") as IEdmEntitySet;
EdmChangedObjectCollection changedObjects = new EdmChangedObjectCollection(customerType);

EdmDeltaComplexObject a = new EdmDeltaComplexObject(addressType);
a.TrySetPropertyValue("State", "State");
a.TrySetPropertyValue("ZipCode", null);

EdmDeltaResourceObject changedEntity = new EdmDeltaResourceObject(customerWithAddressType);
changedEntity.TrySetPropertyValue("Id", 1);
changedEntity.TrySetPropertyValue("Name", "Name");
changedEntity.TrySetPropertyValue("Address", a);
changedEntity.TrySetPropertyValue("PhoneNumbers", new List<string> { "123-4567", "765-4321" });
changedEntity.TrySetPropertyValue("OpenProperty", 10);
changedEntity.TrySetPropertyValue("NullOpenProperty", null);
changedObjects.Add(changedEntity);

EdmComplexObjectCollection places = new EdmComplexObjectCollection(new EdmCollectionTypeReference(new EdmCollectionType(new EdmComplexTypeReference(addressType, true))));
EdmDeltaComplexObject b = new EdmDeltaComplexObject(addressType);
b.TrySetPropertyValue("City", "City2");
b.TrySetPropertyValue("State", "State2");
b.TrySetPropertyValue("ZipCode", 12345);
b.TrySetPropertyValue("OpenProperty", 10);
b.TrySetPropertyValue("NullOpenProperty", null);
places.Add(a);
places.Add(b);

var newCustomer = new EdmDeltaResourceObject(customerType);
newCustomer.TrySetPropertyValue("Id", 10);
newCustomer.TrySetPropertyValue("Name", "NewCustomer");
newCustomer.TrySetPropertyValue("FavoritePlaces", places);
changedObjects.Add(newCustomer);

var newOrder = new EdmDeltaResourceObject(orderType);
newOrder.NavigationSource = ordersSet;
newOrder.TrySetPropertyValue("Id", 27);
newOrder.TrySetPropertyValue("Amount", 100);
changedObjects.Add(newOrder);

var deletedCustomer = new EdmDeltaDeletedResourceObject(customerType);
deletedCustomer.Id = new Uri("7", UriKind.RelativeOrAbsolute);
deletedCustomer.Reason = DeltaDeletedEntryReason.Changed;
changedObjects.Add(deletedCustomer);

var deletedOrder = new EdmDeltaDeletedResourceObject(orderType);
deletedOrder.NavigationSource = ordersSet;
deletedOrder.Id = new Uri("12", UriKind.RelativeOrAbsolute);
deletedOrder.Reason = DeltaDeletedEntryReason.Deleted;
changedObjects.Add(deletedOrder);

var deletedLink = new EdmDeltaDeletedLink(customerType);
deletedLink.Source = new Uri("http://localhost/odata/TestCustomers(1)");
deletedLink.Target = new Uri("http://localhost/odata/TestOrders(12)");
deletedLink.Relationship = "Orders";
changedObjects.Add(deletedLink);

var addedLink = new EdmDeltaLink(customerType);
addedLink.Source = new Uri("http://localhost/odata/TestCustomers(10)");
addedLink.Target = new Uri("http://localhost/odata/TestOrders(27)");
addedLink.Relationship = "Orders";
changedObjects.Add(addedLink);

return Ok(changedObjects);
}
}

public class TestOrdersController : ODataController
{
public IActionResult Get()
{
IEdmModel model = Request.GetModel();
IEdmComplexType addressType = model.FindDeclaredType("Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestAddress") as IEdmComplexType;
IEdmEntityType orderType = model.FindDeclaredType("Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestOrder") as IEdmEntityType;
EdmChangedObjectCollection changedObjects = new EdmChangedObjectCollection(orderType);

EdmDeltaComplexObject sampleList = new EdmDeltaComplexObject(addressType);
sampleList.TrySetPropertyValue("State", "sample state");
sampleList.TrySetPropertyValue("ZipCode", 9);
sampleList.TrySetPropertyValue("title", "sample title"); // primitive dynamic

EdmDeltaComplexObject location = new EdmDeltaComplexObject(addressType);
location.TrySetPropertyValue("State", "State");
location.TrySetPropertyValue("ZipCode", null);
location.TrySetPropertyValue("OpenProperty", 10); // primitive dynamic
location.TrySetPropertyValue("key-samplelist", sampleList); // complex dynamic

EdmDeltaResourceObject changedOrder = new EdmDeltaResourceObject(orderType);
changedOrder.TrySetPropertyValue("Id", 1);
changedOrder.TrySetPropertyValue("Amount", 42);
changedOrder.TrySetPropertyValue("Location", location);
changedObjects.Add(changedOrder);

return Ok(changedObjects);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//-----------------------------------------------------------------------------
// <copyright file="DeltaTokenDataModel.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System.Collections.Generic;

namespace Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken
{
public class TestCustomer
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public virtual IList<string> PhoneNumbers { get; set; }
public virtual IList<TestOrder> Orders { get; set; }
public virtual IList<TestAddress> FavoritePlaces { get; set; }
public IDictionary<string, object> DynamicProperties { get; set; }
}

public class TestCustomerWithAddress : TestCustomer
{
public virtual TestAddress Address { get; set; }
}

public class TestOrder
{
public int Id { get; set; }
public int Amount { get; set; }

public TestAddress Location { get; set; }
}

public class TestAddress
{
public string State { get; set; }
public string City { get; set; }
public int? ZipCode { get; set; }
public IDictionary<string, object> DynamicProperties { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//-----------------------------------------------------------------------------
// <copyright file="DeltaTokenQueryTests.cs" company=".NET Foundation">
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.OData.E2E.Tests.Extensions;
using Microsoft.AspNetCore.OData.TestCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using Newtonsoft.Json.Linq;
using Xunit;

namespace Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken
{
public class DeltaTokenQueryTests : WebApiTestBase<DeltaTokenQueryTests>
{
public DeltaTokenQueryTests(WebApiTestFixture<DeltaTokenQueryTests> fixture)
: base(fixture)
{
}

protected static void UpdateConfigureServices(IServiceCollection services)
{
services.ConfigureControllers(typeof(TestCustomersController), typeof(TestOrdersController));
services.AddControllers().AddOData(opt => opt.AddRouteComponents("odata", GetEdmModel()).Count().Filter().OrderBy().Expand().SetMaxTop(null).Select());
}

private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<TestCustomer>("TestCustomers");
builder.EntitySet<TestOrder>("TestOrders");
return builder.GetEdmModel();
}

[Fact]
public async Task DeltaVerifyReslt()
{
HttpRequestMessage get = new HttpRequestMessage(HttpMethod.Get, "odata/TestCustomers?$deltaToken=abc");
get.Headers.Add("Accept", "application/json;odata.metadata=minimal");
get.Headers.Add("OData-Version", "4.01");
HttpClient client = CreateClient();
HttpResponseMessage response = await client.SendAsync(get);
Assert.True(response.IsSuccessStatusCode);
dynamic results = await response.Content.ReadAsObject<JObject>();

Assert.True(results.value.Count == 7, "There should be 7 entries in the response");

var changeEntity = results.value[0];
Assert.True(((JToken)changeEntity).Count() == 9, "The changed customer should have 6 properties plus type written. But now it contains non-changed properties, it's regression bug?");
string changeEntityType = changeEntity["@type"].Value as string;
Assert.True(changeEntityType != null, "The changed customer should have type written");
Assert.True(changeEntityType.Contains("#Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestCustomerWithAddress"), "The changed order should be a TestCustomerWithAddress");
Assert.True(changeEntity.Id.Value == 1, "The ID Of changed customer should be 1.");
Assert.True(changeEntity.OpenProperty.Value == 10, "The OpenProperty property of changed customer should be 10.");
Assert.True(changeEntity.NullOpenProperty.Value == null, "The NullOpenProperty property of changed customer should be null.");
Assert.True(changeEntity.Name.Value == "Name", "The Name of changed customer should be 'Name'");
Assert.True(((JToken)changeEntity.Address).Count() == 3, "The changed entity's Address should have 2 properties written. But now it contains non-changed properties, it's regression bug?");
Assert.True(changeEntity.Address.State.Value == "State", "The changed customer's Address.State should be 'State'.");
Assert.True(changeEntity.Address.ZipCode.Value == (int?)null, "The changed customer's Address.ZipCode should be null.");

var phoneNumbers = changeEntity.PhoneNumbers;
Assert.True(((JToken)phoneNumbers).Count() == 2, "The changed customer should have 2 phone numbers");
Assert.True(phoneNumbers[0].Value == "123-4567", "The first phone number should be '123-4567'");
Assert.True(phoneNumbers[1].Value == "765-4321", "The second phone number should be '765-4321'");

var newCustomer = results.value[1];
Assert.True(((JToken)newCustomer).Count() == 5, "The new customer should have 3 properties written, But now it contains 2 non-changed properties, it's regression bug?");
Assert.True(newCustomer.Id.Value == 10, "The ID of the new customer should be 10");
Assert.True(newCustomer.Name.Value == "NewCustomer", "The name of the new customer should be 'NewCustomer'");

var places = newCustomer.FavoritePlaces;
Assert.True(((JToken)places).Count() == 2, "The new customer should have 2 favorite places");

var place1 = places[0];
Assert.True(((JToken)place1).Count() == 3, "The first favorite place should have 2 properties written.But now it contains non-changed properties, it's regression bug?");
Assert.True(place1.State.Value == "State", "The first favorite place's state should be 'State'.");
Assert.True(place1.ZipCode.Value == (int?)null, "The first favorite place's Address.ZipCode should be null.");

var place2 = places[1];
Assert.True(((JToken)place2).Count() == 5, "The second favorite place should have 5 properties written.");
Assert.True(place2.City.Value == "City2", "The second favorite place's Address.City should be 'City2'.");
Assert.True(place2.State.Value == "State2", "The second favorite place's Address.State should be 'State2'.");
Assert.True(place2.ZipCode.Value == 12345, "The second favorite place's Address.ZipCode should be 12345.");
Assert.True(place2.OpenProperty.Value == 10, "The second favorite place's Address.OpenProperty should be 10.");
Assert.True(place2.NullOpenProperty.Value == null, "The second favorite place's Address.NullOpenProperty should be null.");

var newOrder = results.value[2];
Assert.True(((JToken)newOrder).Count() == 4, "The new order should have 2 properties plus context written, , But now it contains one non-changed properties, it's regression bug?");
string newOrderContext = newOrder["@context"].Value as string;
Assert.True(newOrderContext != null, "The new order should have a context written");
Assert.True(newOrderContext.Contains("$metadata#TestOrders"), "The new order should come from the TestOrders entity set");
Assert.True(newOrder.Id.Value == 27, "The ID of the new order should be 27");
Assert.True(newOrder.Amount.Value == 100, "The amount of the new order should be 100");

var deletedEntity = results.value[3];
Assert.True(deletedEntity["@id"].Value == "7", "The ID of the deleted customer should be 7");
Assert.True(deletedEntity["@removed"].reason.Value == "changed", "The reason for the deleted customer should be 'changed'");

var deletedOrder = results.value[4];
string deletedOrderContext = deletedOrder["@context"].Value as string;
Assert.True(deletedOrderContext != null, "The deleted order should have a context written");
Assert.True(deletedOrderContext.Contains("$metadata#TestOrders"), "The deleted order should come from the TestOrders entity set");
Assert.True(deletedOrder["@id"].Value == "12", "The ID of the deleted order should be 12");
Assert.True(deletedOrder["@removed"].reason.Value == "deleted", "The reason for the deleted order should be 'deleted'");

var deletedLink = results.value[5];
Assert.True(deletedLink.source.Value == "http://localhost/odata/TestCustomers(1)", "The source of the deleted link should be 'http://localhost/odata/TestCustomers(1)'");
Assert.True(deletedLink.target.Value == "http://localhost/odata/TestOrders(12)", "The target of the deleted link should be 'http://localhost/odata/TestOrders(12)'");
Assert.True(deletedLink.relationship.Value == "Orders", "The relationship of the deleted link should be 'Orders'");

var addedLink = results.value[6];
Assert.True(addedLink.source.Value == "http://localhost/odata/TestCustomers(10)", "The source of the added link should be 'http://localhost/odata/TestCustomers(10)'");
Assert.True(addedLink.target.Value == "http://localhost/odata/TestOrders(27)", "The target of the added link should be 'http://localhost/odata/TestOrders(27)'");
Assert.True(addedLink.relationship.Value == "Orders", "The relationship of the added link should be 'Orders'");
}

[Fact]
public async Task DeltaVerifyReslt_ContainsDynamicComplexProperties()
{
HttpRequestMessage get = new HttpRequestMessage(HttpMethod.Get, "odata/TestOrders?$deltaToken=abc");
get.Headers.Add("Accept", "application/json;odata.metadata=minimal");
get.Headers.Add("OData-Version", "4.01");
HttpClient client = CreateClient();
HttpResponseMessage response = await client.SendAsync(get);
Assert.True(response.IsSuccessStatusCode);

string result = await response.Content.ReadAsStringAsync();
Assert.Equal("{\"@context\":\"http://localhost/odata/$metadata#TestOrders/$delta\"," +
"\"value\":[" +
"{" +
"\"Id\":1," +
"\"Amount\":42," +
"\"Location\":{" +
"\"State\":\"State\"," +
"\"City\":null," +
"\"ZipCode\":null," +
"\"OpenProperty\":10," +
"\"key-samplelist\":{" +
"\"@type\":\"#Microsoft.AspNetCore.OData.E2E.Tests.DeltaToken.TestAddress\"," +
"\"State\":\"sample state\"," +
"\"City\":null," +
"\"ZipCode\":9," +
"\"title\":\"sample title\"" +
"}" +
"}" +
"}" +
"]" +
"}",
result);
}
}
}

0 comments on commit 0ccf968

Please sign in to comment.