From 943889a218a5405568346536a1e3adb852bf5227 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Fri, 6 Dec 2024 11:13:28 -0800 Subject: [PATCH] Fixes #1817: support key alias Introduce the IEdmPropertyRef interface Use default interface method for IEdmEntityType avoid breaking changes This change focus on Edm lib, if it's ready and will move to OData.Core. --- .../Csdl/Parsing/Ast/CsdlPropertyReference.cs | 13 +- .../Csdl/Parsing/CsdlDocumentParser.cs | 2 +- .../Csdl/Parsing/SchemaJsonParser.cs | 24 ++- .../BadElements/UnresolvedPropertyRef.cs | 30 +++ .../CsdlSemanticsEntityTypeDefinition.cs | 98 ++++++++- .../EdmModelCsdlSchemaJsonWriter.cs | 23 +- .../Serialization/EdmModelCsdlSchemaWriter.cs | 4 +- .../EdmModelCsdlSchemaXmlWriter.cs | 24 ++- .../EdmModelCsdlSerializationVisitor.cs | 20 +- .../Microsoft.OData.Edm.cs | 2 + .../Microsoft.OData.Edm.csproj | 13 ++ .../Microsoft.OData.Edm.txt | 2 + .../Parameterized.Microsoft.OData.Edm.cs | 16 ++ .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 16 ++ .../Schema/EdmEntityType.cs | 40 ++-- .../Schema/EdmPropertyRef.cs | 78 +++++++ .../Schema/Interfaces/IEdmEntityType.cs | 9 +- .../Schema/Interfaces/IEdmPropertyRef.cs | 31 +++ .../Validation/EdmErrorCode.cs | 5 + .../Csdl/CsdlReaderJsonTests.cs | 50 +++++ .../Csdl/CsdlReaderTests.cs | 39 ++++ .../Csdl/CsdlWriterTests.cs | 196 ++++++++++++++++++ .../CsdlSemanticsNavigationPropertyTests.cs | 2 +- 23 files changed, 676 insertions(+), 61 deletions(-) create mode 100644 src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPropertyRef.cs create mode 100644 src/Microsoft.OData.Edm/Schema/EdmPropertyRef.cs create mode 100644 src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPropertyRef.cs diff --git a/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs b/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs index 23fa24dca9..2c96e606a0 100644 --- a/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs +++ b/src/Microsoft.OData.Edm/Csdl/Parsing/Ast/CsdlPropertyReference.cs @@ -11,17 +11,14 @@ namespace Microsoft.OData.Edm.Csdl.Parsing.Ast /// internal class CsdlPropertyReference : CsdlElement { - private readonly string propertyName; - - public CsdlPropertyReference(string propertyName, CsdlLocation location) + public CsdlPropertyReference(string propertyName, string propertyAlias, CsdlLocation location) : base(location) { - this.propertyName = propertyName; + PropertyName = propertyName; + PropertyAlias = propertyAlias; } - public string PropertyName - { - get { return this.propertyName; } - } + public string PropertyName { get; } + public string PropertyAlias { get; } } } diff --git a/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs b/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs index d52d0d9a7f..2b09018daf 100644 --- a/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs +++ b/src/Microsoft.OData.Edm/Csdl/Parsing/CsdlDocumentParser.cs @@ -364,7 +364,7 @@ private static CsdlKey OnEntityKeyElement(XmlElementInfo element, XmlElementValu private CsdlPropertyReference OnPropertyRefElement(XmlElementInfo element, XmlElementValueCollection childValues) { - return new CsdlPropertyReference(Required(CsdlConstants.Attribute_Name), element.Location); + return new CsdlPropertyReference(Required(CsdlConstants.Attribute_Name), Optional(CsdlConstants.Attribute_Alias), element.Location); } private CsdlEnumType OnEnumTypeElement(XmlElementInfo element, XmlElementValueCollection childValues) diff --git a/src/Microsoft.OData.Edm/Csdl/Parsing/SchemaJsonParser.cs b/src/Microsoft.OData.Edm/Csdl/Parsing/SchemaJsonParser.cs index f93d666cde..ab3da4eb2b 100644 --- a/src/Microsoft.OData.Edm/Csdl/Parsing/SchemaJsonParser.cs +++ b/src/Microsoft.OData.Edm/Csdl/Parsing/SchemaJsonParser.cs @@ -650,21 +650,37 @@ internal static CsdlKey ParseCsdlKey(string name, JsonElement keyArray, JsonPars Debug.Assert(keyArray.ValueKind == JsonValueKind.Array); Debug.Assert(name == "$Key", "The name should be $Key."); + string alias = null; + string propertyName = null; IList properties = keyArray.ParseAsArray(context, (v, p) => { if (v.ValueKind == JsonValueKind.Object) { - // TODO: ODL doesn't support the key referencing a property of a complex type as below. + // Key properties with a key alias are represented as objects with one member + // whose name is the key alias and whose value is a string containing the path to the property. For example: // "$Key": [ // { // "EntityInfoID": "Info/ID" // } // ] - p.ReportError(EdmErrorCode.InvalidKeyValue, "It doesn't support the key object."); + var objectProperties = v.EnumerateObject(); + if (objectProperties.Count() != 1) + { + p.ReportError(EdmErrorCode.InvalidKeyValue, "Key properties with a key alias are represented as objects with only one member."); + } + else + { + var property = objectProperties.First(); + alias = property.Name; + propertyName = property.Value.ParseAsString(context); + } + } + else + { + propertyName = v.ParseAsString(context); } - string propertyName = v.ParseAsString(context); - return new CsdlPropertyReference(propertyName, context.Location()); + return new CsdlPropertyReference(propertyName, alias, context.Location()); }); return new CsdlKey(properties, context.Location()); diff --git a/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPropertyRef.cs b/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPropertyRef.cs new file mode 100644 index 0000000000..978da82965 --- /dev/null +++ b/src/Microsoft.OData.Edm/Csdl/Semantics/BadElements/UnresolvedPropertyRef.cs @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using Microsoft.OData.Edm.Validation; + +namespace Microsoft.OData.Edm.Csdl.CsdlSemantics +{ + internal class UnresolvedPropertyRef : BadElement, IUnresolvedElement, IEdmPropertyRef + { + public UnresolvedPropertyRef(IEdmStructuredType declaringType, string name, string alias, EdmLocation location) + : base(new EdmError[] + { + new EdmError(location, EdmErrorCode.BadUnresolvedPropertyRef, + Edm.Strings.Bad_UnresolvedPropertyRef(declaringType.FullTypeName(), name, alias)) + }) + { + } + + public IEdmStructuralProperty ReferencedProperty => null; + + public string PropertyAlias { get; } + + public string Name { get; } + + public IEdmPathExpression Path { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs b/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs index c362edb1a7..d8b37eca5e 100644 --- a/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs +++ b/src/Microsoft.OData.Edm/Csdl/Semantics/CsdlSemanticsEntityTypeDefinition.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.OData.Edm.Csdl.Parsing.Ast; +using Microsoft.OData.Edm.Vocabularies; namespace Microsoft.OData.Edm.Csdl.CsdlSemantics { @@ -23,8 +24,8 @@ internal class CsdlSemanticsEntityTypeDefinition : CsdlSemanticsStructuredTypeDe private static readonly Func ComputeBaseTypeFunc = (me) => me.ComputeBaseType(); private static readonly Func OnCycleBaseTypeFunc = (me) => new CyclicEntityType(me.GetCyclicBaseTypeName(me.entity.BaseTypeName), me.Location); - private readonly Cache> declaredKeyCache = new Cache>(); - private static readonly Func> ComputeDeclaredKeyFunc = (me) => me.ComputeDeclaredKey(); + private readonly Cache> declaredKeyCache = new Cache>(); + private static readonly Func> ComputeDeclaredKeyFunc = (me) => me.ComputeDeclaredKey(); public CsdlSemanticsEntityTypeDefinition(CsdlSemanticsSchema context, CsdlEntityType entity) : base(context, entity) @@ -72,6 +73,14 @@ public bool HasStream } public IEnumerable DeclaredKey + { + get + { + return this.DeclaredKeyRef?.Select(x => x.ReferencedProperty); + } + } + + public IEnumerable DeclaredKeyRef { get { @@ -105,17 +114,24 @@ private IEdmEntityType ComputeBaseType() return null; } - private IEnumerable ComputeDeclaredKey() + private IEnumerable ComputeDeclaredKey() { if (this.entity.Key != null) { - List key = new List(); + List key = new List(); foreach (CsdlPropertyReference keyProperty in this.entity.Key.Properties) { - IEdmStructuralProperty structuralProperty = this.FindProperty(keyProperty.PropertyName) as IEdmStructuralProperty; + IEdmStructuralProperty structuralProperty = FindKeyProperty(keyProperty.PropertyName); if (structuralProperty != null) { - key.Add(structuralProperty); + if (keyProperty.PropertyAlias != null) + { + key.Add(new EdmPropertyRef(structuralProperty, new EdmPropertyPathExpression(keyProperty.PropertyName), keyProperty.PropertyAlias)); + } + else + { + key.Add(new EdmPropertyRef(structuralProperty)); + } } else { @@ -125,11 +141,11 @@ private IEnumerable ComputeDeclaredKey() structuralProperty = this.DeclaredProperties.FirstOrDefault(p => p.Name == keyProperty.PropertyName) as IEdmStructuralProperty; if (structuralProperty != null) { - key.Add(structuralProperty); + key.Add(new EdmPropertyRef(structuralProperty)); } else { - key.Add(new UnresolvedProperty(this, keyProperty.PropertyName, this.Location)); + key.Add(new UnresolvedPropertyRef(this, keyProperty.PropertyName, keyProperty.PropertyAlias, this.Location)); } } } @@ -139,5 +155,71 @@ private IEnumerable ComputeDeclaredKey() return null; } + + private IEdmStructuralProperty FindKeyProperty(string nameOrPath) + { + if (string.IsNullOrWhiteSpace(nameOrPath)) + { + return null; + } + + string[] segments = nameOrPath.Split('/'); + if (segments.Length == 1) + { + return FindPropertyOnType(this, nameOrPath) as IEdmStructuralProperty; + } + else + { + // OData spec says "The value of Name is a path expression leading to a primitive property." + // The segment in a path expression could be single value property name, collection value property name, type cast, ... + // However, for the key property reference path expression: + // 1) Collection value property name segment is invalid, right? + // 2) Type cast? reference a key on the sub type? it's valid but So far, let's skip it. + IEdmStructuredType edmStructuredType = this; + for (int i = 0; i < segments.Length; ++i) + { + if (edmStructuredType == null) + { + return null; + } + + string segment = segments[i]; + if (segment.Contains('.', StringComparison.Ordinal)) + { + // a type cast, let's skip it for key reference now. + continue; + } + + IEdmProperty edmProperty = FindPropertyOnType(edmStructuredType, segment); + if (i == segment.Length - 1) + { + return edmProperty as IEdmStructuralProperty; + } + else if (edmProperty != null) + { + // If the property is a collection value, let's move on using the element type of this collection + edmStructuredType = edmProperty.Type.ToStructuredType(); + } + else + { + return null; + } + } + } + + return null; + } + + private static IEdmProperty FindPropertyOnType(IEdmStructuredType structuredType, string name) + { + IEdmProperty property = structuredType.FindProperty(name); + + if (property == null) + { + property = structuredType.DeclaredProperties.FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + } + + return property; + } } } diff --git a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaJsonWriter.cs b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaJsonWriter.cs index 88ec4bb8dc..36d27b9845 100644 --- a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaJsonWriter.cs +++ b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaJsonWriter.cs @@ -398,12 +398,21 @@ internal override Task WriteDeclaredKeyPropertiesElementHeaderAsync() /// Write the Key property /// /// The Edm Structural Property. - internal override void WritePropertyRefElement(IEdmStructuralProperty property) + internal override void WritePropertyRefElement(IEdmPropertyRef propertyRef) { - // Key properties without a key alias are represented as strings containing the property name. - // Key properties with a key alias are represented as objects with one member whose name is the key alias and whose value is a string containing the path to the property. - // TODO: It seems the second one is not supported. - this.jsonWriter.WriteStringValue(property.Name); + if (propertyRef.PropertyAlias != null && propertyRef.PropertyAlias != propertyRef.Path.Path) + { + // Key properties with a key alias are represented as objects with one member + // whose name is the key alias and whose value is a string containing the path to the property. + this.jsonWriter.WriteStartObject(); + this.jsonWriter.WriteRequiredProperty(propertyRef.PropertyAlias, propertyRef.Path.Path); + this.jsonWriter.WriteEndObject(); + } + else + { + // Key properties without a key alias are represented as strings containing the property name. + this.jsonWriter.WriteStringValue(propertyRef.Path.Path); + } } /// @@ -411,11 +420,11 @@ internal override void WritePropertyRefElement(IEdmStructuralProperty property) /// /// The Edm Structural Property. /// Task represents an asynchronous operation that may or may not return a result. - internal override Task WritePropertyRefElementAsync(IEdmStructuralProperty property) + internal override Task WritePropertyRefElementAsync(IEdmPropertyRef propertyRef) { // Key properties without a key alias are represented as strings containing the property name. // Key properties with a key alias are represented as objects with one member whose name is the key alias and whose value is a string containing the path to the property. - this.jsonWriter.WriteStringValue(property.Name); + WritePropertyRefElement(propertyRef); return Task.CompletedTask; } diff --git a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs index 34e5778e6e..29fc0c3371 100644 --- a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs +++ b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaWriter.cs @@ -84,8 +84,8 @@ internal EdmModelCsdlSchemaWriter(IEdmModel model, Version edmVersion) internal abstract void WriteDeclaredKeyPropertiesElementHeader(); internal abstract Task WriteDeclaredKeyPropertiesElementHeaderAsync(); - internal abstract void WritePropertyRefElement(IEdmStructuralProperty property); - internal abstract Task WritePropertyRefElementAsync(IEdmStructuralProperty property); + internal abstract void WritePropertyRefElement(IEdmPropertyRef propertyRef); + internal abstract Task WritePropertyRefElementAsync(IEdmPropertyRef propertyRef); internal abstract void WriteNavigationPropertyElementHeader(IEdmNavigationProperty property); internal abstract Task WriteNavigationPropertyElementHeaderAsync(IEdmNavigationProperty property); diff --git a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaXmlWriter.cs b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaXmlWriter.cs index a9bc3b6c5c..dc6ab46ae7 100644 --- a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaXmlWriter.cs +++ b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSchemaXmlWriter.cs @@ -400,23 +400,35 @@ internal override Task WriteDeclaredKeyPropertiesElementHeaderAsync() /// /// Writes the PropertyRef element. /// - /// The Edm Structural Property. - internal override void WritePropertyRefElement(IEdmStructuralProperty property) + /// The Edm property ref. + internal override void WritePropertyRefElement(IEdmPropertyRef propertyRef) { this.xmlWriter.WriteStartElement(CsdlConstants.Element_PropertyRef); - this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, property.Name, EdmValueWriter.StringAsXml); + + this.WriteRequiredAttribute(CsdlConstants.Attribute_Name, propertyRef.Path.Path, EdmValueWriter.StringAsXml); + + if (propertyRef.PropertyAlias != null && propertyRef.PropertyAlias != propertyRef.Path.Path) + { + this.WriteRequiredAttribute(CsdlConstants.Attribute_Alias, propertyRef.PropertyAlias, EdmValueWriter.StringAsXml); + } + this.WriteEndElement(); } /// /// Asynchronously writes the PropertyRef element. /// - /// The Edm Structural Property. + /// The Edm property ref. /// Task represents an asynchronous operation. - internal override async Task WritePropertyRefElementAsync(IEdmStructuralProperty property) + internal override async Task WritePropertyRefElementAsync(IEdmPropertyRef propertyRef) { await this.xmlWriter.WriteStartElementAsync(null, CsdlConstants.Element_PropertyRef, null).ConfigureAwait(false); - await this.WriteRequiredAttributeAsync(CsdlConstants.Attribute_Name, property.Name, EdmValueWriter.StringAsXml).ConfigureAwait(false); + await this.WriteRequiredAttributeAsync(CsdlConstants.Attribute_Name, propertyRef.Path.Path, EdmValueWriter.StringAsXml).ConfigureAwait(false); + + if (propertyRef.PropertyAlias != null && propertyRef.PropertyAlias != propertyRef.Path.Path) + { + await this.WriteRequiredAttributeAsync(CsdlConstants.Attribute_Name, propertyRef.PropertyAlias, EdmValueWriter.StringAsXml).ConfigureAwait(false); + } await this.WriteEndElementAsync().ConfigureAwait(false); } diff --git a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs index 2db9e587ed..58fd04a88b 100644 --- a/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs +++ b/src/Microsoft.OData.Edm/Csdl/Serialization/EdmModelCsdlSerializationVisitor.cs @@ -282,9 +282,9 @@ protected override async Task ProcessSingletonAsync(IEdmSingleton element) protected override void ProcessEntityType(IEdmEntityType element) { this.BeginElement(element, this.schemaWriter.WriteEntityTypeElementHeader); - if (element.DeclaredKey != null && element.DeclaredKey.Any()) + if (element.DeclaredKeyRef != null && element.DeclaredKeyRef.Any()) { - this.VisitEntityTypeDeclaredKey(element.DeclaredKey); + this.VisitEntityTypeDeclaredKey(element.DeclaredKeyRef); } this.VisitProperties(element.DeclaredStructuralProperties().Cast()); @@ -299,9 +299,9 @@ protected override void ProcessEntityType(IEdmEntityType element) protected override async Task ProcessEntityTypeAsync(IEdmEntityType element) { await this.BeginElementAsync(element, this.schemaWriter.WriteEntityTypeElementHeaderAsync).ConfigureAwait(false); - if (element.DeclaredKey != null && element.DeclaredKey.Any()) + if (element.DeclaredKeyRef != null && element.DeclaredKeyRef.Any()) { - await this.VisitEntityTypeDeclaredKeyAsync(element.DeclaredKey).ConfigureAwait(false); + await this.VisitEntityTypeDeclaredKeyAsync(element.DeclaredKeyRef).ConfigureAwait(false); } await this.VisitPropertiesAsync(element.DeclaredStructuralProperties().Cast()).ConfigureAwait(false); @@ -1512,7 +1512,7 @@ private async Task ProcessFacetsAsync(IEdmTypeReference element, bool inlineType } } - private void VisitEntityTypeDeclaredKey(IEnumerable keyProperties) + private void VisitEntityTypeDeclaredKey(IEnumerable keyProperties) { this.schemaWriter.WriteDeclaredKeyPropertiesElementHeader(); this.VisitPropertyRefs(keyProperties); @@ -1523,16 +1523,16 @@ private void VisitEntityTypeDeclaredKey(IEnumerable keyP /// Asynchronously visits the entity type declared key. /// /// Collection of Edm structural properties. - private async Task VisitEntityTypeDeclaredKeyAsync(IEnumerable keyProperties) + private async Task VisitEntityTypeDeclaredKeyAsync(IEnumerable keyProperties) { await this.schemaWriter.WriteDeclaredKeyPropertiesElementHeaderAsync().ConfigureAwait(false); await this.VisitPropertyRefsAsync(keyProperties).ConfigureAwait(false); await this.schemaWriter.WriteArrayEndElementAsync().ConfigureAwait(false); } - private void VisitPropertyRefs(IEnumerable properties) + private void VisitPropertyRefs(IEnumerable properties) { - foreach (IEdmStructuralProperty property in properties) + foreach (IEdmPropertyRef property in properties) { this.schemaWriter.WritePropertyRefElement(property); } @@ -1542,9 +1542,9 @@ private void VisitPropertyRefs(IEnumerable properties) /// Asynchronously visits the property references. /// /// Collection of Edm structural properties. - private async Task VisitPropertyRefsAsync(IEnumerable properties) + private async Task VisitPropertyRefsAsync(IEnumerable properties) { - foreach (IEdmStructuralProperty property in properties) + foreach (IEdmPropertyRef property in properties) { await this.schemaWriter.WritePropertyRefElementAsync(property).ConfigureAwait(false); } diff --git a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs index 53e98c6571..ff568a6fe9 100644 --- a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs +++ b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs @@ -36,6 +36,7 @@ internal sealed class EdmRes { internal const string ValueWriter_NonSerializableValue = "ValueWriter_NonSerializableValue"; internal const string ValueHasAlreadyBeenSet = "ValueHasAlreadyBeenSet"; internal const string PathSegmentMustNotContainSlash = "PathSegmentMustNotContainSlash"; + internal const string PropertyPathMustEndingWithCorrectPropertyName = "PropertyPathMustEndingWithCorrectPropertyName"; internal const string Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType = "Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType"; internal const string EdmType_UnexpectedEdmType = "EdmType_UnexpectedEdmType"; internal const string NavigationPropertyBinding_PathIsNotValid = "NavigationPropertyBinding_PathIsNotValid"; @@ -306,6 +307,7 @@ internal sealed class EdmRes { internal const string Bad_UnresolvedParameter = "Bad_UnresolvedParameter"; internal const string Bad_UnresolvedReturn = "Bad_UnresolvedReturn"; internal const string Bad_UnresolvedLabeledElement = "Bad_UnresolvedLabeledElement"; + internal const string Bad_UnresolvedPropertyRef = "Bad_UnresolvedPropertyRef"; internal const string Bad_CyclicEntity = "Bad_CyclicEntity"; internal const string Bad_CyclicComplex = "Bad_CyclicComplex"; internal const string Bad_CyclicEntityContainer = "Bad_CyclicEntityContainer"; diff --git a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj index b6a46ec802..1dfbb684a3 100644 --- a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj +++ b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.csproj @@ -109,4 +109,17 @@ + + + True + True + Microsoft.OData.Edm.tt + + + True + True + Parameterized.Microsoft.OData.Edm.tt + + + diff --git a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt index 0d7155d183..15c7c32333 100644 --- a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt +++ b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt @@ -16,6 +16,7 @@ EdmEntityContainer_CannotUseElementWithTypeNone=An element with type 'None' cann ValueWriter_NonSerializableValue=The value writer cannot write a value of kind '{0}'. ValueHasAlreadyBeenSet=Value has already been set. PathSegmentMustNotContainSlash=Path segments must not contain '/' character. +PropertyPathMustEndingWithCorrectPropertyName=Property Path '{0}' is not ended with '{1}'. Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType=The number of dependent properties must match the number of key properties on the principal entity type. '{0}' principal properties were provided, but {1} dependent properties were provided. EdmType_UnexpectedEdmType=Unexpected Edm type. NavigationPropertyBinding_PathIsNotValid=The navigation property binding path is not valid. @@ -309,6 +310,7 @@ Bad_UnresolvedProperty=The property '{0}' could not be found. Bad_UnresolvedParameter=The parameter '{0}' could not be found. Bad_UnresolvedReturn=The return of operation '{0}' could not be found. Bad_UnresolvedLabeledElement=The labeled element '{0}' could not be found. +Bad_UnresolvedPropertyRef=The property ref on type '{0}' with name '{1}' and alias '{2}' could not be resolved. Bad_CyclicEntity=The entity '{0}' is invalid because its base type is cyclic. Bad_CyclicComplex=The complex type '{0}' is invalid because its base type is cyclic. Bad_CyclicEntityContainer=The entity container '{0}' is invalid because its extends hierarchy is cyclic. diff --git a/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs b/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs index cf46b2b0dd..2ba1496523 100644 --- a/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs +++ b/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs @@ -148,6 +148,14 @@ internal static string PathSegmentMustNotContainSlash } } + /// + /// A string like "Property Path '{0}' is not ended with '{1}'." + /// + internal static string PropertyPathMustEndingWithCorrectPropertyName(object p0, object p1) + { + return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.PropertyPathMustEndingWithCorrectPropertyName, p0, p1); + } + /// /// A string like "The number of dependent properties must match the number of key properties on the principal entity type. '{0}' principal properties were provided, but {1} dependent properties were provided." /// @@ -2458,6 +2466,14 @@ internal static string Bad_UnresolvedLabeledElement(object p0) return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.Bad_UnresolvedLabeledElement, p0); } + /// + /// A string like "The property ref on type '{0}' with name '{1}' and alias '{2}' could not be resolved." + /// + internal static string Bad_UnresolvedPropertyRef(object p0, object p1, object p2) + { + return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.Bad_UnresolvedPropertyRef, p0, p1, p2); + } + /// /// A string like "The entity '{0}' is invalid because its base type is cyclic." /// diff --git a/src/Microsoft.OData.Edm/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Edm/PublicAPI/net8.0/PublicAPI.Unshipped.txt index e69de29bb2..b6187dde42 100644 --- a/src/Microsoft.OData.Edm/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Edm/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -0,0 +1,16 @@ +Microsoft.OData.Edm.EdmEntityType.AddKeys(params Microsoft.OData.Edm.IEdmPropertyRef[] keys) -> void +Microsoft.OData.Edm.EdmEntityType.AddKeys(System.Collections.Generic.IEnumerable keys) -> void +Microsoft.OData.Edm.EdmPropertyRef +Microsoft.OData.Edm.EdmPropertyRef.EdmPropertyRef(Microsoft.OData.Edm.IEdmStructuralProperty property) -> void +Microsoft.OData.Edm.EdmPropertyRef.EdmPropertyRef(Microsoft.OData.Edm.IEdmStructuralProperty property, Microsoft.OData.Edm.IEdmPathExpression path, string alias) -> void +Microsoft.OData.Edm.EdmPropertyRef.EdmPropertyRef(Microsoft.OData.Edm.IEdmStructuralProperty property, string alias) -> void +Microsoft.OData.Edm.EdmPropertyRef.Path.get -> Microsoft.OData.Edm.IEdmPathExpression +Microsoft.OData.Edm.EdmPropertyRef.PropertyAlias.get -> string +Microsoft.OData.Edm.EdmPropertyRef.ReferencedProperty.get -> Microsoft.OData.Edm.IEdmStructuralProperty +Microsoft.OData.Edm.IEdmEntityType.DeclaredKeyRef.get -> System.Collections.Generic.IEnumerable +Microsoft.OData.Edm.IEdmPropertyRef +Microsoft.OData.Edm.IEdmPropertyRef.Path.get -> Microsoft.OData.Edm.IEdmPathExpression +Microsoft.OData.Edm.IEdmPropertyRef.PropertyAlias.get -> string +Microsoft.OData.Edm.IEdmPropertyRef.ReferencedProperty.get -> Microsoft.OData.Edm.IEdmStructuralProperty +Microsoft.OData.Edm.Validation.EdmErrorCode.BadUnresolvedPropertyRef = 412 -> Microsoft.OData.Edm.Validation.EdmErrorCode +virtual Microsoft.OData.Edm.EdmEntityType.DeclaredKeyRef.get -> System.Collections.Generic.IEnumerable \ No newline at end of file diff --git a/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs b/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs index 56b36300b3..b8a7d43c54 100644 --- a/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs +++ b/src/Microsoft.OData.Edm/Schema/EdmEntityType.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace Microsoft.OData.Edm { @@ -19,7 +20,7 @@ public class EdmEntityType : EdmStructuredType, IEdmEntityType, IEdmFullNamedEle private readonly string name; private readonly string fullName; private readonly bool hasStream; - private List declaredKey; + private List declaredKey; /// /// Initializes a new instance of the class. @@ -81,7 +82,12 @@ public EdmEntityType(string namespaceName, string name, IEdmEntityType baseType, /// public virtual IEnumerable DeclaredKey { - get { return this.declaredKey; } + get { return this.declaredKey?.Select(k => k.ReferencedProperty); } + } + + public virtual IEnumerable DeclaredKeyRef + { + get => this.declaredKey; } /// @@ -133,6 +139,24 @@ public bool HasStream get { return hasStream || (this.BaseType != null && this.BaseEntityType().HasStream); } } + public void AddKeys(params IEdmPropertyRef[] keys) + => this.AddKeys((IEnumerable)keys); + + public void AddKeys(IEnumerable keys) + { + EdmUtil.CheckArgumentNull(keys, "keys"); + + foreach (IEdmPropertyRef key in keys) + { + if (this.declaredKey == null) + { + this.declaredKey = new List(); + } + + this.declaredKey.Add(key); + } + } + /// /// Adds the to the key of this entity type. /// @@ -148,17 +172,7 @@ public void AddKeys(params IEdmStructuralProperty[] keyProperties) /// The key properties. public void AddKeys(IEnumerable keyProperties) { - EdmUtil.CheckArgumentNull(keyProperties, "keyProperties"); - - foreach (IEdmStructuralProperty property in keyProperties) - { - if (this.declaredKey == null) - { - this.declaredKey = new List(); - } - - this.declaredKey.Add(property); - } + this.AddKeys(keyProperties?.Select(key => new EdmPropertyRef(key))); } /// diff --git a/src/Microsoft.OData.Edm/Schema/EdmPropertyRef.cs b/src/Microsoft.OData.Edm/Schema/EdmPropertyRef.cs new file mode 100644 index 0000000000..0739580863 --- /dev/null +++ b/src/Microsoft.OData.Edm/Schema/EdmPropertyRef.cs @@ -0,0 +1,78 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System; +using System.Linq; +using Microsoft.OData.Edm.Vocabularies; + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a definition of an EDM property reference element. + /// + public class EdmPropertyRef : IEdmPropertyRef + { + /// + /// Initializes a new instance of the class. + /// + /// The referenced property without an alias provided. + public EdmPropertyRef(IEdmStructuralProperty property) + : this(property, null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The referenced property. + /// The given alias. If it's null, it means to use the property name as the alias. + public EdmPropertyRef(IEdmStructuralProperty property, string alias) + { + EdmUtil.CheckArgumentNull(property, "property"); + + ReferencedProperty = property; + Path = new EdmPropertyPathExpression(property.Name); + PropertyAlias = alias; + } + + /// + /// Initializes a new instance of the class. + /// + /// The referenced property. + /// The path to referenced property. + /// The given alias, it should not be null. + public EdmPropertyRef(IEdmStructuralProperty property, IEdmPathExpression path, string alias) + { + EdmUtil.CheckArgumentNull(property, "property"); + EdmUtil.CheckArgumentNull(path, "path"); + EdmUtil.CheckArgumentNull(alias, "alias"); + + if (path.PathSegments == null || path.PathSegments.LastOrDefault() != property.Name) + { + throw new ArgumentException(Strings.PropertyPathMustEndingWithCorrectPropertyName(path.Path, property.Name)); + } + + ReferencedProperty = property; + Path = path; + PropertyAlias = alias; + } + + /// + /// Gets the referenced property. + /// + public IEdmStructuralProperty ReferencedProperty { get; } + + /// + /// Gets the path to the referenced property. + /// + public IEdmPathExpression Path { get; } + + /// + /// Gets the alias. + /// + public string PropertyAlias { get; } + } +} diff --git a/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs b/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs index 2fe03e2487..8f05c0432c 100644 --- a/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs +++ b/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmEntityType.cs @@ -4,8 +4,9 @@ // //--------------------------------------------------------------------- +using System; using System.Collections.Generic; -using Microsoft.OData.Edm.Vocabularies; +using System.Linq; namespace Microsoft.OData.Edm { @@ -23,5 +24,11 @@ public interface IEdmEntityType : IEdmStructuredType, IEdmSchemaType /// Gets the value indicating whether or not this type is a media entity. /// bool HasStream { get; } + + /// + /// Gets the property refs of the entity type that make up the entity type. + /// In the next major release, should use this to replace the DeclaredKey. + /// + IEnumerable DeclaredKeyRef { get => DeclaredKey.Select(x => new EdmPropertyRef(x)); } } } diff --git a/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPropertyRef.cs b/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPropertyRef.cs new file mode 100644 index 0000000000..bbb104f3ef --- /dev/null +++ b/src/Microsoft.OData.Edm/Schema/Interfaces/IEdmPropertyRef.cs @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +namespace Microsoft.OData.Edm +{ + /// + /// Represents a property reference element. + /// The edm:PropertyRef element MUST contain the Name attribute and MAY contain the Alias attribute. + /// + public interface IEdmPropertyRef : IEdmElement + { + /// + /// Gets the referenced structural property. + /// the leading to a primitive property. + /// + IEdmStructuralProperty ReferencedProperty { get; } + + /// + /// The value of Name is a path expression leading to a primitive property. The names of the properties in the path are joined together by forward slashes. + /// + IEdmPathExpression Path { get; } + + /// + /// Gets the value of Alias + /// + string PropertyAlias { get; } + } +} diff --git a/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs b/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs index 87d59617af..7974fb66a8 100644 --- a/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs +++ b/src/Microsoft.OData.Edm/Validation/EdmErrorCode.cs @@ -1437,5 +1437,10 @@ public enum EdmErrorCode /// A recursive complex-typed property must be optional. /// RecursiveComplexTypedPropertyMustBeOptional, + + /// + /// Could not resolve a property ref with that name + /// + BadUnresolvedPropertyRef, } } diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderJsonTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderJsonTests.cs index 9dd3531659..4ff11bc710 100644 --- a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderJsonTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderJsonTests.cs @@ -23,6 +23,56 @@ namespace Microsoft.OData.Edm.Tests.Csdl { public class CsdlReaderJsonTests { + [Fact] + public void ReadKeyAliasInJsonTest() + { + var jsonCsdl = @"{ + ""$Version"": ""4.0"", + ""NS"": { + ""EntityInfo"": { + ""$Kind"": ""ComplexType"", + ""ID"": { + ""$Type"": ""Edm.Int32"" + }, + ""Created"": { + ""$Type"": ""Edm.DateTimeOffset"", + ""$Nullable"": true, + ""$Precision"": 0 + } + }, + ""Category"": { + ""$Kind"": ""EntityType"", + ""$Key"": [ + ""ID"", + { + ""EntityInfoID"": ""Info/ID"" + } + ], + ""Info"": { + ""$Type"": ""NS.EntityInfo"" + }, + ""Name"": { + ""$Nullable"": true + }, + ""ID"": {} + } + } +}"; + + IEdmModel model = Parse(jsonCsdl); + + // Act & Assert + IEdmEntityType categoryType = model.SchemaElements.OfType().First(); + + // Two "ID" because the referenced property names are "ID" in entity type and complex type. + Assert.Equal(["NS.Category.ID", "NS.EntityInfo.ID"], + categoryType.DeclaredKey.Select(k => $"{k.DeclaringType.FullTypeName()}.{k.Name}")); + + IEnumerable propertyRefs = categoryType.DeclaredKeyRef; + Assert.Equal(["ID", "Info/ID"], propertyRefs.Select(k => k.Path.Path)); + Assert.Equal(["--", "EntityInfoID" ], propertyRefs.Select(k => k.PropertyAlias ?? "--")); + } + [Fact] public void ReadNavigationPropertyPartnerInJsonTest() { diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderTests.cs index cca374d845..a10b36ecb3 100644 --- a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlReaderTests.cs @@ -967,6 +967,45 @@ public void ParsingOptionalParametersShouldSucceed() Assert.Equal("Smith", optionalParamWithDefault.DefaultValueString); } + [Fact] + public void ParsingXmlWithKeyAliasOnPropertyOnComplex() + { + // Arrange + string csdlWithKeyAlias = "" + +"" + +"" + +"" + +"" + +"" + +"" + +"" + +"" + +"" + +"" + +"" + +"" + +"" + +""; + + // Act + IEdmModel model = CsdlReader.Parse(XElement.Parse(csdlWithKeyAlias).CreateReader()); + + // Assert + // There's only one entity type and one complex type + IEdmEntityType entityType = model.SchemaElements.OfType().First(); + IEdmComplexType complexType = model.SchemaElements.OfType().First(); + + IEdmProperty complexIdProperty = complexType.FindProperty("ID"); + + IEdmStructuralProperty keyProperty = Assert.Single(entityType.DeclaredKey); + Assert.Same(complexIdProperty, keyProperty); + + IEdmPropertyRef keyPropertyRef = Assert.Single(entityType.DeclaredKeyRef); + Assert.Same(complexIdProperty, keyPropertyRef.ReferencedProperty); + Assert.Equal("Info/ID", keyPropertyRef.Path.Path); + Assert.Equal("ComID", keyPropertyRef.PropertyAlias); + } + [Theory] [InlineData(EdmVocabularyAnnotationSerializationLocation.Inline)] [InlineData(EdmVocabularyAnnotationSerializationLocation.OutOfLine)] diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlWriterTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlWriterTests.cs index 91fb187097..60f9ad8b97 100644 --- a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlWriterTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/CsdlWriterTests.cs @@ -9,6 +9,8 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Numerics; +using System.Reflection; using System.Text; using System.Text.Encodings.Web; using System.Text.Json; @@ -2155,6 +2157,200 @@ public void CanWriteUrlEscapeFunction() }"); } + [Fact] + public void CanWriteEntityType_WithSimpleKey_WithOrWithoutAlias() + { + // Arrange + EdmModel model = new EdmModel(); + + EdmEntityType entity = new EdmEntityType("NS", "Category"); + IEdmStructuralProperty key1 = entity.AddStructuralProperty("MyKey1", EdmCoreModel.Instance.GetInt32(false)); + IEdmStructuralProperty key2 = entity.AddStructuralProperty("MyKey2", EdmCoreModel.Instance.GetString(false)); + entity.AddKeys(new EdmPropertyRef(key1)); + entity.AddKeys(new EdmPropertyRef(key2, "alias2")); + model.AddElement(entity); + + // Act & Assert for XML + WriteAndVerifyXml(model, "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + + // Act & Assert for JSON + WriteAndVerifyJson(model, @"{ + ""$Version"": ""4.0"", + ""NS"": { + ""Category"": { + ""$Kind"": ""EntityType"", + ""$Key"": [ + ""MyKey1"", + { + ""alias2"": ""MyKey2"" + } + ], + ""MyKey1"": { + ""$Type"": ""Edm.Int32"" + }, + ""MyKey2"": {} + } + } +}"); + } + + [Fact] + public void CanWriteEntityType_WithASimpleKey_ReferencingAPropertyOfAComplex() + { + // Arrange + EdmModel model = new EdmModel(); + EdmComplexType complex = new EdmComplexType("NS", "EntityInfo"); + var key = complex.AddStructuralProperty("ID", EdmCoreModel.Instance.GetInt32(false)); + complex.AddStructuralProperty("Created", EdmCoreModel.Instance.GetDateTimeOffset(true)); + model.AddElement(complex); + + EdmEntityType entity = new EdmEntityType("NS", "Category"); + entity.AddStructuralProperty("Info", new EdmComplexTypeReference(complex, false)); + entity.AddStructuralProperty("Name", EdmCoreModel.Instance.GetString(true)); + model.AddElement(entity); + entity.AddKeys(new EdmPropertyRef(key, new EdmPropertyPathExpression("Info/ID"), "EntityInfoID")); + + // Act & Assert for XML + WriteAndVerifyXml(model, "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + + // Act & Assert for JSON + WriteAndVerifyJson(model, @"{ + ""$Version"": ""4.0"", + ""NS"": { + ""EntityInfo"": { + ""$Kind"": ""ComplexType"", + ""ID"": { + ""$Type"": ""Edm.Int32"" + }, + ""Created"": { + ""$Type"": ""Edm.DateTimeOffset"", + ""$Nullable"": true, + ""$Precision"": 0 + } + }, + ""Category"": { + ""$Kind"": ""EntityType"", + ""$Key"": [ + { + ""EntityInfoID"": ""Info/ID"" + } + ], + ""Info"": { + ""$Type"": ""NS.EntityInfo"" + }, + ""Name"": { + ""$Nullable"": true + } + } + } +}"); + } + + [Fact] + public void CanWriteEntityType_WithAMultipleKeys_ReferencingAPropertyOfAComplex() + { + // Arrange + EdmModel model = new EdmModel(); + EdmComplexType complex = new EdmComplexType("NS", "EntityInfo"); + var key = complex.AddStructuralProperty("ID", EdmCoreModel.Instance.GetInt32(false)); + complex.AddStructuralProperty("Created", EdmCoreModel.Instance.GetDateTimeOffset(true)); + model.AddElement(complex); + + EdmEntityType entity = new EdmEntityType("NS", "Category"); + entity.AddStructuralProperty("Info", new EdmComplexTypeReference(complex, false)); + entity.AddStructuralProperty("Name", EdmCoreModel.Instance.GetString(true)); + var id = entity.AddStructuralProperty("ID", EdmCoreModel.Instance.GetString(false)); + model.AddElement(entity); + entity.AddKeys( + new EdmPropertyRef(id), + new EdmPropertyRef(key, new EdmPropertyPathExpression("Info/ID"), "EntityInfoID")); + + // Act & Assert for XML + WriteAndVerifyXml(model, "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + +""); + + // Act & Assert for JSON + WriteAndVerifyJson(model, @"{ + ""$Version"": ""4.0"", + ""NS"": { + ""EntityInfo"": { + ""$Kind"": ""ComplexType"", + ""ID"": { + ""$Type"": ""Edm.Int32"" + }, + ""Created"": { + ""$Type"": ""Edm.DateTimeOffset"", + ""$Nullable"": true, + ""$Precision"": 0 + } + }, + ""Category"": { + ""$Kind"": ""EntityType"", + ""$Key"": [ + ""ID"", + { + ""EntityInfoID"": ""Info/ID"" + } + ], + ""Info"": { + ""$Type"": ""NS.EntityInfo"" + }, + ""Name"": { + ""$Nullable"": true + }, + ""ID"": {} + } + } +}"); + } + + [Fact] public void CanWriteAnnotationPathExpression() { diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Semantics/CsdlSemanticsNavigationPropertyTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Semantics/CsdlSemanticsNavigationPropertyTests.cs index 7e212eef5a..cb4dc74c7e 100644 --- a/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Semantics/CsdlSemanticsNavigationPropertyTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/Csdl/Semantics/CsdlSemanticsNavigationPropertyTests.cs @@ -37,7 +37,7 @@ public CsdlSemanticsNavigationPropertyTests() var idProperty = new CsdlProperty("ID", new CsdlNamedTypeReference("Edm.Int32", false, null), null, null); var fkProperty = new CsdlProperty("FK", new CsdlNamedTypeReference("Edm.Int32", false, null), null, null); - this.csdlEntityType = new CsdlEntityType("EntityType", null, false, false, false, new CsdlKey(new[] { new CsdlPropertyReference("ID", null) }, null), new[] { idProperty, fkProperty }, new[] { collectionProperty, referenceProperty, navigationWithoutPartner }, null); + this.csdlEntityType = new CsdlEntityType("EntityType", null, false, false, false, new CsdlKey(new[] { new CsdlPropertyReference("ID", null, null) }, null), new[] { idProperty, fkProperty }, new[] { collectionProperty, referenceProperty, navigationWithoutPartner }, null); var csdlSchema = new CsdlSchema("FQ.NS", null, null, new[] { this.csdlEntityType }, Enumerable.Empty(), Enumerable.Empty(),Enumerable.Empty(),Enumerable.Empty(),Enumerable.Empty(), Enumerable.Empty(), null); var csdlModel = new CsdlModel();