Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix serialization issue affecting nested complex property #755

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ await serializer.WriteDeltaObjectInlineAsync(propertyValue, edmProperty.Type, wr
}
}

private static IEnumerable<ODataProperty> CreateODataPropertiesFromDynamicType(EdmEntityType entityType, object graph,
private static IEnumerable<ODataProperty> CreateODataPropertiesFromDynamicType(EdmStructuredType structuredType, object graph,
Dictionary<IEdmProperty, object> dynamicTypeProperties, ODataSerializerContext writeContext)
{
Contract.Assert(dynamicTypeProperties != null);
Expand All @@ -267,7 +267,7 @@ private static IEnumerable<ODataProperty> CreateODataPropertiesFromDynamicType(E
{
foreach (var prop in dynamicObject.Values)
{
IEdmProperty edmProperty = entityType?.Properties()
IEdmProperty edmProperty = structuredType?.Properties()
.FirstOrDefault(p => p.Name.Equals(prop.Key, StringComparison.Ordinal));

if (prop.Value != null
Expand Down Expand Up @@ -321,21 +321,21 @@ private async Task WriteDynamicTypeResourceAsync(object graph, ODataWriter write
ODataSerializerContext writeContext)
{
var dynamicTypeProperties = new Dictionary<IEdmProperty, object>();
var entityType = expectedType.Definition as EdmEntityType;
var structuredType = expectedType.Definition as EdmStructuredType;
var resource = new ODataResource()
{
TypeName = expectedType.FullName(),
Properties = CreateODataPropertiesFromDynamicType(entityType, graph, dynamicTypeProperties, writeContext)
Properties = CreateODataPropertiesFromDynamicType(structuredType, graph, dynamicTypeProperties, writeContext)
};

resource.IsTransient = true;
await writer.WriteStartAsync(resource).ConfigureAwait(false);
foreach (var property in dynamicTypeProperties.Keys)
{
var resourceContext = new ResourceContext(writeContext, expectedType.AsEntity(), graph);
if (entityType.NavigationProperties().Any(p => p.Type.Equals(property.Type)) && !(property.Type is EdmCollectionTypeReference))
var resourceContext = new ResourceContext(writeContext, expectedType.AsStructured(), graph);
if (structuredType.NavigationProperties().Any(p => p.Type.Equals(property.Type)) && !(property.Type is EdmCollectionTypeReference))
{
var navigationProperty = entityType.NavigationProperties().FirstOrDefault(p => p.Type.Equals(property.Type));
var navigationProperty = structuredType.NavigationProperties().FirstOrDefault(p => p.Type.Equals(property.Type));
var navigationLink = CreateNavigationLink(navigationProperty, resourceContext);
if (navigationLink != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,32 @@ public void Generate()
_context.SaveChanges();
}
}

public class EmployeesController : ODataController
{
private static readonly List<Employee> employees = new List<Employee>
{
new Employee
{
Id = 1,
NextOfKin = new NextOfKin { Name = "NoK 1", PhysicalAddress = new Location { City = "Redmond" } }
},
new Employee
{
Id = 2,
NextOfKin = new NextOfKin { Name = "NoK 2", PhysicalAddress = new Location { City = "Nairobi" } }
},
new Employee
{
Id = 3,
NextOfKin = new NextOfKin { Name = "NoK 3", PhysicalAddress = new Location { City = "Redmond" } }
}
};

[EnableQuery]
public IQueryable<Employee> Get()
{
return employees.AsQueryable();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,21 @@ public class Address

public string Street { get; set; }
}

public class Employee
{
public int Id { get; set; }
public NextOfKin NextOfKin { get; set; }
}

public class NextOfKin
{
public string Name { get; set; }
public Location PhysicalAddress { get; set; }
}

public class Location
{
public string City { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using Newtonsoft.Json.Linq;
using Xunit;

Expand Down Expand Up @@ -202,4 +203,70 @@ public async Task AggregationOnEntitySetWorksWithGroupby()
Assert.Equal(2 * (25 + 75), customerOnePrice);
}
}

public class NestedComplexPropertyAggregationTests : WebApiTestBase<NestedComplexPropertyAggregationTests>
{
public NestedComplexPropertyAggregationTests(WebApiTestFixture<NestedComplexPropertyAggregationTests> fixture)
: base(fixture)
{
}

protected static void UpdateConfigureServices(IServiceCollection services)
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");

services.ConfigureControllers(typeof(EmployeesController));

services.AddControllers().AddOData(options => options.Select().Filter().OrderBy().Expand().Count().SkipToken().SetMaxTop(null)
.AddRouteComponents("aggregation", builder.GetEdmModel()));
}

private const string AggregationTestBaseUrl = "aggregation/Employees";

[Fact]
public async Task GroupByComplexProperty()
{
// Arrange
string queryUrl = AggregationTestBaseUrl + "?$apply=groupby((NextOfKin/Name))";

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, queryUrl);
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata.metadata=none"));
HttpClient client = this.CreateClient();

// Act
HttpResponseMessage response = await client.SendAsync(request);

// Assert
var result = await response.Content.ReadAsObject<JObject>();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var results = result["value"] as JArray;
Assert.Equal(3, results.Count);
Assert.Equal("NoK 1", (results[0]["NextOfKin"] as JObject)["Name"].ToString());
Assert.Equal("NoK 2", (results[1]["NextOfKin"] as JObject)["Name"].ToString());
Assert.Equal("NoK 3", (results[2]["NextOfKin"] as JObject)["Name"].ToString());
}

[Fact]
public async Task GroupByNestedComplexProperty()
{
// Arrange
string queryUrl = AggregationTestBaseUrl + "?$apply=groupby((NextOfKin/PhysicalAddress/City))";

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, queryUrl);
request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata.metadata=none"));
HttpClient client = this.CreateClient();

// Act
HttpResponseMessage response = await client.SendAsync(request);

// Assert
var result = await response.Content.ReadAsObject<JObject>();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var results = result["value"] as JArray;
Assert.Equal(2, results.Count);
Assert.Equal("Redmond", ((results[0]["NextOfKin"] as JObject)["PhysicalAddress"] as JObject)["City"].ToString());
Assert.Equal("Nairobi", ((results[1]["NextOfKin"] as JObject)["PhysicalAddress"] as JObject)["City"].ToString());
}
}
}