diff --git a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs index a1b9ef0491..d59b883605 100644 --- a/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs +++ b/src/Microsoft.OData.Core/UriParser/Binders/SelectExpandBinder.cs @@ -284,7 +284,7 @@ private SelectItem GenerateSelectItem(SelectTermToken tokenIn) IEdmNavigationSource targetNavigationSource = null; ODataPathSegment lastSegment = selectedPath.Last(); - IEdmType targetElementType = lastSegment.TargetEdmType; + IEdmType targetElementType = lastSegment.EdmType; IEdmCollectionType collection = targetElementType as IEdmCollectionType; if (collection != null) { @@ -441,23 +441,25 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) ODataExpandPath pathToNavProp = new ODataExpandPath(pathSoFar); + IEdmTypeReference elementTypeReference = hasDerivedTypeSegment ? derivedType.ToTypeReference() : null; + // $apply - ApplyClause applyOption = BindApply(tokenIn.ApplyOptions, this.ResourcePathNavigationSource, targetNavigationSource); + ApplyClause applyOption = BindApply(tokenIn.ApplyOptions, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference); // $compute - ComputeClause computeOption = BindCompute(tokenIn.ComputeOption, this.ResourcePathNavigationSource, targetNavigationSource); + ComputeClause computeOption = BindCompute(tokenIn.ComputeOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference); var generatedProperties = GetGeneratedProperties(computeOption, applyOption); bool collapsed = applyOption?.Transformations.Any(t => t.Kind == TransformationNodeKind.Aggregate || t.Kind == TransformationNodeKind.GroupBy) ?? false; // $filter - FilterClause filterOption = BindFilter(tokenIn.FilterOption, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed); + FilterClause filterOption = BindFilter(tokenIn.FilterOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties, collapsed); // $orderby - OrderByClause orderbyOption = BindOrderby(tokenIn.OrderByOptions, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed); + OrderByClause orderbyOption = BindOrderby(tokenIn.OrderByOptions, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties, collapsed); // $search - SearchClause searchOption = BindSearch(tokenIn.SearchOption, this.ResourcePathNavigationSource, targetNavigationSource, null); + SearchClause searchOption = BindSearch(tokenIn.SearchOption, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference); if (isRef) { @@ -470,7 +472,7 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) } // $select & $expand - SelectExpandClause subSelectExpand = BindSelectExpand(tokenIn.ExpandOption, tokenIn.SelectOption, parsedPath, this.ResourcePathNavigationSource, targetNavigationSource, null, generatedProperties, collapsed); + SelectExpandClause subSelectExpand = BindSelectExpand(tokenIn.ExpandOption, tokenIn.SelectOption, parsedPath, this.ResourcePathNavigationSource, targetNavigationSource, elementTypeReference, generatedProperties, collapsed); // $levels LevelsClause levelsOption = ParseLevels(tokenIn.LevelsOption, currentLevelEntityType, currentNavProp); @@ -485,12 +487,13 @@ private SelectItem GenerateExpandItem(ExpandTermToken tokenIn) /// The apply tokens to visit. /// The navigation source at the resource path. /// The target navigation source at the current level. + /// The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type. /// The null or the built apply clause. - private ApplyClause BindApply(IEnumerable applyToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource) + private ApplyClause BindApply(IEnumerable applyToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource, IEdmTypeReference elementType = null) { if (applyToken != null && applyToken.Any()) { - MetadataBinder binder = BuildNewMetadataBinder(this.Configuration, resourcePathNavigationSource, targetNavigationSource, null); + MetadataBinder binder = BuildNewMetadataBinder(this.Configuration, resourcePathNavigationSource, targetNavigationSource, elementType); ApplyBinder applyBinder = new ApplyBinder(binder.Bind, binder.BindingState); return applyBinder.BindApply(applyToken); } @@ -504,7 +507,7 @@ private ApplyClause BindApply(IEnumerable applyToken, IEdmNavigation /// The compute token to visit. /// The navigation source at the resource path. /// The target navigation source at the current level. - /// The target element type. + /// The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type. /// The null or the built compute clause. private ComputeClause BindCompute(ComputeToken computeToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource, IEdmTypeReference elementType = null) { @@ -524,7 +527,7 @@ private ComputeClause BindCompute(ComputeToken computeToken, IEdmNavigationSourc /// The filter token to visit. /// The navigation source at the resource path. /// The target navigation source at the current level. - /// The Edm element type. + /// The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type. /// The generated properties. /// The collapsed boolean value. /// The null or the built filter clause. @@ -547,7 +550,7 @@ private FilterClause BindFilter(QueryToken filterToken, IEdmNavigationSource res /// The orderby token to visit. /// The navigation source at the resource path. /// The target navigation source at the current level. - /// The Edm element type. + /// The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type. /// The generated properties. /// The collapsed boolean value. /// The null or the built filter clause. @@ -571,7 +574,7 @@ private OrderByClause BindOrderby(IEnumerable orderByToken, IEdmNa /// The search token to visit. /// The navigation source at the resource path. /// The target navigation source at the current level. - /// The Edm element type. + /// The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type. /// The null or the built search clause. private SearchClause BindSearch(QueryToken searchToken, IEdmNavigationSource resourcePathNavigationSource, IEdmNavigationSource targetNavigationSource, IEdmTypeReference elementType) { @@ -593,7 +596,7 @@ private SearchClause BindSearch(QueryToken searchToken, IEdmNavigationSource res /// The parsed segments to visit. /// The navigation source at the resource path. /// The target navigation source at the current level. - /// The Edm element type. + /// The target element type if different from the type of the target navigation source. Null indicates that the element type is the same as the target navigation source type. /// The generated properties. /// The collapsed boolean value. /// The null or the built select and expand clause. diff --git a/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs b/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs index 3a8518c4c5..08c65611bd 100644 --- a/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs +++ b/src/Microsoft.OData.Core/UriParser/ODataPathInfo.cs @@ -35,7 +35,7 @@ public ODataPathInfo(ODataPath odataPath) } this.targetNavigationSource = lastSegment.TargetEdmNavigationSource; - this.targetEdmType = lastSegment.TargetEdmType; + this.targetEdmType = lastSegment.EdmType; if (this.targetEdmType != null) { IEdmCollectionType collectionType = this.targetEdmType as IEdmCollectionType; diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs index fb9bcd5e3c..5a48133725 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/UriParser/SelectExpandFunctionalTests.cs @@ -1732,7 +1732,9 @@ public void ExpandWithNavigationPropCountWithFullyQualifiedTypeWorks(string quer // $expand=navProp/fully.qualified.type/$count($filter=prop) [Theory] [InlineData("MyPeople/Fully.Qualified.Namespace.Employee/$count($filter=ID eq 1)")] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee/$count($filter=WorkEmail eq 'xyz@wp.com')")] [InlineData("MyPeople/MainAlias.Employee/$count($filter=ID eq 1)")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee/$count($filter=WorkEmail eq 'xyz@wp.com')")] // With schema alias public void ExpandWithNavigationPropCountWithFilterAndFullyQualifiedTypeWorks(string query) { // Arrange @@ -1818,6 +1820,199 @@ public void ExpandWithNavigationPropWithUndefinedTypeThrows(string query) action.Throws(ODataErrorStrings.ExpandItemBinder_CannotFindType("Fully.Qualified.Namespace.UndefinedType")); } + // $expand=navProp/fully.qualified.type($filter=prop) + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($filter=ID eq 1)")] // ID is a property in the base type Person. + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($filter=WorkEmail eq 'xyz@wp.com')")] // WorkEmail is a property in the derived type Employee. + [InlineData("MyPeople/MainAlias.Employee($filter=ID eq 1)")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee($filter=WorkEmail eq 'xyz@wp.com')")] // With schema alias + public void ExpandWithNavigationPropWithFilterAndFullyQualifiedTypeWorks(string query) + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", query} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedNavigationSelectItem expandedNavigationSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedNavigationSelectItem.NavigationSource); + Assert.NotNull(expandedNavigationSelectItem.FilterOption); + Assert.Equal(2, expandedNavigationSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); + + FilterClause filterClause = expandedNavigationSelectItem.FilterOption; + Assert.Equal("Fully.Qualified.Namespace.Employee", filterClause.RangeVariable.TypeReference.FullName()); + } + + // $expand=navProp/fully.qualified.type($select=prop) + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($select=Name)")] // Name is a property in the base type Person. + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($select=WorkEmail)")] // WorkEmail is a property in the derived type Employee. + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($select=WorkAddress)")] // WorkAddress is a structured property in the derived type Employee. + [InlineData("MyPeople/MainAlias.Employee($select=Name)")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee($select=WorkEmail)")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee($select=WorkAddress)")] // With schema alias + public void ExpandWithNavigationPropWithSelectAndFullyQualifiedTypeWorks(string query) + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", query} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedNavigationSelectItem expandedNavigationSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedNavigationSelectItem.NavigationSource); + Assert.NotNull(expandedNavigationSelectItem.SelectAndExpand); + Assert.Equal(2, expandedNavigationSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); + + SelectExpandClause innerSelectExpandClause = expandedNavigationSelectItem.SelectAndExpand; + Assert.IsType(Assert.Single(innerSelectExpandClause.SelectedItems)); + } + + // $expand=navProp/fully.qualified.type($expand=prop) + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($expand=MyLions)")] // MyLions is a navigation property in the base type Person. + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($expand=PaintingsInOffice)")] // PaintingsInOffice is a navigation property in the derived type Employee. + [InlineData("MyPeople/MainAlias.Employee($expand=MyLions)")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee($expand=PaintingsInOffice)")] // With schema alias + public void ExpandWithNavigationPropWithExpandAndFullyQualifiedTypeWorks(string query) + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", query} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedNavigationSelectItem expandedNavigationSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedNavigationSelectItem.NavigationSource); + Assert.NotNull(expandedNavigationSelectItem.SelectAndExpand); + Assert.Equal(2, expandedNavigationSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); + + SelectExpandClause innerSelectExpandClause = expandedNavigationSelectItem.SelectAndExpand; + Assert.IsType(Assert.Single(innerSelectExpandClause.SelectedItems)); + } + + // $expand=navProp/fully.qualified.type($orderby=prop) + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($orderby=Name)")] // Name is a property in the base type Person. + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($orderby=WorkEmail)")] // WorkEmail is a property in the derived type Employee. + [InlineData("MyPeople/MainAlias.Employee($orderby=Name)")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee($orderby=WorkEmail)")] // With schema alias + public void ExpandWithNavigationPropWithOrderByAndFullyQualifiedTypeWorks(string query) + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", query} + }); + + // Act + var selectExpandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + Assert.NotNull(selectExpandClause); + ExpandedNavigationSelectItem expandedNavigationSelectItem = Assert.IsType(Assert.Single(selectExpandClause.SelectedItems)); + Assert.Same(HardCodedTestModel.GetPeopleSet(), expandedNavigationSelectItem.NavigationSource); + Assert.NotNull(expandedNavigationSelectItem.OrderByOption); + Assert.Equal(2, expandedNavigationSelectItem.PathToNavigationProperty.Count); + + NavigationPropertySegment navPropSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.First()); + TypeSegment typeSegment = Assert.IsType(expandedNavigationSelectItem.PathToNavigationProperty.Segments.Last()); + Assert.Equal("MyPeople", navPropSegment.Identifier); + Assert.Equal("Collection(Fully.Qualified.Namespace.Person)", navPropSegment.EdmType.FullTypeName()); + Assert.Equal("Fully.Qualified.Namespace.Employee", typeSegment.EdmType.FullTypeName()); + } + + // $expand=navProp/fully.qualified.type($compute=prop as ComputedProp) + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($compute=Name as CustomDetails)")] // Name is a property in the base type Person. + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($compute=WorkEmail as CustomDetails)")] // WorkEmail is a property in the derived type Employee. + [InlineData("MyPeople/MainAlias.Employee($compute=Name as CustomDetails)")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee($compute=WorkEmail as CustomDetails)")] // With schema alias + public void ExpandWithNavigationPropWithComputeAndFullyQualifiedTypeWorks(string query) + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", query} + }); + + // Act + var expandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + var expandedSelectionItem = expandClause.SelectedItems.OfType().Single(); + Assert.NotNull(expandedSelectionItem.ComputeOption); + Assert.Equal("CustomDetails", expandedSelectionItem.ComputeOption.ComputedItems.Single().Alias); + } + + // $expand=navProp/fully.qualified.type($apply=aggregate({aggregateQuery})) + [Theory] + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($apply=aggregate(ID with max as MaxID))")] // ID is a property in the base type Person. + [InlineData("MyPeople/Fully.Qualified.Namespace.Employee($apply=aggregate(WorkID with max as MaxID))")] // WorkID is a property in the derived type Employee. + [InlineData("MyPeople/MainAlias.Employee($apply=aggregate(ID with max as MaxID))")] // With schema alias + [InlineData("MyPeople/MainAlias.Employee($apply=aggregate(WorkID with max as MaxID))")] // With schema alias + public void ExpandWithNavigationPropWithApplyAndFullyQualifiedTypeWorks(string query) + { + // Arrange + var odataQueryOptionParser = new ODataQueryOptionParser(HardCodedTestModel.TestModel, + HardCodedTestModel.GetDogType(), HardCodedTestModel.GetDogsSet(), + new Dictionary() + { + {"$expand", query} + }); + + // Act + var expandClause = odataQueryOptionParser.ParseSelectAndExpand(); + + // Assert + var expandedSelectionItem = expandClause.SelectedItems.OfType().Single(); + Assert.NotNull(expandedSelectionItem.ApplyOption); + Assert.Equal("MaxID", (expandedSelectionItem.ApplyOption.Transformations.Single() as AggregateTransformationNode).AggregateExpressions.Single().Alias); + } + [Fact] public void SelectWithNestedSelectWorks() { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs index 882ac65621..ab728a25a7 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/HardCodedTestModel.cs @@ -100,6 +100,7 @@ internal static IEdmModel GetEdmModel() var FullyQualifiedNamespaceAddress = new EdmComplexType("Fully.Qualified.Namespace", "Address"); var FullyQualifiedNamespaceOpenAddress = new EdmComplexType("Fully.Qualified.Namespace", "OpenAddress", null, false, true); var FullyQualifiedNamespaceHomeAddress = new EdmComplexType("Fully.Qualified.Namespace", "HomeAddress", FullyQualifiedNamespaceAddress); + var FullyQualifiedNamespaceWorkAddress = new EdmComplexType("Fully.Qualified.Namespace", "WorkAddress", FullyQualifiedNamespaceAddress); var FullyQualifiedNamespaceHeartbeat = new EdmComplexType("Fully.Qualified.Namespace", "Heartbeat"); var FullyQualifiedNamespaceFilm = new EdmEntityType("Fully.Qualified.Namespace", "Film", null, false, false); @@ -131,6 +132,7 @@ internal static IEdmModel GetEdmModel() var FullyQualifiedNamespaceAddressTypeReference = new EdmComplexTypeReference(FullyQualifiedNamespaceAddress, true); var FullyQualifiedNamespaceOpenAddressTypeReference = new EdmComplexTypeReference(FullyQualifiedNamespaceOpenAddress, true); + var FullyQualifiedNamespaceWorkAddressTypeReference = new EdmComplexTypeReference(FullyQualifiedNamespaceWorkAddress, true); var FullyQualifiedNamespacePerson_ID = FullyQualifiedNamespacePerson.AddStructuralProperty("ID", EdmCoreModel.Instance.GetInt32(false)); var FullyQualifiedNamespacePerson_SSN = FullyQualifiedNamespacePerson.AddStructuralProperty("SSN", EdmCoreModel.Instance.GetString(true)); FullyQualifiedNamespacePerson.AddStructuralProperty("Shoe", EdmCoreModel.Instance.GetString(true)); @@ -216,6 +218,8 @@ internal static IEdmModel GetEdmModel() model.AddElement(FullyQualifiedNamespacePerson); FullyQualifiedNamespaceEmployee.AddStructuralProperty("WorkEmail", EdmCoreModel.Instance.GetString(true)); + FullyQualifiedNamespaceEmployee.AddStructuralProperty("WorkID", EdmCoreModel.Instance.GetInt32(false)); + FullyQualifiedNamespaceEmployee.AddStructuralProperty("WorkAddress", FullyQualifiedNamespaceWorkAddressTypeReference); var FullyQualifiedNamespaceEmployee_PaintingsInOffice = FullyQualifiedNamespaceEmployee.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo { Name = "PaintingsInOffice", TargetMultiplicity = EdmMultiplicity.Many, Target = FullyQualifiedNamespacePainting }); var FullyQualifiedNamespaceEmployee_Manager = FullyQualifiedNamespaceEmployee.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo { Name = "Manager", TargetMultiplicity = EdmMultiplicity.ZeroOrOne, Target = FullyQualifiedNamespaceManager }); var FullyQualifiedNamespaceEmployee_OfficeDog = FullyQualifiedNamespaceDog.AddBidirectionalNavigation @@ -344,6 +348,9 @@ internal static IEdmModel GetEdmModel() FullyQualifiedNamespaceHomeAddress.AddStructuralProperty("HomeNO", EdmCoreModel.Instance.GetString(true)); model.AddElement(FullyQualifiedNamespaceHomeAddress); + FullyQualifiedNamespaceWorkAddress.AddStructuralProperty("WorkNO", EdmCoreModel.Instance.GetString(true)); + model.AddElement(FullyQualifiedNamespaceWorkAddress); + model.AddElement(FullyQualifiedNamespaceOpenAddress); FullyQualifiedNamespaceHeartbeat.AddStructuralProperty("Frequency", EdmCoreModel.Instance.GetDouble(true)); @@ -1059,6 +1066,8 @@ internal class HardCodedTestModelXml + + @@ -1151,6 +1160,9 @@ internal class HardCodedTestModelXml + + +