Skip to content

Commit

Permalink
Fix 1937 : Expanded navigation properties should be properly represented
Browse files Browse the repository at this point in the history
on the response payload

Serialize null single valued navigation property as null in payload.
Serialize null collection valued navigation property as empty collection
in payload.  Previously, they were not serialized, i.e., missing in
payload.
  • Loading branch information
congysu committed May 30, 2014
1 parent 977bff5 commit 4c5f054
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,26 @@ private void WriteExpandedNavigationProperty(
SelectExpandClause selectExpandClause = navigationPropertyToExpand.Value;

object propertyValue = entityInstanceContext.GetPropertyValue(navigationProperty.Name);
if (propertyValue != null)

if (propertyValue == null)
{
if (navigationProperty.Type.IsCollection())
{
// A navigation property whose Type attribute specifies a collection, the collection always exists,
// it may just be empty.
// If a collection of entities can be related, it is represented as a JSON array. An empty
// collection of entities (one that contains no entities) is represented as an empty JSON array.
writer.WriteStart(new ODataFeed());
}
else
{
// If at most one entity can be related, the value is null if no entity is currently related.
writer.WriteStart(entry: null);
}

writer.WriteEnd();
}
else
{
// create the serializer context for the expanded item.
ODataSerializerContext nestedWriteContext = new ODataSerializerContext(entityInstanceContext, selectExpandClause, navigationProperty);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ public class ODataEntityTypeSerializerTests
{
private IEdmModel _model;
private IEdmEntitySet _customerSet;
private IEdmEntitySet _orderSet;
private Customer _customer;
private Order _order;
private ODataEntityTypeSerializer _serializer;
private ODataSerializerContext _writeContext;
private EntityInstanceContext _entityInstanceContext;
private ODataSerializerProvider _serializerProvider;
private IEdmEntityTypeReference _customerType;
private IEdmEntityTypeReference _orderType;
private IEdmEntityTypeReference _specialCustomerType;
private IEdmEntityTypeReference _specialOrderType;
private ODataPath _path;

public ODataEntityTypeSerializerTests()
Expand All @@ -42,6 +47,12 @@ public ODataEntityTypeSerializerTests()

_model.SetAnnotationValue<ClrTypeAnnotation>(_model.FindType("Default.Customer"), new ClrTypeAnnotation(typeof(Customer)));
_model.SetAnnotationValue<ClrTypeAnnotation>(_model.FindType("Default.Order"), new ClrTypeAnnotation(typeof(Order)));
_model.SetAnnotationValue(
_model.FindType("Default.SpecialCustomer"),
new ClrTypeAnnotation(typeof(SpecialCustomer)));
_model.SetAnnotationValue(
_model.FindType("Default.SpecialOrder"),
new ClrTypeAnnotation(typeof(SpecialOrder)));

_customerSet = _model.EntityContainer.FindEntitySet("Customers");
_customer = new Customer()
Expand All @@ -51,8 +62,17 @@ public ODataEntityTypeSerializerTests()
ID = 10,
};

_orderSet = _model.EntityContainer.FindEntitySet("Orders");
_order = new Order
{
ID = 20,
};

_serializerProvider = new DefaultODataSerializerProvider();
_customerType = _model.GetEdmTypeReference(typeof(Customer)).AsEntity();
_orderType = _model.GetEdmTypeReference(typeof(Order)).AsEntity();
_specialCustomerType = _model.GetEdmTypeReference(typeof(SpecialCustomer)).AsEntity();
_specialOrderType = _model.GetEdmTypeReference(typeof(SpecialOrder)).AsEntity();
_serializer = new ODataEntityTypeSerializer(_serializerProvider);
_path = new ODataPath(new EntitySetPathSegment(_customerSet));
_writeContext = new ODataSerializerContext() { NavigationSource = _customerSet, Model = _model, Path = _path };
Expand Down Expand Up @@ -375,6 +395,193 @@ public void WriteObjectInline_CanExpandNavigationProperty_ContainingEdmObject()
ordersSerializer.Verify();
}

[Fact]
public void WriteObjectInline_CanWriteExpandedNavigationProperty_ExpandedCollectionValuedNavigationPropertyIsNull()
{
// Arrange
IEdmEntityType customerType = _customerSet.EntityType();
IEdmNavigationProperty ordersProperty = customerType.NavigationProperties().Single(p => p.Name == "Orders");

Mock<IEdmEntityObject> customer = new Mock<IEdmEntityObject>();
object ordersValue = null;
customer.Setup(c => c.TryGetPropertyValue("Orders", out ordersValue)).Returns(true);
customer.Setup(c => c.GetEdmType()).Returns(customerType.AsReference());

ODataQueryOptionParser parser = new ODataQueryOptionParser(_model, customerType, _customerSet,
new Dictionary<string, string> { { "$select", "Orders" }, { "$expand", "Orders" } });
SelectExpandClause selectExpandClause = parser.ParseSelectAndExpand();

SelectExpandNode selectExpandNode = new SelectExpandNode();
selectExpandNode.ExpandedNavigationProperties[ordersProperty] =
selectExpandClause.SelectedItems.OfType<ExpandedNavigationSelectItem>().Single().SelectAndExpand;

Mock<ODataWriter> writer = new Mock<ODataWriter>();
writer.Setup(w => w.WriteStart(It.IsAny<ODataFeed>())).Callback(
(ODataFeed feed) =>
{
Assert.Null(feed.Count);
Assert.Null(feed.DeltaLink);
Assert.Null(feed.Id);
Assert.Empty(feed.InstanceAnnotations);
Assert.Null(feed.NextPageLink);
}).Verifiable();
Mock<ODataEdmTypeSerializer> ordersSerializer = new Mock<ODataEdmTypeSerializer>(ODataPayloadKind.Entry);
Mock<ODataSerializerProvider> serializerProvider = new Mock<ODataSerializerProvider>();
serializerProvider.Setup(p => p.GetEdmTypeSerializer(ordersProperty.Type)).Returns(ordersSerializer.Object);

Mock<ODataEntityTypeSerializer> serializer = new Mock<ODataEntityTypeSerializer>(serializerProvider.Object);
serializer.Setup(s => s.CreateSelectExpandNode(It.IsAny<EntityInstanceContext>())).Returns(selectExpandNode);
serializer.CallBase = true;

// Act
serializer.Object.WriteObjectInline(customer.Object, _customerType, writer.Object, _writeContext);

// Assert
writer.Verify();
}

[Fact]
public void WriteObjectInline_CanWriteExpandedNavigationProperty_ExpandedSingleValuedNavigationPropertyIsNull()
{
// Arrange
IEdmEntityType orderType = _orderSet.EntityType();
IEdmNavigationProperty customerProperty = orderType.NavigationProperties().Single(p => p.Name == "Customer");

Mock<IEdmEntityObject> order = new Mock<IEdmEntityObject>();
object customerValue = null;
order.Setup(c => c.TryGetPropertyValue("Customer", out customerValue)).Returns(true);
order.Setup(c => c.GetEdmType()).Returns(orderType.AsReference());

ODataQueryOptionParser parser = new ODataQueryOptionParser(_model, orderType, _orderSet,
new Dictionary<string, string> { { "$select", "Customer" }, { "$expand", "Customer" } });
SelectExpandClause selectExpandClause = parser.ParseSelectAndExpand();

SelectExpandNode selectExpandNode = new SelectExpandNode();
selectExpandNode.ExpandedNavigationProperties[customerProperty] =
selectExpandClause.SelectedItems.OfType<ExpandedNavigationSelectItem>().Single().SelectAndExpand;

Mock<ODataWriter> writer = new Mock<ODataWriter>();

writer.Setup(w => w.WriteStart(null as ODataEntry)).Verifiable();
Mock<ODataEdmTypeSerializer> ordersSerializer = new Mock<ODataEdmTypeSerializer>(ODataPayloadKind.Entry);
Mock<ODataSerializerProvider> serializerProvider = new Mock<ODataSerializerProvider>();
serializerProvider.Setup(p => p.GetEdmTypeSerializer(customerProperty.Type))
.Returns(ordersSerializer.Object);

Mock<ODataEntityTypeSerializer> serializer = new Mock<ODataEntityTypeSerializer>(serializerProvider.Object);
serializer.Setup(s => s.CreateSelectExpandNode(It.IsAny<EntityInstanceContext>())).Returns(selectExpandNode);
serializer.CallBase = true;

// Act
serializer.Object.WriteObjectInline(order.Object, _orderType, writer.Object, _writeContext);

// Assert
writer.Verify();
}

[Fact]
public void WriteObjectInline_CanWriteExpandedNavigationProperty_DerivedExpandedCollectionValuedNavigationPropertyIsNull()
{
// Arrange
IEdmEntityType specialCustomerType = (IEdmEntityType)_specialCustomerType.Definition;
IEdmNavigationProperty specialOrdersProperty =
specialCustomerType.NavigationProperties().Single(p => p.Name == "SpecialOrders");

Mock<IEdmEntityObject> customer = new Mock<IEdmEntityObject>();
object specialOrdersValue = null;
customer.Setup(c => c.TryGetPropertyValue("SpecialOrders", out specialOrdersValue)).Returns(true);
customer.Setup(c => c.GetEdmType()).Returns(_specialCustomerType);

IEdmEntityType customerType = _customerSet.EntityType();
ODataQueryOptionParser parser = new ODataQueryOptionParser(
_model,
customerType,
_customerSet,
new Dictionary<string, string>
{
{ "$select", "Default.SpecialCustomer/SpecialOrders" },
{ "$expand", "Default.SpecialCustomer/SpecialOrders" }
});
SelectExpandClause selectExpandClause = parser.ParseSelectAndExpand();

SelectExpandNode selectExpandNode = new SelectExpandNode();
selectExpandNode.ExpandedNavigationProperties[specialOrdersProperty] =
selectExpandClause.SelectedItems.OfType<ExpandedNavigationSelectItem>().Single().SelectAndExpand;

Mock<ODataWriter> writer = new Mock<ODataWriter>();
writer.Setup(w => w.WriteStart(It.IsAny<ODataFeed>())).Callback(
(ODataFeed feed) =>
{
Assert.Null(feed.Count);
Assert.Null(feed.DeltaLink);
Assert.Null(feed.Id);
Assert.Empty(feed.InstanceAnnotations);
Assert.Null(feed.NextPageLink);
}).Verifiable();
Mock<ODataEdmTypeSerializer> ordersSerializer = new Mock<ODataEdmTypeSerializer>(ODataPayloadKind.Entry);
Mock<ODataSerializerProvider> serializerProvider = new Mock<ODataSerializerProvider>();
serializerProvider.Setup(p => p.GetEdmTypeSerializer(specialOrdersProperty.Type))
.Returns(ordersSerializer.Object);

Mock<ODataEntityTypeSerializer> serializer = new Mock<ODataEntityTypeSerializer>(serializerProvider.Object);
serializer.Setup(s => s.CreateSelectExpandNode(It.IsAny<EntityInstanceContext>())).Returns(selectExpandNode);
serializer.CallBase = true;

// Act
serializer.Object.WriteObjectInline(customer.Object, _customerType, writer.Object, _writeContext);

// Assert
writer.Verify();
}

[Fact]
public void WriteObjectInline_CanWriteExpandedNavigationProperty_DerivedExpandedSingleValuedNavigationPropertyIsNull()
{
// Arrange
IEdmEntityType specialOrderType = (IEdmEntityType)_specialOrderType.Definition;
IEdmNavigationProperty customerProperty =
specialOrderType.NavigationProperties().Single(p => p.Name == "SpecialCustomer");

Mock<IEdmEntityObject> order = new Mock<IEdmEntityObject>();
object customerValue = null;
order.Setup(c => c.TryGetPropertyValue("SpecialCustomer", out customerValue)).Returns(true);
order.Setup(c => c.GetEdmType()).Returns(_specialOrderType);

IEdmEntityType orderType = (IEdmEntityType)_orderType.Definition;
ODataQueryOptionParser parser = new ODataQueryOptionParser(
_model,
orderType,
_orderSet,
new Dictionary<string, string>
{
{ "$select", "Default.SpecialOrder/SpecialCustomer" },
{ "$expand", "Default.SpecialOrder/SpecialCustomer" }
});
SelectExpandClause selectExpandClause = parser.ParseSelectAndExpand();

SelectExpandNode selectExpandNode = new SelectExpandNode();
selectExpandNode.ExpandedNavigationProperties[customerProperty] =
selectExpandClause.SelectedItems.OfType<ExpandedNavigationSelectItem>().Single().SelectAndExpand;

Mock<ODataWriter> writer = new Mock<ODataWriter>();

writer.Setup(w => w.WriteStart(null as ODataEntry)).Verifiable();
Mock<ODataEdmTypeSerializer> ordersSerializer = new Mock<ODataEdmTypeSerializer>(ODataPayloadKind.Entry);
Mock<ODataSerializerProvider> serializerProvider = new Mock<ODataSerializerProvider>();
serializerProvider.Setup(p => p.GetEdmTypeSerializer(customerProperty.Type))
.Returns(ordersSerializer.Object);

Mock<ODataEntityTypeSerializer> serializer = new Mock<ODataEntityTypeSerializer>(serializerProvider.Object);
serializer.Setup(s => s.CreateSelectExpandNode(It.IsAny<EntityInstanceContext>())).Returns(selectExpandNode);
serializer.CallBase = true;

// Act
serializer.Object.WriteObjectInline(order.Object, _orderType, writer.Object, _writeContext);

// Assert
writer.Verify();
}

[Fact]
public void CreateEntry_ThrowsArgumentNull_SelectExpandNode()
{
Expand Down Expand Up @@ -1488,6 +1695,11 @@ public Customer()
public IList<Order> Orders { get; private set; }
}

private class SpecialCustomer
{
public IList<SpecialOrder> SpecialOrders { get; private set; }
}

private class Order
{
public int ID { get; set; }
Expand All @@ -1496,6 +1708,11 @@ private class Order
public Customer Customer { get; set; }
}

private class SpecialOrder
{
public SpecialCustomer SpecialCustomer { get; set; }
}

private class FakeBindableProcedureFinder : BindableProcedureFinder
{
private IEdmOperation[] _procedures;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ public static IEdmModel SimpleCustomerOrderModel()
concurrencyMode: EdmConcurrencyMode.Fixed);
model.AddElement(customerType);

var specialCustomerType = new EdmEntityType("Default", "SpecialCustomer", customerType);
model.AddElement(specialCustomerType);

var orderType = new EdmEntityType("Default", "Order");
orderType.AddStructuralProperty("ID", EdmPrimitiveTypeKind.Int32);
orderType.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String);
orderType.AddStructuralProperty("Shipment", EdmPrimitiveTypeKind.String);
model.AddElement(orderType);

