diff --git a/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs b/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs index 13b6696fd5..768b650d9c 100644 --- a/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs +++ b/src/Microsoft.OData.Client/Metadata/ClientTypeUtil.cs @@ -1,4 +1,4 @@ -//--------------------------------------------------------------------- +//--------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. // @@ -18,6 +18,7 @@ namespace Microsoft.OData.Client.Metadata using Microsoft.OData.Metadata; using Microsoft.OData.Edm; using Client = Microsoft.OData.Client; + using System.Runtime.CompilerServices; #endregion Namespaces. @@ -835,5 +836,20 @@ private static bool SkipAssembly(Assembly assembly) || assembly.Equals(typeof(EdmModel).Assembly) // OData Edm assembly || assembly.Equals(typeof(Spatial.Geography).Assembly); // Spatial assembly } + + + internal static bool IsAnonymousProperty(this PropertyInfo propertyInfo) + { + return propertyInfo.DeclaringType.IsAnonymousType(); + } + + internal static bool IsAnonymousType(this Type type) + { + return type.IsDefined(typeof(CompilerGeneratedAttribute), false) + && type.IsGenericType + && (type.Name.Contains("AnonymousType", StringComparison.Ordinal) || type.Name.Contains("AnonType", StringComparison.Ordinal)) + && (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$")) + && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic; + } } } diff --git a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs index 0a8a47fc11..9f6ef0ea87 100644 --- a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs +++ b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs @@ -1,4 +1,4 @@ -//--------------------------------------------------------------------- +//--------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. // @@ -46,7 +46,7 @@ internal class ODataTypeInfo public ODataTypeInfo(Type type) { this.type = type; - ServerSideNameDict = new ConcurrentDictionary(); + ServerSideNameDict = new ConcurrentDictionary(); } /// @@ -84,8 +84,8 @@ public bool HasProperties /// /// Sertver defined type name /// - public string ServerDefinedTypeName - { + public string ServerDefinedTypeName + { get { if (_serverDefinedTypeName == null) @@ -102,7 +102,7 @@ public string ServerDefinedTypeName else { _serverDefinedTypeName = type.Name; - } + } } return _serverDefinedTypeName; @@ -251,9 +251,9 @@ private IEnumerable GetAllProperties() (typeof(UIntPtr) == propertyType)) { continue; - } + } - Debug.Assert(!propertyType.ContainsGenericParameters(), "remove when test case is found that encounters this"); + Debug.Assert(!propertyType.ContainsGenericParameters(), "remove when test case is found that encounters this"); if (propertyInfo.CanRead && (!propertyType.IsValueType() || propertyInfo.CanWrite) && @@ -334,10 +334,10 @@ private PropertyInfo[] GetKeyProperties() if (newKeyKind == KeyKind.AttributedKey && keyProperties.Count != dataServiceKeyAttribute?.KeyNames.Count) { var m = (from string a in dataServiceKeyAttribute.KeyNames - where (from b in Properties + where (from b in Properties where b.Name == a select b).FirstOrDefault() == null - select a).First(); + select a).First(); throw Client.Error.InvalidOperation(Client.Strings.ClientType_MissingProperty(typeName, m)); } } @@ -381,10 +381,16 @@ private static void InserKeyBasedOnOrder(List> k private static KeyKind IsKeyProperty(PropertyInfo propertyInfo, KeyAttribute dataServiceKeyAttribute, out int order) { Debug.Assert(propertyInfo != null, "propertyInfo != null"); + order = -1; + + //If the property's declaring type is anonymous, it is not a key. + if (propertyInfo.IsAnonymousProperty()) + { + return KeyKind.NotKey; + } string propertyName = ClientTypeUtil.GetServerDefinedName(propertyInfo); - order = -1; KeyKind keyKind = KeyKind.NotKey; if (dataServiceKeyAttribute != null && dataServiceKeyAttribute.KeyNames.Contains(propertyName)) { @@ -396,10 +402,10 @@ private static KeyKind IsKeyProperty(PropertyInfo propertyInfo, KeyAttribute dat order = newOrder; keyKind = newKind; } - else if (propertyName.EndsWith("ID", StringComparison.Ordinal)) + else if (propertyName.Equals(propertyInfo.DeclaringType.Name + "Id", StringComparison.OrdinalIgnoreCase) || propertyName.Equals("Id", StringComparison.OrdinalIgnoreCase)) { string declaringTypeName = propertyInfo.DeclaringType.Name; - if ((propertyName.Length == (declaringTypeName.Length + 2)) && propertyName.StartsWith(declaringTypeName, StringComparison.Ordinal)) + if ((propertyName.Length == (declaringTypeName.Length + 2)) && propertyName.StartsWith(declaringTypeName, StringComparison.OrdinalIgnoreCase)) { // matched "DeclaringType.Name+ID" pattern keyKind = KeyKind.TypeNameId; @@ -419,7 +425,7 @@ private static bool IsDataAnnotationsKeyProperty(PropertyInfo propertyInfo, out order = -1; kind = KeyKind.NotKey; var attributes = propertyInfo.GetCustomAttributes(); - if(!attributes.Any(a => a is System.ComponentModel.DataAnnotations.KeyAttribute)) + if (!attributes.Any(a => a is System.ComponentModel.DataAnnotations.KeyAttribute)) { return false; } diff --git a/test/FunctionalTests/Microsoft.OData.Client.Tests/Metadata/ClientTypeUtilTests.cs b/test/FunctionalTests/Microsoft.OData.Client.Tests/Metadata/ClientTypeUtilTests.cs index db997409d5..f3b97e282d 100644 --- a/test/FunctionalTests/Microsoft.OData.Client.Tests/Metadata/ClientTypeUtilTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Client.Tests/Metadata/ClientTypeUtilTests.cs @@ -1,4 +1,4 @@ -//--------------------------------------------------------------------- +//--------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. // @@ -17,6 +17,19 @@ namespace Microsoft.OData.Client.Tests.Metadata /// public class ClientTypeUtilTests { + [Theory] + [InlineData(typeof(Giraffe), true)] + [InlineData(typeof(Hippo), true)] + [InlineData(typeof(Ferret), true)] + [InlineData(typeof(Lion), false)] + public void IfTypeProperty_HasConventionalKey_TypeIsEntity(Type entityType, bool isEntity) + { + //Act + bool actualResult = ClientTypeUtil.TypeOrElementTypeIsEntity(entityType); + //Assert + Assert.Equal(actualResult, isEntity); + } + [Fact] public void IFTypeProperty_HasKeyAttribute_TypeIsEntity() { @@ -214,5 +227,37 @@ public struct EmployeeTypeStruct public int EmpTypeId { get; set; } } + public class Giraffe + { + /// + /// Conventional Id Key property + /// + public int Id { get; set; } + public string Name { get; set; } + } + + public class Hippo + { + /// + /// Conventional {TypeName + Id} Key Property + /// + public int HippoId { get; set; } + public double Weight { get; set; } + } + + public class Ferret + { + /// + /// Conventional {TypeName + Id} Key Property with odd casing + /// + public int FeRReTID { get; set; } + public double Weight { get; set; } + } + + public class Lion + { + public int SomeId { get; set; } + public string Name { get; set; } + } } }