Skip to content

Commit

Permalink
Allow enum properties to be keys. #138
Browse files Browse the repository at this point in the history
  • Loading branch information
xuzhg committed Mar 26, 2015
1 parent 5f771f2 commit 6323639
Show file tree
Hide file tree
Showing 15 changed files with 475 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public override void Apply(EntityTypeConfiguration edmTypeConfiguration, ODataCo
{
edmTypeConfiguration.RemoveKey(key);
}

EnumPropertyConfiguration[] enumKeys = edmTypeConfiguration.EnumKeys.ToArray();
foreach (EnumPropertyConfiguration key in enumKeys)
{
edmTypeConfiguration.RemoveKey(key);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace System.Web.OData.Builder.Conventions.Attributes
/// <summary>
/// Configures properties that have the <see cref="KeyAttribute"/> as keys in the <see cref="IEdmEntityType"/>.
/// </summary>
internal class KeyAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<PrimitivePropertyConfiguration>
internal class KeyAttributeEdmPropertyConvention : AttributeEdmPropertyConvention<StructuralPropertyConfiguration>
{
public KeyAttributeEdmPropertyConvention()
: base(attribute => attribute.GetType() == typeof(KeyAttribute), allowMultiple: false)
Expand All @@ -24,7 +24,7 @@ public KeyAttributeEdmPropertyConvention()
/// <param name="structuralTypeConfiguration">The edm type being configured.</param>
/// <param name="attribute">The <see cref="Attribute"/> found on the property.</param>
/// <param name="model">The ODataConventionModelBuilder used to build the model.</param>
public override void Apply(PrimitivePropertyConfiguration edmProperty,
public override void Apply(StructuralPropertyConfiguration edmProperty,
StructuralTypeConfiguration structuralTypeConfiguration,
Attribute attribute,
ODataConventionModelBuilder model)
Expand All @@ -34,10 +34,13 @@ public override void Apply(PrimitivePropertyConfiguration edmProperty,
throw Error.ArgumentNull("edmProperty");
}

EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration;
if (entity != null)
if (edmProperty.Kind == PropertyKind.Primitive || edmProperty.Kind == PropertyKind.Enum)
{
entity.HasKey(edmProperty.PropertyInfo);
EntityTypeConfiguration entity = structuralTypeConfiguration as EntityTypeConfiguration;
if (entity != null)
{
entity.HasKey(edmProperty.PropertyInfo);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,21 @@ public override void Apply(EntityTypeConfiguration entity, ODataConventionModelB
}

// Suppress the EntityKeyConvention if there is any key in EntityTypeConfiguration.
if (entity.Keys.Any())
if (entity.Keys.Any() || entity.EnumKeys.Any())
{
return;
}

// Try to figure out keys only if there is no base type.
if (entity.BaseType == null)
// Suppress the EntityKeyConvention if base type has any key.
if (entity.BaseType != null && entity.BaseType.Keys().Any())
{
PropertyConfiguration key = GetKeyProperty(entity);
if (key != null)
{
entity.HasKey(key.PropertyInfo);
}
return;
}

PropertyConfiguration key = GetKeyProperty(entity);
if (key != null)
{
entity.HasKey(key.PropertyInfo);
}
}

Expand All @@ -48,7 +50,7 @@ private static PropertyConfiguration GetKeyProperty(EntityTypeConfiguration enti
IEnumerable<PropertyConfiguration> keys =
entityType.Properties
.Where(p => (p.Name.Equals(entityType.Name + "Id", StringComparison.OrdinalIgnoreCase) || p.Name.Equals("Id", StringComparison.OrdinalIgnoreCase))
&& EdmLibHelpers.GetEdmPrimitiveTypeOrNull(p.PropertyInfo.PropertyType) != null);
&& (EdmLibHelpers.GetEdmPrimitiveTypeOrNull(p.PropertyInfo.PropertyType) != null || TypeHelper.IsEnum(p.PropertyInfo.PropertyType)));

if (keys.Count() == 1)
{
Expand Down
4 changes: 4 additions & 0 deletions OData/src/System.Web.OData/OData/Builder/EdmTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ private void CreateEntityTypeBody(EdmEntityType type, EntityTypeConfiguration co
CreateStructuralTypeBody(type, config);
IEnumerable<IEdmStructuralProperty> keys = config.Keys.Select(p => type.DeclaredProperties.OfType<IEdmStructuralProperty>().First(dp => dp.Name == p.Name));
type.AddKeys(keys);

// Add the Enum keys
keys = config.EnumKeys.Select(p => type.DeclaredProperties.OfType<IEdmStructuralProperty>().First(dp => dp.Name == p.Name));
type.AddKeys(keys);
}

private void CreateNavigationProperty(EntityTypeConfiguration config)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ public static IEnumerable<PropertyConfiguration> DerivedProperties(
public static IEnumerable<PropertyConfiguration> Keys(this EntityTypeConfiguration entity)
{
Contract.Assert(entity != null);
if (entity.Keys.Any())
if (entity.Keys.Any() || entity.EnumKeys.Any())
{
return entity.Keys;
return entity.Keys.OfType<PropertyConfiguration>().Concat(entity.EnumKeys);
}

if (entity.BaseType == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace System.Web.OData.Builder
public class EntityTypeConfiguration : StructuralTypeConfiguration
{
private List<PrimitivePropertyConfiguration> _keys = new List<PrimitivePropertyConfiguration>();
private List<EnumPropertyConfiguration> _enumKeys = new List<EnumPropertyConfiguration>();

/// <summary>
/// Initializes a new instance of the <see cref="EntityTypeConfiguration"/> class.
Expand Down Expand Up @@ -68,6 +69,14 @@ public virtual IEnumerable<PrimitivePropertyConfiguration> Keys
}
}

/// <summary>
/// Gets the collection of enum keys for this entity type.
/// </summary>
public virtual IEnumerable<EnumPropertyConfiguration> EnumKeys
{
get { return _enumKeys; }
}

/// <summary>
/// Gets or sets the base type of this entity type.
/// </summary>
Expand Down Expand Up @@ -105,14 +114,31 @@ public virtual EntityTypeConfiguration HasKey(PropertyInfo keyProperty)
throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, BaseType.FullName);
}

PrimitivePropertyConfiguration propertyConfig = AddProperty(keyProperty);
// Add the enum key if the property type is enum
if (keyProperty.PropertyType.IsEnum)
{
ModelBuilder.AddEnumType(keyProperty.PropertyType);
EnumPropertyConfiguration enumConfig = AddEnumProperty(keyProperty);

// keys are always required
propertyConfig.IsRequired();
// keys are always required
enumConfig.IsRequired();

if (!_keys.Contains(propertyConfig))
if (!_enumKeys.Contains(enumConfig))
{
_enumKeys.Add(enumConfig);
}
}
else
{
_keys.Add(propertyConfig);
PrimitivePropertyConfiguration propertyConfig = AddProperty(keyProperty);

// keys are always required
propertyConfig.IsRequired();

if (!_keys.Contains(propertyConfig))
{
_keys.Add(propertyConfig);
}
}

return this;
Expand All @@ -134,6 +160,22 @@ public virtual void RemoveKey(PrimitivePropertyConfiguration keyProperty)
_keys.Remove(keyProperty);
}

/// <summary>
/// Removes the enum property from the entity enum keys collection.
/// </summary>
/// <param name="enumKeyProperty">The key to be removed.</param>
/// <remarks>This method just disable the property to be not a key anymore. It does not remove the property all together.
/// To remove the property completely, use the method <see cref="RemoveProperty"/></remarks>
public virtual void RemoveKey(EnumPropertyConfiguration enumKeyProperty)
{
if (enumKeyProperty == null)
{
throw Error.ArgumentNull("enumKeyProperty");
}

_enumKeys.Remove(enumKeyProperty);
}

/// <summary>
/// Sets the base type of this entity type to <c>null</c> meaning that this entity type
/// does not derive from anything.
Expand All @@ -152,7 +194,7 @@ public virtual EntityTypeConfiguration DerivesFromNothing()
/// <returns>Returns itself so that multiple calls can be chained.</returns>
public virtual EntityTypeConfiguration DerivesFrom(EntityTypeConfiguration baseType)
{
if (Keys.Any() && baseType.Keys().Any())
if ((Keys.Any() || EnumKeys.Any()) && baseType.Keys().Any())
{
throw Error.InvalidOperation(SRResources.CannotDefineKeysOnDerivedTypes, FullName, baseType.FullName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ internal void DiscoverInheritanceRelationships()
{
entity.RemoveKey(keyProperty);
}

foreach (EnumPropertyConfiguration enumKeyProperty in entity.EnumKeys.ToArray())
{
entity.RemoveKey(enumKeyProperty);
}
}

entity.DerivesFrom(baseEntityType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Web.Http;
Expand Down Expand Up @@ -422,6 +421,16 @@ private static string TranslateKeySegmentValue(object value, bool enableUriTempl
}
}

ConstantNode constantNode = value as ConstantNode;
if (constantNode != null)
{
ODataEnumValue enumValue = constantNode.Value as ODataEnumValue;
if (enumValue != null)
{
return ODataUriUtils.ConvertToUriLiteral(enumValue, ODataVersion.V4);
}
}

return ODataUriUtils.ConvertToUriLiteral(value, ODataVersion.V4);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public void Apply_IgnoresKey_ComplexProperty()

Mock<ComplexPropertyConfiguration> complexProperty =
new Mock<ComplexPropertyConfiguration>(property.Object, entityType.Object);
complexProperty.Setup(c => c.Kind).Returns(PropertyKind.Complex);

// Act
new KeyAttributeEdmPropertyConvention().Apply(complexProperty.Object, entityType.Object, builder);
Expand All @@ -102,12 +103,41 @@ public void Apply_IgnoresKey_NavigationProperty()

Mock<NavigationPropertyConfiguration> navigationProperty =
new Mock<NavigationPropertyConfiguration>(property.Object, EdmMultiplicity.ZeroOrOne, entityType.Object);
navigationProperty.Setup(c => c.Kind).Returns(PropertyKind.Navigation);

// Act
new KeyAttributeEdmPropertyConvention().Apply(navigationProperty.Object, entityType.Object, builder);

// Assert
entityType.Verify();
}

[Fact]
public void Apply_AddsEnumKey_EntityTypeConfiguration()
{
// Arrange
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

Mock<PropertyInfo> property = new Mock<PropertyInfo>();
property.Setup(p => p.Name).Returns("Property");
property.Setup(p => p.PropertyType).Returns(typeof(MyEnumType));
property.Setup(p => p.GetCustomAttributes(It.IsAny<bool>())).Returns(new[] { new KeyAttribute() });

Mock<EntityTypeConfiguration> entityType = new Mock<EntityTypeConfiguration>(MockBehavior.Strict);
entityType.Setup(e => e.HasKey(property.Object)).Returns(entityType.Object).Verifiable();

Mock<EnumPropertyConfiguration> enumProperty =
new Mock<EnumPropertyConfiguration>(property.Object, entityType.Object);

// Act
new KeyAttributeEdmPropertyConvention().Apply(enumProperty.Object, entityType.Object, builder);

// Assert
entityType.Verify();
}

enum MyEnumType
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.ComponentModel.DataAnnotations.Schema;
using System.Web.OData.Builder.TestModels;
using System.Web.OData.TestCommon;
using Microsoft.OData.Edm;
using Microsoft.TestCommon;
Expand Down Expand Up @@ -35,6 +36,33 @@ public void Apply_Calls_HasKey_OnEdmType(string propertyName)
mockEntityType.Verify();
}

[Fact]
public void Apply_Calls_HasKey_ForEnumProperty_OnEdmType()
{
// Arrange
Mock<EntityTypeConfiguration> mockEntityType = new Mock<EntityTypeConfiguration>();
Mock<PropertyConfiguration> property =
new Mock<PropertyConfiguration>(typeof(EntityKeyConventionTests_EntityType).GetProperty("ColorId"),
mockEntityType.Object);
property.Setup(c => c.Kind).Returns(PropertyKind.Enum);

mockEntityType.Setup(e => e.Name).Returns("Color");
mockEntityType.Setup(
entityType => entityType.HasKey(typeof(EntityKeyConventionTests_EntityType).GetProperty("ColorId")))
.Returns(mockEntityType.Object)
.Verifiable();

mockEntityType.Object.ExplicitProperties.Add(new MockPropertyInfo(), property.Object);

var mockModelBuilder = new Mock<ODataConventionModelBuilder>(MockBehavior.Strict);

// Act
new EntityKeyConvention().Apply(mockEntityType.Object, mockModelBuilder.Object);

// Assert
mockEntityType.Verify();
}

[Fact]
public void EntityKeyConvention_FiguresOutTheKeyProperty()
{
Expand All @@ -51,6 +79,31 @@ public void EntityKeyConvention_FiguresOutTheKeyProperty()
entity.AssertHasKey(model, "ID", EdmPrimitiveTypeKind.Int64);
}

[Fact]
public void EntityKeyConvention_FiguresOutTheEnumKeyProperty()
{
// Arrange
MockType baseType =
new MockType("BaseType")
.Property<Color>("ID");

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.AddEntityType(baseType);

// Act
IEdmModel model = builder.GetEdmModel();

// Assert
IEdmEntityType entity = model.AssertHasEntityType(baseType);

IEdmStructuralProperty enumProperty = entity.AssertHasProperty<IEdmStructuralProperty>(model, "ID", typeof(Color), false);
IEdmProperty enumKey = Assert.Single(entity.DeclaredKey);
Assert.Same(enumProperty, enumKey);

Assert.Equal(EdmTypeKind.Enum, enumKey.Type.TypeKind());
Assert.Equal("System.Web.OData.Builder.TestModels.Color", enumKey.Type.Definition.FullTypeName());
}

[Fact]
public void EntityKeyConvention_DoesnotFigureOutKeyPropertyOnDerivedTypes()
{
Expand Down Expand Up @@ -101,6 +154,8 @@ class EntityKeyConventionTests_EntityType
public string iD { get; set; }

public string SampleEntityID { get; set; }

public Color ColorId { get; set; }
}
}
}
Loading

0 comments on commit 6323639

Please sign in to comment.