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

Issue2467: Support Core.AlternateKeys #2470

Merged
merged 9 commits into from
Aug 8, 2022
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
88 changes: 55 additions & 33 deletions src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2146,14 +2146,22 @@ public static IEnumerable<IDictionary<string, IEdmProperty>> GetAlternateKeysAnn
/// <param name="model">The model to be used.</param>
/// <param name="type">Reference to the calling object.</param>
/// <param name="alternateKey">Dictionary of alias and structural properties for the alternate key.</param>
public static void AddAlternateKeyAnnotation(this EdmModel model, IEdmEntityType type, IDictionary<string, IEdmProperty> alternateKey)
/// <param name="useCore">A flag to indicate which alternate term to use.
/// If ture, 'Org.OData.Core.V1.AlternateKeys' is used, otherwise, 'OData.Community.Keys.V1.AlternateKeys' is used.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo

/// </param>
public static void AddAlternateKeyAnnotation(this EdmModel model, IEdmEntityType type, IDictionary<string, IEdmProperty> alternateKey, bool useCore = false)
{
EdmUtil.CheckArgumentNull(model, "model");
EdmUtil.CheckArgumentNull(type, "type");
EdmUtil.CheckArgumentNull(alternateKey, "alternateKey");

IEdmTerm alternateKeysTerm = useCore ? CoreVocabularyModel.AlternateKeysTerm : AlternateKeysVocabularyModel.AlternateKeysTerm;

IEdmComplexType propertyRefType = useCore ? CoreVocabularyModel.PropertyRefType : AlternateKeysVocabularyModel.PropertyRefType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UT and handling of duplicate entries under each annotation namespace?

IEdmComplexType alternateKeyType = useCore ? CoreVocabularyModel.AlternateKeyType : AlternateKeysVocabularyModel.AlternateKeyType;

EdmCollectionExpression annotationValue = null;
var ann = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(type, AlternateKeysVocabularyModel.AlternateKeysTerm).FirstOrDefault();
var ann = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(type, alternateKeysTerm).FirstOrDefault();
if (ann != null)
{
annotationValue = ann.Value as EdmCollectionExpression;
Expand All @@ -2166,21 +2174,21 @@ public static void AddAlternateKeyAnnotation(this EdmModel model, IEdmEntityType
foreach (KeyValuePair<string, IEdmProperty> kvp in alternateKey)
{
IEdmRecordExpression propertyRef = new EdmRecordExpression(
new EdmComplexTypeReference(AlternateKeysVocabularyModel.PropertyRefType, false),
new EdmPropertyConstructor(AlternateKeysVocabularyConstants.PropertyRefTypeAliasPropertyName, new EdmStringConstant(kvp.Key)),
new EdmPropertyConstructor(AlternateKeysVocabularyConstants.PropertyRefTypeNamePropertyName, new EdmPropertyPathExpression(kvp.Value.Name)));
new EdmComplexTypeReference(propertyRefType, false),
new EdmPropertyConstructor("Alias", new EdmStringConstant(kvp.Key)),
new EdmPropertyConstructor("Name", new EdmPropertyPathExpression(kvp.Value.Name)));
propertyRefs.Add(propertyRef);
}

EdmRecordExpression alternateKeyRecord = new EdmRecordExpression(
new EdmComplexTypeReference(AlternateKeysVocabularyModel.AlternateKeyType, false),
new EdmPropertyConstructor(AlternateKeysVocabularyConstants.AlternateKeyTypeKeyPropertyName, new EdmCollectionExpression(propertyRefs)));
new EdmComplexTypeReference(alternateKeyType, false),
new EdmPropertyConstructor("Key", new EdmCollectionExpression(propertyRefs)));

alternateKeysCollection.Add(alternateKeyRecord);

var annotation = new EdmVocabularyAnnotation(
type,
AlternateKeysVocabularyModel.AlternateKeysTerm,
alternateKeysTerm,
new EdmCollectionExpression(alternateKeysCollection));

annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline);
Expand Down Expand Up @@ -3424,42 +3432,56 @@ internal static bool HasAny<T>(this IEnumerable<T> enumerable) where T : class
private static IEnumerable<IDictionary<string, IEdmProperty>> GetDeclaredAlternateKeysForType(IEdmEntityType type, IEdmModel model)
{
IEdmVocabularyAnnotation annotationValue = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(type, AlternateKeysVocabularyModel.AlternateKeysTerm).FirstOrDefault();
IEdmVocabularyAnnotation coreAnnotationValue = model.FindVocabularyAnnotations<IEdmVocabularyAnnotation>(type, CoreVocabularyModel.AlternateKeysTerm).FirstOrDefault();

if (annotationValue != null)
if (annotationValue != null || coreAnnotationValue != null)
{
List<IDictionary<string, IEdmProperty>> declaredAlternateKeys = new List<IDictionary<string, IEdmProperty>>();

IEdmCollectionExpression keys = annotationValue.Value as IEdmCollectionExpression;
Debug.Assert(keys != null, "expected IEdmCollectionExpression for alternate key annotation value");

foreach (IEdmRecordExpression key in keys.Elements.OfType<IEdmRecordExpression>())
Action<IEdmVocabularyAnnotation> retrieveAnnotationAction = ann =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Define a private static function rather than this action closure which will incur an extra allocation for each call.

{
var edmPropertyConstructor = key.Properties.FirstOrDefault(e => e.Name == AlternateKeysVocabularyConstants.AlternateKeyTypeKeyPropertyName);
if (edmPropertyConstructor != null)
if (ann == null)
{
IEdmCollectionExpression collectionExpression = edmPropertyConstructor.Value as IEdmCollectionExpression;
Debug.Assert(collectionExpression != null, "expected IEdmCollectionExpression type for Key Property");

IDictionary<string, IEdmProperty> alternateKey = new Dictionary<string, IEdmProperty>();
foreach (IEdmRecordExpression propertyRef in collectionExpression.Elements.OfType<IEdmRecordExpression>())
{
var aliasProp = propertyRef.Properties.FirstOrDefault(e => e.Name == AlternateKeysVocabularyConstants.PropertyRefTypeAliasPropertyName);
Debug.Assert(aliasProp != null, "expected non null Alias Property");
string alias = ((IEdmStringConstantExpression)aliasProp.Value).Value;

var nameProp = propertyRef.Properties.FirstOrDefault(e => e.Name == AlternateKeysVocabularyConstants.PropertyRefTypeNamePropertyName);
Debug.Assert(nameProp != null, "expected non null Name Property");
string propertyName = ((IEdmPathExpression)nameProp.Value).PathSegments.FirstOrDefault();
return;
}

alternateKey[alias] = type.FindProperty(propertyName);
}
IEdmCollectionExpression keys = ann.Value as IEdmCollectionExpression;
Debug.Assert(keys != null, "expected IEdmCollectionExpression for alternate key annotation value");

if (alternateKey.Any())
foreach (IEdmRecordExpression key in keys.Elements.OfType<IEdmRecordExpression>())
{
var edmPropertyConstructor = key.Properties.FirstOrDefault(e => e.Name == "Key");
if (edmPropertyConstructor != null)
{
declaredAlternateKeys.Add(alternateKey);
IEdmCollectionExpression collectionExpression = edmPropertyConstructor.Value as IEdmCollectionExpression;
Debug.Assert(collectionExpression != null, "expected IEdmCollectionExpression type for Key Property");

IDictionary<string, IEdmProperty> alternateKey = new Dictionary<string, IEdmProperty>();
foreach (IEdmRecordExpression propertyRef in collectionExpression.Elements.OfType<IEdmRecordExpression>())
{
var aliasProp = propertyRef.Properties.FirstOrDefault(e => e.Name == "Alias");
Debug.Assert(aliasProp != null, "expected non null Alias Property");
string alias = ((IEdmStringConstantExpression)aliasProp.Value).Value;

var nameProp = propertyRef.Properties.FirstOrDefault(e => e.Name == "Name");
Debug.Assert(nameProp != null, "expected non null Name Property");
string propertyName = ((IEdmPathExpression)nameProp.Value).PathSegments.FirstOrDefault();

alternateKey[alias] = type.FindProperty(propertyName);
}

if (alternateKey.Any())
{
declaredAlternateKeys.Add(alternateKey);
}
}
}
}
};

// For backwards-compability, we merge the alternate keys from community and core vocabulary annotations.

retrieveAnnotationAction(annotationValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output of GetAlternateKeysAnnotation(this IEdmModel model, IEdmEntityType type) does not honor the unicity requirements from the community vocabulary spec:

Services SHOULD NOT return multiple alternate key definitions for the same entity type that are composed of the exact same set of properties.

The alternate keys are not "merged" as the comment line 3483 calls out, it's instead an union that occurs.

retrieveAnnotationAction(coreAnnotationValue);

return declaredAlternateKeys;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ public static class CoreVocabularyConstants
/// <summary>Org.OData.Core.V1.Revisions</summary>
public const string Revisions = "Org.OData.Core.V1.Revisions";

/// <summary>Org.OData.Core.V1.AlternateKeys</summary>
public const string AlternateKeys = "Org.OData.Core.V1.AlternateKeys";

// <summary>Org.OData.Core.V1.AlternateKey</summary>
public const string AlternateKey = "Org.OData.Core.V1.AlternateKey";

// <summary>Org.OData.Core.V1.PropertyRef</summary>
public const string PropertyRef = "Org.OData.Core.V1.PropertyRef";

/// <summary>Org.OData.Core.V1.xml file suffix</summary>
internal const string VocabularyUrlSuffix = "/Org.OData.Core.V1.xml";
}
Expand Down
17 changes: 17 additions & 0 deletions src/Microsoft.OData.Edm/Vocabularies/CoreVocabularyModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// </copyright>
//---------------------------------------------------------------------

using Microsoft.OData.Edm.Vocabularies.Community.V1;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.OData.Edm.Vocabularies.V1
Expand Down Expand Up @@ -120,5 +121,21 @@ public static class CoreVocabularyModel
/// </summary>
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")]
public static readonly IEdmTerm PermissionsTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.Permissions);

