Skip to content

Commit

Permalink
Issue OData#55: support alternate key. PR: OData#219, OData#250.
Browse files Browse the repository at this point in the history
For alternate key Protocol,
Please refer to: OData/vocabularies#9
  • Loading branch information
abkmr authored and Sagar Hotchandani committed Jul 9, 2015
1 parent 8ab24fe commit 8aca79a
Show file tree
Hide file tree
Showing 24 changed files with 893 additions and 36 deletions.
6 changes: 5 additions & 1 deletion src/CodeGen/ODataT4CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace Microsoft.OData.Client.Design.T4
using Microsoft.OData.Edm.Library;
using Microsoft.OData.Edm.Values;
using Microsoft.OData.Edm.Vocabularies.V1;
using Microsoft.OData.Edm.Vocabularies.Community.V1;
using System.Text;
using System.Net;

Expand Down Expand Up @@ -993,7 +994,10 @@ private static IEnumerable<T> GetElementsFromModelTree<T>(IEdmModel mainModel, F
ret.AddRange(getElementFromOneModelFunc(mainModel));
foreach (var tmp in mainModel.ReferencedModels)
{
if (tmp is EdmCoreModel || tmp.FindDeclaredValueTerm(CoreVocabularyConstants.OptimisticConcurrencyControl) != null || tmp.FindDeclaredValueTerm(CapabilitiesVocabularyConstants.ChangeTracking) != null)
if (tmp is EdmCoreModel ||
tmp.FindDeclaredValueTerm(CoreVocabularyConstants.OptimisticConcurrencyControl) != null ||
tmp.FindDeclaredValueTerm(CapabilitiesVocabularyConstants.ChangeTracking) != null ||
tmp.FindDeclaredValueTerm(CommunityVocabularyConstants.AlternateKeys) != null)
{
continue;
}
Expand Down
6 changes: 5 additions & 1 deletion src/CodeGen/ODataT4CodeGenerator.ttinclude
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
<#@ Import Namespace="Microsoft.OData.Edm.Library" #>
<#@ Import Namespace="Microsoft.OData.Edm.Values" #>
<#@ Import Namespace="Microsoft.OData.Edm.Vocabularies.V1" #>
<#@ Import Namespace="Microsoft.OData.Edm.Vocabularies.Community.V1" #>
<#@ Import Namespace="System.Text"#>
<#@ Import Namespace="System.Net"#>
<#
Expand Down Expand Up @@ -886,7 +887,10 @@ public class CodeGenerationContext
ret.AddRange(getElementFromOneModelFunc(mainModel));
foreach (var tmp in mainModel.ReferencedModels)
{
if (tmp is EdmCoreModel || tmp.FindDeclaredValueTerm(CoreVocabularyConstants.OptimisticConcurrencyControl) != null || tmp.FindDeclaredValueTerm(CapabilitiesVocabularyConstants.ChangeTracking) != null)
if (tmp is EdmCoreModel ||
tmp.FindDeclaredValueTerm(CoreVocabularyConstants.OptimisticConcurrencyControl) != null ||
tmp.FindDeclaredValueTerm(CapabilitiesVocabularyConstants.ChangeTracking) != null ||
tmp.FindDeclaredValueTerm(CommunityVocabularyConstants.AlternateKeys) != null)
{
continue;
}
Expand Down
1 change: 1 addition & 0 deletions src/Microsoft.OData.Core/Microsoft.OData.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@
<Compile Include="UriParser\InternalErrorCodes.cs" />
<Compile Include="UriParser\KeyPropertyValue.cs" />
<Compile Include="UriParser\LiteralUtils.cs" />
<Compile Include="UriParser\Metadata\AlternateKeysODataUriResolver.cs" />
<Compile Include="UriParser\Metadata\StringAsEnumResolver.cs" />
<Compile Include="UriParser\Metadata\ODataUriResolver.cs" />
<Compile Include="UriParser\Metadata\UnqualifiedODataUriResolver.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal static QueryNode GetNavigationNode(IEdmNavigationProperty property, Sin
// Doing key lookup on the collection navigation property
if (namedValues != null)
{
return keyBinder.BindKeyValues(collectionNavigationNode, namedValues);
return keyBinder.BindKeyValues(collectionNavigationNode, namedValues, state.Model);
}

// Otherwise it's just a normal collection of entities
Expand Down
110 changes: 99 additions & 11 deletions src/Microsoft.OData.Core/UriParser/Binders/KeyBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,101 @@ 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");
Debug.Assert(model != null, "model != 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))
{
return keyLookupNode;
}
else if (TryBindToDeclaredAlternateKey(collectionNode, namedValues, model, collectionItemEntityType, out keyLookupNode))
{
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>
/// <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, keys, out keyPropertyValue))
{
keyLookupNode = null;
return false;
}

Debug.Assert(keyPropertyValue != null, "keyPropertyValue != null");
Debug.Assert(keyPropertyValue.KeyProperty != null, "keyPropertyValue.KeyProperty != null");

Expand All @@ -70,15 +150,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;
}
}

Expand All @@ -87,8 +170,10 @@ 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="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, 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");
Expand All @@ -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)
{
Expand All @@ -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.SingleOrDefault(k => string.CompareOrdinal(k.Key, namedValue.Name) == 0).Value;

if (keyProperty == null)
{
throw new ODataException(ODataErrorStrings.MetadataBinder_PropertyNotDeclaredOrNotKeyInKeyValue(namedValue.Name, collectionItemEntityType.ODataFullName()));
keyPropertyValue = null;
return false;
}
}

Expand All @@ -129,11 +215,13 @@ 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,133 @@
//---------------------------------------------------------------------
// <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.Linq;
using Microsoft.OData.Edm;

/// <summary>
/// Implementation for resolving the alternate keys.
/// </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')
/// </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)
{
if (!TryResolveAlternateKeys(type, namedValues, convertFunc, out convertedPairs))
{
throw;
}
}

return convertedPairs;
}

/// <summary>
/// Try to resolve alternate keys for certain entity type, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key')
/// </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 TryResolveAlternateKeys(IEdmEntityType type, IDictionary<string, string> namedValues, Func<IEdmTypeReference, string, object> convertFunc, out IEnumerable<KeyValuePair<string, object>> convertedPairs)
{
IEnumerable<IDictionary<string, IEdmProperty>> alternateKeys = type.DeclaredAlternateKeys(model);
foreach (IDictionary<string, IEdmProperty> keys in alternateKeys)
{
if (TryResolveKeys(type, namedValues, keys, convertFunc, out convertedPairs))
{
return true;
}
}

convertedPairs = null;
return false;
}

/// <summary>
/// Try to resolve keys for certain entity type, this function would be called when key is specified as name value pairs. E.g. EntitySet(ID='key')
/// </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 TryResolveKeys(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;
}
}
}
Loading

0 comments on commit 8aca79a

Please sign in to comment.