From 66c9ade2b48d2fb7a5a353d2fc70346126c9e18d Mon Sep 17 00:00:00 2001 From: Garrett DeBruin Date: Wed, 17 Aug 2022 17:43:15 -0700 Subject: [PATCH 1/2] allow configuring alternate key vocabulary and address performance concerns --- .../Microsoft.OData.Core.cs | 1 + .../Microsoft.OData.Core.txt | 1 + .../Parameterized.Microsoft.OData.Core.cs | 8 +++ .../Resolver/AlternateKeysODataUriResolver.cs | 33 ++++++++- .../ExtensionMethods/ExtensionMethods.cs | 66 ++++++++++++------ .../Microsoft.OData.Edm.cs | 1 + .../Microsoft.OData.Edm.txt | 1 + .../Parameterized.Microsoft.OData.Edm.cs | 8 +++ .../UriParser/ODataUriParserTests.cs | 67 +++++++++++++++++++ .../ExtensionMethods/ExtensionMethodTests.cs | 19 ++++++ .../Microsoft.OData.PublicApi.net45.bsl | 6 ++ ...crosoft.OData.PublicApi.netstandard1.1.bsl | 6 ++ ...crosoft.OData.PublicApi.netstandard2.0.bsl | 6 ++ 13 files changed, 203 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs index d1412751c7..6f86efea4c 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.cs @@ -730,6 +730,7 @@ internal sealed class TextRes { internal const string UriParser_ExpandDepthExceeded = "UriParser_ExpandDepthExceeded"; internal const string UriParser_TypeInvalidForSelectExpand = "UriParser_TypeInvalidForSelectExpand"; internal const string UriParser_ContextHandlerCanNotBeNull = "UriParser_ContextHandlerCanNotBeNull"; + internal const string UriParser_NullAlternateKeyTerm = "UriParser_NullAlternateKeyTerm"; internal const string UriParserMetadata_MultipleMatchingPropertiesFound = "UriParserMetadata_MultipleMatchingPropertiesFound"; internal const string UriParserMetadata_MultipleMatchingNavigationSourcesFound = "UriParserMetadata_MultipleMatchingNavigationSourcesFound"; internal const string UriParserMetadata_MultipleMatchingTypesFound = "UriParserMetadata_MultipleMatchingTypesFound"; diff --git a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt index fc88bb09b3..160f4b3110 100644 --- a/src/Microsoft.OData.Core/Microsoft.OData.Core.txt +++ b/src/Microsoft.OData.Core/Microsoft.OData.Core.txt @@ -840,6 +840,7 @@ UriParser_ExpandCountExceeded=The result of parsing $expand contained at least { UriParser_ExpandDepthExceeded=The result of parsing $expand was at least {0} items deep, but the maximum allowed is {1}. UriParser_TypeInvalidForSelectExpand=The type '{0}' is not valid for $select or $expand, only structured types are allowed. UriParser_ContextHandlerCanNotBeNull=The handler property for context '{0}' should not return null. +UriParser_NullAlternateKeyTerm=A null term was provided in the '{0}' parameter UriParserMetadata_MultipleMatchingPropertiesFound=More than one properties match the name '{0}' were found in type '{1}'. UriParserMetadata_MultipleMatchingNavigationSourcesFound=More than one navigation sources match the name '{0}' were found in model. UriParserMetadata_MultipleMatchingTypesFound=More than one types match the name '{0}' were found in model. diff --git a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs index fa27bc4b55..3e8f59233f 100644 --- a/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs +++ b/src/Microsoft.OData.Core/Parameterized.Microsoft.OData.Core.cs @@ -6372,6 +6372,14 @@ internal static string UriParser_ContextHandlerCanNotBeNull(object p0) return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_ContextHandlerCanNotBeNull, p0); } + /// + /// A string like "A null term was provided in the '{0}' parameter" + /// + internal static string UriParser_NullAlternateKeyTerm(object p0) + { + return Microsoft.OData.TextRes.GetString(Microsoft.OData.TextRes.UriParser_NullAlternateKeyTerm, p0); + } + /// /// A string like "More than one properties match the name '{0}' were found in type '{1}'." /// diff --git a/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs b/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs index e5c70fbd7c..230e473206 100644 --- a/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs +++ b/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs @@ -10,6 +10,9 @@ namespace Microsoft.OData.UriParser using System.Collections.Generic; using System.Linq; using Microsoft.OData.Edm; + using Microsoft.OData.Edm.Vocabularies; + using Microsoft.OData.Edm.Vocabularies.Community.V1; + using Microsoft.OData.Edm.Vocabularies.V1; /// /// Implementation for resolving the alternate keys. @@ -21,13 +24,41 @@ public sealed class AlternateKeysODataUriResolver : ODataUriResolver /// private readonly IEdmModel model; + /// + /// The s that are used within to represent alternate key annotations + /// + private readonly IEnumerable alternateKeyTerms; + /// /// Constructs a AlternateKeysODataUriResolver with the given edmModel to be used for resolving alternate keys /// /// The model to be used. public AlternateKeysODataUriResolver(IEdmModel model) + : this(model, new[] { AlternateKeysVocabularyModel.AlternateKeysTerm, CoreVocabularyModel.AlternateKeysTerm }) + { + } + + /// + /// Constructs a AlternateKeysODataUriResolver with the given edmModel to be used for resolving alternate keys + /// + /// The model to be used. + /// The s that are used within to represent alternate key annotations + /// Thrown if is + /// Thrown if contains any values + public AlternateKeysODataUriResolver(IEdmModel model, IEnumerable alternateKeyTerms) { + if (alternateKeyTerms == null) + { + throw new ArgumentNullException(nameof(alternateKeyTerms)); + } + + if (alternateKeyTerms.Where(term => term == null).Any()) + { + throw new ArgumentException(Strings.UriParser_NullAlternateKeyTerm(nameof(alternateKeyTerms))); + } + this.model = model; + this.alternateKeyTerms = alternateKeyTerms; } /// @@ -62,7 +93,7 @@ public override IEnumerable> ResolveKeys(IEdmEntity /// True if resolve succeeded. private bool TryResolveAlternateKeys(IEdmEntityType type, IDictionary namedValues, Func convertFunc, out IEnumerable> convertedPairs) { - IEnumerable> alternateKeys = model.GetAlternateKeysAnnotation(type); + IEnumerable> alternateKeys = model.GetAlternateKeysAnnotation(type, this.alternateKeyTerms); foreach (IDictionary keys in alternateKeys) { if (TryResolveKeys(type, namedValues, keys, convertFunc, out convertedPairs)) diff --git a/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs b/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs index a69c3bf04e..22af269b1b 100644 --- a/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs +++ b/src/Microsoft.OData.Edm/ExtensionMethods/ExtensionMethods.cs @@ -2155,14 +2155,37 @@ public static bool IsKey(this IEdmProperty property) /// Reference to the calling object. /// Alternate Keys of this type. public static IEnumerable> GetAlternateKeysAnnotation(this IEdmModel model, IEdmEntityType type) + { + return GetAlternateKeysAnnotation(model, type, new[] { AlternateKeysVocabularyModel.AlternateKeysTerm, CoreVocabularyModel.AlternateKeysTerm }); + } + + /// + /// Gets the declared alternate keys of the most defined entity with a declared key present. + /// + /// The model to be used. + /// Reference to the calling object. + /// The s that are used within to represent alternate key annotations + /// Alternate Keys of this type. + /// + /// Thrown if or or is + /// + /// Thrown if contains any values + public static IEnumerable> GetAlternateKeysAnnotation( + this IEdmModel model, + IEdmEntityType type, + IEnumerable alternateKeyTerms) { EdmUtil.CheckArgumentNull(model, "model"); EdmUtil.CheckArgumentNull(type, "type"); + EdmUtil.CheckArgumentNull(alternateKeyTerms, nameof(alternateKeyTerms)); IEdmEntityType checkingType = type; while (checkingType != null) { - IEnumerable> declaredAlternateKeys = GetDeclaredAlternateKeysForType(checkingType, model); + IEnumerable> declaredAlternateKeys = GetDeclaredAlternateKeysForType( + checkingType, + model, + alternateKeyTerms); if (declaredAlternateKeys != null) { return declaredAlternateKeys; @@ -3462,24 +3485,31 @@ internal static bool HasAny(this IEnumerable enumerable) where T : class /// /// Reference to the calling object. /// The model to be used. + /// + /// The s that are used within to represent alternate key annotations; assumed to not be + /// /// Alternate Keys of this type. - private static IEnumerable> GetDeclaredAlternateKeysForType(IEdmEntityType type, IEdmModel model) + /// Thrown if contains any values + private static IEnumerable> GetDeclaredAlternateKeysForType( + IEdmEntityType type, + IEdmModel model, + IEnumerable alternateKeyTerms) { - IEdmVocabularyAnnotation annotationValue = model.FindVocabularyAnnotations(type, AlternateKeysVocabularyModel.AlternateKeysTerm).FirstOrDefault(); - IEdmVocabularyAnnotation coreAnnotationValue = model.FindVocabularyAnnotations(type, CoreVocabularyModel.AlternateKeysTerm).FirstOrDefault(); - - if (annotationValue != null || coreAnnotationValue != null) + var any = false; + var declaredAlternateKeys = new List>(); + foreach (var alternateKeyTerm in alternateKeyTerms) { - List> declaredAlternateKeys = new List>(); + if (alternateKeyTerm == null) + { + throw new ArgumentException(Strings.NullTermForAlternateKey(nameof(alternateKeyTerms))); + } - Action retrieveAnnotationAction = ann => + var annotationValue = model.FindVocabularyAnnotations(type, alternateKeyTerm).FirstOrDefault(); + if (annotationValue != null) { - if (ann == null) - { - return; - } + any = true; - IEdmCollectionExpression keys = ann.Value as IEdmCollectionExpression; + IEdmCollectionExpression keys = annotationValue.Value as IEdmCollectionExpression; Debug.Assert(keys != null, "expected IEdmCollectionExpression for alternate key annotation value"); foreach (IEdmRecordExpression key in keys.Elements.OfType()) @@ -3510,13 +3540,11 @@ private static IEnumerable> GetDeclaredAlterna } } } - }; - - // For backwards-compability, we merge the alternate keys from community and core vocabulary annotations. - - retrieveAnnotationAction(annotationValue); - retrieveAnnotationAction(coreAnnotationValue); + } + } + if (any) + { return declaredAlternateKeys; } diff --git a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs index 790f3b631f..1a0f682870 100644 --- a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs +++ b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.cs @@ -40,6 +40,7 @@ internal sealed class EdmRes { internal const string EdmType_UnexpectedEdmType = "EdmType_UnexpectedEdmType"; internal const string NavigationPropertyBinding_PathIsNotValid = "NavigationPropertyBinding_PathIsNotValid"; internal const string MultipleMatchingPropertiesFound = "MultipleMatchingPropertiesFound"; + internal const string NullTermForAlternateKey = "NullTermForAlternateKey"; internal const string Edm_Evaluator_NoTermTypeAnnotationOnType = "Edm_Evaluator_NoTermTypeAnnotationOnType"; internal const string Edm_Evaluator_NoValueAnnotationOnType = "Edm_Evaluator_NoValueAnnotationOnType"; internal const string Edm_Evaluator_NoValueAnnotationOnElement = "Edm_Evaluator_NoValueAnnotationOnElement"; diff --git a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt index 2252bf036f..faa4ec90a3 100644 --- a/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt +++ b/src/Microsoft.OData.Edm/Microsoft.OData.Edm.txt @@ -20,6 +20,7 @@ Constructable_DependentPropertyCountMustMatchNumberOfPropertiesOnPrincipalType=T EdmType_UnexpectedEdmType=Unexpected Edm type. NavigationPropertyBinding_PathIsNotValid=The navigation property binding path is not valid. MultipleMatchingPropertiesFound=More than one properties match the name '{0}' were found in type '{1}'. +NullTermForAlternateKey=A null term was provided in the '{0}' parameter when enumerating alternate keys on a type ; Evaluation messages Edm_Evaluator_NoTermTypeAnnotationOnType=Type '{0}' must have a single type annotation with term type '{1}'. diff --git a/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs b/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs index 13ce0dfa57..8c0114ad9a 100644 --- a/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs +++ b/src/Microsoft.OData.Edm/Parameterized.Microsoft.OData.Edm.cs @@ -186,6 +186,14 @@ internal static string MultipleMatchingPropertiesFound(object p0, object p1) return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.MultipleMatchingPropertiesFound, p0, p1); } + /// + /// A string like "A null term was provided in the '{0}' parameter when enumerating alternate keys on a type" + /// + internal static string NullTermForAlternateKey(object p0) + { + return Microsoft.OData.Edm.EdmRes.GetString(Microsoft.OData.Edm.EdmRes.NullTermForAlternateKey, p0); + } + /// /// A string like "Type '{0}' must have a single type annotation with term type '{1}'." /// diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs index a412117b71..83e22ca22a 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/UriParser/ODataUriParserTests.cs @@ -12,6 +12,7 @@ using Microsoft.OData.Edm.Csdl; using Microsoft.OData.Edm.Vocabularies; using Microsoft.OData.Edm.Vocabularies.Community.V1; +using Microsoft.OData.Edm.Vocabularies.V1; using Microsoft.OData.UriParser; using Xunit; using ODataErrorStrings = Microsoft.OData.Strings; @@ -405,6 +406,72 @@ public void AlternateKeyUsingCoreVocabularyVersionShouldWork() pathSegment.LastSegment.ShouldBeKeySegment(new KeyValuePair("CoreSN", "1")); } + [Fact] + public void CoreAlternateKeyNotFound() + { + var parser = new ODataUriParser(HardCodedTestModel.TestModel, new Uri("http://host"), new Uri("http://host/People(CoreSN = \'1\')")) + { + Resolver = new AlternateKeysODataUriResolver(HardCodedTestModel.TestModel, new[] { AlternateKeysVocabularyModel.AlternateKeysTerm }), + }; + var exception = Assert.Throws(() => parser.ParsePath()); + Assert.Contains("The key in the request URI is not valid", exception.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void CommunityAlternateKeyNotFound() + { + var parser = new ODataUriParser(HardCodedTestModel.TestModel, new Uri("http://host"), new Uri("http://host/People(SocialSN = \'1\')")) + { + Resolver = new AlternateKeysODataUriResolver(HardCodedTestModel.TestModel, new[] { CoreVocabularyModel.AlternateKeysTerm }), + }; + var exception = Assert.Throws(() => parser.ParsePath()); + Assert.Contains("The key in the request URI is not valid", exception.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void SideBySideAlternateKeys() + { + var resolver = new AlternateKeysODataUriResolver(HardCodedTestModel.TestModel); + + // check for a community alternate key + var communityPath = new ODataUriParser(HardCodedTestModel.TestModel, new Uri("http://host"), new Uri("http://host/People(SocialSN = \'1\')")) + { + Resolver = resolver, + }.ParsePath(); + + Assert.Equal(2, communityPath.Count); + communityPath.FirstSegment.ShouldBeEntitySetSegment(HardCodedTestModel.TestModel.FindDeclaredEntitySet("People")); + communityPath.LastSegment.ShouldBeKeySegment(new KeyValuePair("SocialSN", "1")); + + // check for a core alternate key + var corePath = new ODataUriParser(HardCodedTestModel.TestModel, new Uri("http://host"), new Uri("http://host/People(CoreSN = \'1\')")) + { + Resolver = resolver + }.ParsePath(); + + Assert.Equal(2, corePath.Count); + corePath.FirstSegment.ShouldBeEntitySetSegment(HardCodedTestModel.TestModel.FindDeclaredEntitySet("People")); + corePath.LastSegment.ShouldBeKeySegment(new KeyValuePair("CoreSN", "1")); + } + + [Fact] + public void AlternateKeyResolverWithNullTerms() + { + Assert.Throws(() => new AlternateKeysODataUriResolver(HardCodedTestModel.TestModel, null)); + } + + [Fact] + public void AlternateKeyResolverWithNullTerm() + { + Assert.Throws(() => new AlternateKeysODataUriResolver( + HardCodedTestModel.TestModel, + new IEdmTerm[] + { + CoreVocabularyModel.AlternateKeysTerm, + null, + })); + } + [Fact] public void CompositeAlternateKeyShouldWork() { diff --git a/test/FunctionalTests/Microsoft.OData.Edm.Tests/ExtensionMethods/ExtensionMethodTests.cs b/test/FunctionalTests/Microsoft.OData.Edm.Tests/ExtensionMethods/ExtensionMethodTests.cs index 8913208cf0..6f84c01810 100644 --- a/test/FunctionalTests/Microsoft.OData.Edm.Tests/ExtensionMethods/ExtensionMethodTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Edm.Tests/ExtensionMethods/ExtensionMethodTests.cs @@ -11,6 +11,7 @@ using Microsoft.OData.Edm.Csdl.CsdlSemantics; using Microsoft.OData.Edm.Csdl.Parsing.Ast; using Microsoft.OData.Edm.Vocabularies; +using Microsoft.OData.Edm.Vocabularies.V1; using Microsoft.OData.Edm.Tests.Validation; using Microsoft.OData.Edm.Validation; using Xunit; @@ -180,6 +181,24 @@ public void FullTypeNameAndFullNameIEdmTypeReferenceShouldBeEqual() Assert.Equal(enumType.Definition.FullTypeName(), enumType.FullName()); } + [Fact] + public void GetAlternateKeyAnnotationsWithNullTerms() + { + Assert.Throws(() => EdmCoreModel.Instance.GetAlternateKeysAnnotation(EdmCoreModelEntityType.Instance, null)); + } + + [Fact] + public void GetAlternateKeyAnnotationsWithNullTerm() + { + Assert.Throws(() => EdmCoreModel.Instance.GetAlternateKeysAnnotation( + EdmCoreModelEntityType.Instance, + new IEdmTerm[] + { + CoreVocabularyModel.AlternateKeysTerm, + null, + })); + } + #region IEdmType FullName tests [Fact] diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl index 9be128dd61..3d70d71a5b 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl @@ -1975,6 +1975,11 @@ public sealed class Microsoft.OData.Edm.ExtensionMethods { ] public static System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]]]] GetAlternateKeysAnnotation (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmEntityType type) + [ + ExtensionAttribute(), + ] + public static System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]]]] GetAlternateKeysAnnotation (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IEnumerable`1[[Microsoft.OData.Edm.Vocabularies.IEdmTerm]] alternateKeyTerms) + [ ExtensionAttribute(), ] @@ -6621,6 +6626,7 @@ public sealed class Microsoft.OData.UriParser.AllToken : Microsoft.OData.UriPars public sealed class Microsoft.OData.UriParser.AlternateKeysODataUriResolver : Microsoft.OData.UriParser.ODataUriResolver { public AlternateKeysODataUriResolver (Microsoft.OData.Edm.IEdmModel model) + public AlternateKeysODataUriResolver (Microsoft.OData.Edm.IEdmModel model, System.Collections.Generic.IEnumerable`1[[Microsoft.OData.Edm.Vocabularies.IEdmTerm]] alternateKeyTerms) public virtual System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] ResolveKeys (Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IDictionary`2[[System.String],[System.String]] namedValues, System.Func`3[[Microsoft.OData.Edm.IEdmTypeReference],[System.String],[System.Object]] convertFunc) } diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl index bd335d16f3..e00a383faf 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl @@ -1975,6 +1975,11 @@ public sealed class Microsoft.OData.Edm.ExtensionMethods { ] public static System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]]]] GetAlternateKeysAnnotation (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmEntityType type) + [ + ExtensionAttribute(), + ] + public static System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]]]] GetAlternateKeysAnnotation (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IEnumerable`1[[Microsoft.OData.Edm.Vocabularies.IEdmTerm]] alternateKeyTerms) + [ ExtensionAttribute(), ] @@ -6621,6 +6626,7 @@ public sealed class Microsoft.OData.UriParser.AllToken : Microsoft.OData.UriPars public sealed class Microsoft.OData.UriParser.AlternateKeysODataUriResolver : Microsoft.OData.UriParser.ODataUriResolver { public AlternateKeysODataUriResolver (Microsoft.OData.Edm.IEdmModel model) + public AlternateKeysODataUriResolver (Microsoft.OData.Edm.IEdmModel model, System.Collections.Generic.IEnumerable`1[[Microsoft.OData.Edm.Vocabularies.IEdmTerm]] alternateKeyTerms) public virtual System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] ResolveKeys (Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IDictionary`2[[System.String],[System.String]] namedValues, System.Func`3[[Microsoft.OData.Edm.IEdmTypeReference],[System.String],[System.Object]] convertFunc) } diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl index 9be128dd61..3d70d71a5b 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl @@ -1975,6 +1975,11 @@ public sealed class Microsoft.OData.Edm.ExtensionMethods { ] public static System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]]]] GetAlternateKeysAnnotation (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmEntityType type) + [ + ExtensionAttribute(), + ] + public static System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.IDictionary`2[[System.String],[Microsoft.OData.Edm.IEdmProperty]]]] GetAlternateKeysAnnotation (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IEnumerable`1[[Microsoft.OData.Edm.Vocabularies.IEdmTerm]] alternateKeyTerms) + [ ExtensionAttribute(), ] @@ -6621,6 +6626,7 @@ public sealed class Microsoft.OData.UriParser.AllToken : Microsoft.OData.UriPars public sealed class Microsoft.OData.UriParser.AlternateKeysODataUriResolver : Microsoft.OData.UriParser.ODataUriResolver { public AlternateKeysODataUriResolver (Microsoft.OData.Edm.IEdmModel model) + public AlternateKeysODataUriResolver (Microsoft.OData.Edm.IEdmModel model, System.Collections.Generic.IEnumerable`1[[Microsoft.OData.Edm.Vocabularies.IEdmTerm]] alternateKeyTerms) public virtual System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] ResolveKeys (Microsoft.OData.Edm.IEdmEntityType type, System.Collections.Generic.IDictionary`2[[System.String],[System.String]] namedValues, System.Func`3[[Microsoft.OData.Edm.IEdmTypeReference],[System.String],[System.Object]] convertFunc) } From 6810609f85e8898e5f4b8d5e738a5c8518da4684 Mon Sep 17 00:00:00 2001 From: Garrett DeBruin Date: Mon, 22 Aug 2022 07:56:25 -0700 Subject: [PATCH 2/2] added a line to duplicate the alternate key terms sequence so that we can guarantee preconditions --- .../UriParser/Resolver/AlternateKeysODataUriResolver.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs b/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs index 230e473206..fa47b01dfc 100644 --- a/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs +++ b/src/Microsoft.OData.Core/UriParser/Resolver/AlternateKeysODataUriResolver.cs @@ -52,13 +52,14 @@ public AlternateKeysODataUriResolver(IEdmModel model, IEnumerable alte throw new ArgumentNullException(nameof(alternateKeyTerms)); } - if (alternateKeyTerms.Where(term => term == null).Any()) + this.alternateKeyTerms = alternateKeyTerms.ToList(); + + if (this.alternateKeyTerms.Where(term => term == null).Any()) { throw new ArgumentException(Strings.UriParser_NullAlternateKeyTerm(nameof(alternateKeyTerms))); } this.model = model; - this.alternateKeyTerms = alternateKeyTerms; } ///