/// <summary>
/// The AlternateKeys term.
/// </summary>
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "EdmTerm is immutable")]
public static readonly IEdmTerm AlternateKeysTerm = VocabularyModelProvider.CoreModel.FindDeclaredTerm(CoreVocabularyConstants.AlternateKeys);

/// <summary>
/// The AlternateKey ComplexType.
/// </summary>
internal static readonly IEdmComplexType AlternateKeyType = VocabularyModelProvider.CoreModel.FindDeclaredType(CoreVocabularyConstants.AlternateKey) as IEdmComplexType;

/// <summary>
/// The PropertyRef ComplexType.
/// </summary>
internal static readonly IEdmComplexType PropertyRefType = VocabularyModelProvider.CoreModel.FindDeclaredType(CoreVocabularyConstants.PropertyRef) as IEdmComplexType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ internal static IEdmModel GetEdmModel()
{"SocialSN", FullyQualifiedNamespacePerson_SSN}
});

// Use Core Vocabulary version.
model.AddAlternateKeyAnnotation(FullyQualifiedNamespacePerson, new Dictionary<string, IEdmProperty>()
{
{"CoreSN", FullyQualifiedNamespacePerson_SSN}
},
true);

model.AddAlternateKeyAnnotation(FullyQualifiedNamespacePerson, new Dictionary<string, IEdmProperty>()
{
{"NameAlias", FullyQualifiedNamespacePerson_Name},
Expand Down Expand Up @@ -1063,6 +1070,20 @@ internal class HardCodedTestModelXml
</Record>
</Collection>
</Annotation>
<Annotation Term=""Org.OData.Core.V1.AlternateKeys"">
<Collection>
<Record Type=""Org.OData.Core.V1.AlternateKey"">
<PropertyValue Property=""Key"">
<Collection>
<Record Type=""Org.OData.Core.V1.PropertyRef"">
<PropertyValue Property=""Alias"" String=""CoreSN"" />
<PropertyValue Property=""Name"" PropertyPath=""SSN"" />
</Record>
</Collection>
</PropertyValue>
</Record>
</Collection>
</Annotation>
</EntityType>
<EntityType Name=""Employee"" BaseType=""Fully.Qualified.Namespace.Person"">
<Property Name=""WorkEmail"" Type=""Edm.String"" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,19 @@ public void AlternateKeyShouldWork()
pathSegment.LastSegment.ShouldBeKeySegment(new KeyValuePair<string, object>("SocialSN", "1"));
}

[Fact]
public void AlternateKeyUsingCoreVocabularyVersionShouldWork()
{
ODataPath pathSegment = new ODataUriParser(HardCodedTestModel.TestModel, new Uri("http://host"), new Uri("http://host/People(CoreSN = \'1\')"))
{
Resolver = new AlternateKeysODataUriResolver(HardCodedTestModel.TestModel)
}.ParsePath();

Assert.Equal(2, pathSegment.Count);
pathSegment.FirstSegment.ShouldBeEntitySetSegment(HardCodedTestModel.TestModel.FindDeclaredEntitySet("People"));
pathSegment.LastSegment.ShouldBeKeySegment(new KeyValuePair<string, object>("CoreSN", "1"));
}

[Fact]
public void CompositeAlternateKeyShouldWork()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1648,7 +1648,7 @@ public sealed class Microsoft.OData.Edm.ExtensionMethods {
[
ExtensionAttribute(),
]
public static void AddAlternateKeyAnnotation (Microsoft.OData.Edm.EdmModel model, Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]] alternateKey)
public static void AddAlternateKeyAnnotation (Microsoft.OData.Edm.EdmModel model, Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]] alternateKey, params bool useCore)