var specialOrderType = new EdmEntityType("Default", "SpecialOrder", orderType);
model.AddElement(specialOrderType);

var addressType = new EdmComplexType("Default", "Address");
addressType.AddStructuralProperty("Street", EdmPrimitiveTypeKind.String);
addressType.AddStructuralProperty("City", EdmPrimitiveTypeKind.String);
Expand All @@ -44,13 +50,33 @@ public static IEdmModel SimpleCustomerOrderModel()
// Add navigations
customerType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() { Name = "Orders", Target = orderType, TargetMultiplicity = EdmMultiplicity.Many });
orderType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo() { Name = "Customer", Target = customerType, TargetMultiplicity = EdmMultiplicity.One });
specialCustomerType.AddUnidirectionalNavigation(
new EdmNavigationPropertyInfo
{
Name = "SpecialOrders",
Target = specialOrderType,
TargetMultiplicity = EdmMultiplicity.Many
});
orderType.AddUnidirectionalNavigation(
new EdmNavigationPropertyInfo
{
Name = "SpecialCustomer",
Target = specialCustomerType,
TargetMultiplicity = EdmMultiplicity.One
});

// Add Entity set
var container = new EdmEntityContainer("Default", "Container");
var customerSet = container.AddEntitySet("Customers", customerType);
var orderSet = container.AddEntitySet("Orders", orderType);
customerSet.AddNavigationTarget(customerType.NavigationProperties().Single(np => np.Name == "Orders"), orderSet);
customerSet.AddNavigationTarget(
specialCustomerType.NavigationProperties().Single(np => np.Name == "SpecialOrders"),
orderSet);
orderSet.AddNavigationTarget(orderType.NavigationProperties().Single(np => np.Name == "Customer"), customerSet);
orderSet.AddNavigationTarget(
specialOrderType.NavigationProperties().Single(np => np.Name == "SpecialCustomer"),
customerSet);

NavigationSourceLinkBuilderAnnotation linkAnnotation = new MockNavigationSourceLinkBuilderAnnotation();
model.SetNavigationSourceLinkBuilder(customerSet, linkAnnotation);
Expand Down

0 comments on commit 4c5f054

Please sign in to comment.