-
Notifications
You must be signed in to change notification settings - Fork 352
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
Adding first stage Alternate Key Support #219
Changes from 11 commits
c205707
b7301f3
50dd461
a05c6e1
1afc03e
f1a6b25
ce24e2f
1d2da54
87f1975
09ba4e8
7835431
72d9a3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,21 +41,100 @@ internal KeyBinder(MetadataBinder.QueryTokenVisitor keyValueBindMethod) | |
/// </summary> | ||
/// <param name="collectionNode">Already bound collection node.</param> | ||
/// <param name="namedValues">The named value tokens to bind.</param> | ||
/// <param name="model">The model to be used.</param> | ||
/// <returns>The bound key lookup.</returns> | ||
internal QueryNode BindKeyValues(EntityCollectionNode collectionNode, IEnumerable<NamedValue> namedValues) | ||
internal QueryNode BindKeyValues(EntityCollectionNode collectionNode, IEnumerable<NamedValue> namedValues, IEdmModel model) | ||
{ | ||
Debug.Assert(namedValues != null, "namedValues != null"); | ||
Debug.Assert(collectionNode != null, "CollectionNode != null"); | ||
|
||
IEdmEntityTypeReference collectionItemType = collectionNode.EntityItemType; | ||
List<KeyPropertyValue> keyPropertyValues = new List<KeyPropertyValue>(); | ||
|
||
IEdmEntityType collectionItemEntityType = collectionItemType.EntityDefinition(); | ||
QueryNode keyLookupNode; | ||
|
||
if (TryBindToDeclaredKey(collectionNode, namedValues, model, collectionItemEntityType, out keyLookupNode) == true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. == true is unncessary There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed. |
||
{ | ||
return keyLookupNode; | ||
} | ||
else if (TryBindToDeclaredAlternateKey(collectionNode, namedValues, model, collectionItemEntityType, out keyLookupNode) == true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
{ | ||
return keyLookupNode; | ||
} | ||
else | ||
{ | ||
throw new ODataException(ODataErrorStrings.MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues(collectionNode.ItemType.ODataFullName())); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Tries to bind key values to a key lookup on a collection. | ||
/// </summary> | ||
/// <param name="collectionNode">Already bound collection node.</param> | ||
/// <param name="namedValues">The named value tokens to bind.</param> | ||
/// <param name="model">The model to be used.</param> | ||
/// <param name="collectionItemEntityType">The type of a single item in a collection to apply the key value to.</param> | ||
/// <param name="keyLookupNode">The bound key lookup.</param> | ||
/// <returns>Returns true if binding succeeded.</returns> | ||
private bool TryBindToDeclaredAlternateKey(EntityCollectionNode collectionNode, IEnumerable<NamedValue> namedValues, IEdmModel model, IEdmEntityType collectionItemEntityType, out QueryNode keyLookupNode) | ||
{ | ||
IEnumerable<IDictionary<string, IEdmProperty>> alternateKeys = collectionItemEntityType.DeclaredAlternateKeys(model); | ||
foreach (IDictionary<string, IEdmProperty> keys in alternateKeys) | ||
{ | ||
if (TryBindToKeys(collectionNode, namedValues, model, collectionItemEntityType, keys, out keyLookupNode)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
keyLookupNode = null; | ||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Tries to bind key values to a key lookup on a collection. | ||
/// </summary> | ||
/// <param name="collectionNode">Already bound collection node.</param> | ||
/// <param name="namedValues">The named value tokens to bind.</param> | ||
/// <param name="model">The model to be used.</param> | ||
/// <param name="collectionItemEntityType">The type of a single item in a collection to apply the key value to.</param> | ||
/// <param name="keyLookupNode">The bound key lookup.</param> | ||
/// <returns>Returns true if binding succeeded.</returns> | ||
private bool TryBindToDeclaredKey(EntityCollectionNode collectionNode, IEnumerable<NamedValue> namedValues, IEdmModel model, IEdmEntityType collectionItemEntityType, out QueryNode keyLookupNode) | ||
{ | ||
Dictionary<string, IEdmProperty> keys = new Dictionary<string, IEdmProperty>(); | ||
foreach (IEdmStructuralProperty property in collectionItemEntityType.Key()) | ||
{ | ||
keys[property.Name] = property; | ||
} | ||
|
||
return TryBindToKeys(collectionNode, namedValues, model, collectionItemEntityType, keys, out keyLookupNode); | ||
} | ||
|
||
/// <summary> | ||
/// Binds key values to a key lookup on a collection. | ||
/// </summary> | ||
/// <param name="collectionNode">Already bound collection node.</param> | ||
/// <param name="namedValues">The named value tokens to bind.</param> | ||
/// <param name="model">The model to be used.</param> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why remove this exception? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there is an exception thrown from BindKeyValues to take care of scenario where segment cannot be bound to declared keys. |
||
/// <param name="collectionItemEntityType">The type of a single item in a collection to apply the key value to.</param> | ||
/// <param name="keys">Dictionary of aliases to structural property names for the key.</param> | ||
/// <param name="keyLookupNode">The bound key lookup.</param> | ||
/// <returns>Returns true if binding succeeded.</returns> | ||
private bool TryBindToKeys(EntityCollectionNode collectionNode, IEnumerable<NamedValue> namedValues, IEdmModel model, IEdmEntityType collectionItemEntityType, IDictionary<string, IEdmProperty> keys, out QueryNode keyLookupNode) | ||
{ | ||
List<KeyPropertyValue> keyPropertyValues = new List<KeyPropertyValue>(); | ||
HashSet<string> keyPropertyNames = new HashSet<string>(StringComparer.Ordinal); | ||
foreach (NamedValue namedValue in namedValues) | ||
{ | ||
KeyPropertyValue keyPropertyValue = this.BindKeyPropertyValue(namedValue, collectionItemEntityType); | ||
KeyPropertyValue keyPropertyValue; | ||
|
||
if (!this.TryBindKeyPropertyValue(namedValue, collectionItemEntityType, model, keys, out keyPropertyValue)) | ||
{ | ||
keyLookupNode = null; | ||
return false; | ||
} | ||
|
||
Debug.Assert(keyPropertyValue != null, "keyPropertyValue != null"); | ||
Debug.Assert(keyPropertyValue.KeyProperty != null, "keyPropertyValue.KeyProperty != null"); | ||
|
||
|
@@ -70,15 +149,18 @@ internal QueryNode BindKeyValues(EntityCollectionNode collectionNode, IEnumerabl | |
if (keyPropertyValues.Count == 0) | ||
{ | ||
// No key values specified, for example '/Customers()', do not include the key lookup at all | ||
return collectionNode; | ||
keyLookupNode = collectionNode; | ||
return true; | ||
} | ||
else if (keyPropertyValues.Count != collectionItemEntityType.Key().Count()) | ||
{ | ||
throw new ODataException(ODataErrorStrings.MetadataBinder_NotAllKeyPropertiesSpecifiedInKeyValues(collectionNode.ItemType.ODataFullName())); | ||
keyLookupNode = null; | ||
return false; | ||
} | ||
else | ||
{ | ||
return new KeyLookupNode(collectionNode, new ReadOnlyCollection<KeyPropertyValue>(keyPropertyValues)); | ||
keyLookupNode = new KeyLookupNode(collectionNode, new ReadOnlyCollection<KeyPropertyValue>(keyPropertyValues)); | ||
return true; | ||
} | ||
} | ||
|
||
|
@@ -87,8 +169,11 @@ internal QueryNode BindKeyValues(EntityCollectionNode collectionNode, IEnumerabl | |
/// </summary> | ||
/// <param name="namedValue">The named value to bind.</param> | ||
/// <param name="collectionItemEntityType">The type of a single item in a collection to apply the key value to.</param> | ||
/// <param name="model">The model to be used.</param> | ||
/// <param name="keys">Dictionary of alias to keys.</param> | ||
/// <param name="keyPropertyValue">The bound key property value node.</param> | ||
/// <returns>The bound key property value node.</returns> | ||
private KeyPropertyValue BindKeyPropertyValue(NamedValue namedValue, IEdmEntityType collectionItemEntityType) | ||
private bool TryBindKeyPropertyValue(NamedValue namedValue, IEdmEntityType collectionItemEntityType, IEdmModel model, IDictionary<string, IEdmProperty> keys, out KeyPropertyValue keyPropertyValue) | ||
{ | ||
// These are exception checks because the data comes directly from the potentially user specified tree. | ||
ExceptionUtils.CheckArgumentNotNull(namedValue, "namedValue"); | ||
|
@@ -98,7 +183,7 @@ private KeyPropertyValue BindKeyPropertyValue(NamedValue namedValue, IEdmEntityT | |
IEdmProperty keyProperty = null; | ||
if (namedValue.Name == null) | ||
{ | ||
foreach (IEdmProperty p in collectionItemEntityType.Key()) | ||
foreach (IEdmProperty p in keys.Values) | ||
{ | ||
if (keyProperty == null) | ||
{ | ||
|
@@ -112,11 +197,12 @@ private KeyPropertyValue BindKeyPropertyValue(NamedValue namedValue, IEdmEntityT | |
} | ||
else | ||
{ | ||
keyProperty = collectionItemEntityType.Key().Where(k => string.CompareOrdinal(k.Name, namedValue.Name) == 0).SingleOrDefault(); | ||
keyProperty = keys.Where(k => string.CompareOrdinal(k.Key, namedValue.Name) == 0).SingleOrDefault().Value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keys.SingleOrDefault(...) |
||
|
||
if (keyProperty == null) | ||
{ | ||
throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclaredOrNotKeyInKeyValue(namedValue.Name, collectionItemEntityType.ODataFullName())); | ||
keyPropertyValue = null; | ||
return false; | ||
} | ||
} | ||
|
||
|
@@ -129,11 +215,12 @@ private KeyPropertyValue BindKeyPropertyValue(NamedValue namedValue, IEdmEntityT | |
value = MetadataBindingUtils.ConvertToTypeIfNeeded(value, keyPropertyType); | ||
|
||
Debug.Assert(keyProperty != null, "keyProperty != null"); | ||
return new KeyPropertyValue() | ||
keyPropertyValue = new KeyPropertyValue() | ||
{ | ||
KeyProperty = keyProperty, | ||
KeyValue = value | ||
}; | ||
return true; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
//--------------------------------------------------------------------- | ||
// <copyright file="AlternateKeysODataUriResolver.cs" company="Microsoft"> | ||
// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. | ||
// </copyright> | ||
//--------------------------------------------------------------------- | ||
|
||
namespace Microsoft.OData.Core.UriParser.Metadata | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Globalization; | ||
using System.Linq; | ||
using Microsoft.OData.Core.UriParser.Semantic; | ||
using Microsoft.OData.Core.UriParser.TreeNodeKinds; | ||
using Microsoft.OData.Edm; | ||
|
||
/// <summary> | ||
/// Implementation for resolving a literal value without qualified namespace to enum type. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. modify the summary |
||
/// </summary> | ||
public sealed class AlternateKeysODataUriResolver : ODataUriResolver | ||
{ | ||
/// <summary> | ||
/// Model to be used for resolving the alternate keys. | ||
/// </summary> | ||
private readonly IEdmModel model; | ||
|
||
/// <summary> | ||
/// Constructs a AlternateKeysODataUriResolver with the given edmModel to be used for resolving alternate keys | ||
/// </summary> | ||
/// <param name="model">The model to be used.</param> | ||
public AlternateKeysODataUriResolver(IEdmModel model) | ||
{ | ||
this.model = model; | ||
} | ||
|
||
/// <summary> | ||
/// Resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') | ||
/// Enum value could omit type name prefix using this resolver. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this comment |
||
/// </summary> | ||
/// <param name="type">Type for current entityset.</param> | ||
/// <param name="namedValues">The dictionary of name value pairs.</param> | ||
/// <param name="convertFunc">The convert function to be used for value converting.</param> | ||
/// <returns>The resolved key list.</returns> | ||
public override IEnumerable<KeyValuePair<string, object>> ResolveKeys(IEdmEntityType type, IDictionary<string, string> namedValues, Func<IEdmTypeReference, string, object> convertFunc) | ||
{ | ||
IEnumerable<KeyValuePair<string, object>> convertedPairs; | ||
try | ||
{ | ||
convertedPairs = base.ResolveKeys(type, namedValues, convertFunc); | ||
} | ||
catch (ODataException ex) | ||
{ | ||
if (!TryResolveUsingAlternateKeys(type, namedValues, convertFunc, out convertedPairs)) | ||
{ | ||
throw ex; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} | ||
|
||
return convertedPairs; | ||
} | ||
|
||
/// <summary> | ||
/// Try to resolve alternate keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for certain entity type, ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
/// </summary> | ||
/// <param name="type">Type for current entityset.</param> | ||
/// <param name="namedValues">The dictionary of name value pairs.</param> | ||
/// <param name="convertFunc">The convert function to be used for value converting.</param> | ||
/// <param name="convertedPairs">The resolved key list.</param> | ||
/// <returns>True if resolve succeeded.</returns> | ||
private bool TryResolveUsingAlternateKeys(IEdmEntityType type, IDictionary<string, string> namedValues, Func<IEdmTypeReference, string, object> convertFunc, out IEnumerable<KeyValuePair<string, object>> convertedPairs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better as "TryResolveAlternateKeys()" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree, done. |
||
{ | ||
IEnumerable<IDictionary<string, IEdmProperty>> alternateKeys = type.DeclaredAlternateKeys(model); | ||
foreach (IDictionary<string, IEdmProperty> keys in alternateKeys) | ||
{ | ||
if (TryResolveUsingKeys(type, namedValues, keys, convertFunc, out convertedPairs)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
convertedPairs = null; | ||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Try to resolve keys for certain entity set, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for certain entity type, ... |
||
/// </summary> | ||
/// <param name="type">Type for current entityset.</param> | ||
/// <param name="namedValues">The dictionary of name value pairs.</param> | ||
/// <param name="keyProperties">Dictionary of alias to key properties.</param> | ||
/// <param name="convertFunc">The convert function to be used for value converting.</param> | ||
/// <param name="convertedPairs">The resolved key list.</param> | ||
/// <returns>True if resolve succeeded.</returns> | ||
private bool TryResolveUsingKeys(IEdmEntityType type, IDictionary<string, string> namedValues, IDictionary<string, IEdmProperty> keyProperties, Func<IEdmTypeReference, string, object> convertFunc, out IEnumerable<KeyValuePair<string, object>> convertedPairs) | ||
{ | ||
Dictionary<string, object> pairs = new Dictionary<string, object>(StringComparer.Ordinal); | ||
|
||
foreach (KeyValuePair<string, IEdmProperty> kvp in keyProperties) | ||
{ | ||
string valueText; | ||
|
||
if (EnableCaseInsensitive) | ||
{ | ||
var list = namedValues.Keys.Where(key => string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)).ToList(); | ||
if (list.Count > 1) | ||
{ | ||
throw new ODataException(Strings.UriParserMetadata_MultipleMatchingKeysFound(kvp.Key)); | ||
} | ||
else if (list.Count == 0) | ||
{ | ||
convertedPairs = null; | ||
return false; | ||
} | ||
|
||
valueText = namedValues[list.Single()]; | ||
} | ||
else if (!namedValues.TryGetValue(kvp.Key, out valueText)) | ||
{ | ||
convertedPairs = null; | ||
return false; | ||
} | ||
|
||
object convertedValue = convertFunc(kvp.Value.Type, valueText); | ||
if (convertedValue == null) | ||
{ | ||
convertedPairs = null; | ||
return false; | ||
} | ||
|
||
pairs[kvp.Key] = convertedValue; | ||
} | ||
|
||
convertedPairs = pairs; | ||
return true; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug.Assert(model != null, "model != null");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added.