[
ExtensionAttribute(),
Expand Down Expand Up @@ -4169,6 +4169,8 @@ public sealed class Microsoft.OData.Edm.Vocabularies.V1.CapabilitiesVocabularyMo

public sealed class Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyConstants {
public static string AcceptableMediaTypes = "Org.OData.Core.V1.AcceptableMediaTypes"
public static string AlternateKey = "Org.OData.Core.V1.AlternateKey"
public static string AlternateKeys = "Org.OData.Core.V1.AlternateKeys"
public static string Computed = "Org.OData.Core.V1.Computed"
public static string ConventionalIDs = "Org.OData.Core.V1.ConventionalIDs"
public static string CoreNamespace = "Org.OData.Core.V1"
Expand All @@ -4183,13 +4185,15 @@ public sealed class Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyConstants
public static string OptimisticConcurrency = "Org.OData.Core.V1.OptimisticConcurrency"
public static string OptionalParameter = "Org.OData.Core.V1.OptionalParameter"
public static string Permissions = "Org.OData.Core.V1.Permissions"
public static string PropertyRef = "Org.OData.Core.V1.PropertyRef"
public static string RequiresType = "Org.OData.Core.V1.RequiresType"
public static string ResourcePath = "Org.OData.Core.V1.ResourcePath"
public static string Revisions = "Org.OData.Core.V1.Revisions"
}

public sealed class Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyModel {
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm AcceptableMediaTypesTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm AlternateKeysTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm ComputedTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm ConcurrencyTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm ConventionalIDsTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1648,7 +1648,7 @@ public sealed class Microsoft.OData.Edm.ExtensionMethods {
[
ExtensionAttribute(),
]
public static void AddAlternateKeyAnnotation (Microsoft.OData.Edm.EdmModel model, Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]] alternateKey)
public static void AddAlternateKeyAnnotation (Microsoft.OData.Edm.EdmModel model, Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]] alternateKey, params bool useCore)

[
ExtensionAttribute(),
Expand Down Expand Up @@ -4169,6 +4169,8 @@ public sealed class Microsoft.OData.Edm.Vocabularies.V1.CapabilitiesVocabularyMo

public sealed class Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyConstants {
public static string AcceptableMediaTypes = "Org.OData.Core.V1.AcceptableMediaTypes"
public static string AlternateKey = "Org.OData.Core.V1.AlternateKey"
public static string AlternateKeys = "Org.OData.Core.V1.AlternateKeys"
public static string Computed = "Org.OData.Core.V1.Computed"
public static string ConventionalIDs = "Org.OData.Core.V1.ConventionalIDs"
public static string CoreNamespace = "Org.OData.Core.V1"
Expand All @@ -4183,13 +4185,15 @@ public sealed class Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyConstants
public static string OptimisticConcurrency = "Org.OData.Core.V1.OptimisticConcurrency"
public static string OptionalParameter = "Org.OData.Core.V1.OptionalParameter"
public static string Permissions = "Org.OData.Core.V1.Permissions"
public static string PropertyRef = "Org.OData.Core.V1.PropertyRef"
public static string RequiresType = "Org.OData.Core.V1.RequiresType"
public static string ResourcePath = "Org.OData.Core.V1.ResourcePath"
public static string Revisions = "Org.OData.Core.V1.Revisions"
}

public sealed class Microsoft.OData.Edm.Vocabularies.V1.CoreVocabularyModel {
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm AcceptableMediaTypesTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm AlternateKeysTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm ComputedTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm ConcurrencyTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
public static readonly Microsoft.OData.Edm.Vocabularies.IEdmTerm ConventionalIDsTerm = Microsoft.OData.Edm.Csdl.CsdlSemantics.CsdlSemanticsTerm
Expand Down
Loading