From 2f75fbfb4327aef865ec4d5ba2a97c9deede32bd Mon Sep 17 00:00:00 2001 From: Lance Wynn <69650335+lawynn@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:52:12 -0600 Subject: [PATCH 1/7] Update ODataTypeInfo.cs I found an issue where the Conventional Id fields are not recognized as key fields. As the Property Name Ends with Id, rather than "ID". The result is that the EdmModel is properly defining the key property, however on the client the EntityType is resolved as "ComplexType" and failures in tracking occur later on in the pipeline. --- src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs index 0a8a47fc11..c1f5b63e0c 100644 --- a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs +++ b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs @@ -396,7 +396,7 @@ private static KeyKind IsKeyProperty(PropertyInfo propertyInfo, KeyAttribute dat order = newOrder; keyKind = newKind; } - else if (propertyName.EndsWith("ID", StringComparison.Ordinal)) + else if (propertyName.EndsWith("ID", StringComparison.OrdinalIgnoreCase)) { string declaringTypeName = propertyInfo.DeclaringType.Name; if ((propertyName.Length == (declaringTypeName.Length + 2)) && propertyName.StartsWith(declaringTypeName, StringComparison.Ordinal)) From a3ff3d512d9b4533fc0635273cf1a6175abbc9dc Mon Sep 17 00:00:00 2001 From: Lance Wynn <69650335+lawynn@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:28:33 -0600 Subject: [PATCH 2/7] Update src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs --- src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs index c1f5b63e0c..103982769d 100644 --- a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs +++ b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs @@ -396,7 +396,7 @@ private static KeyKind IsKeyProperty(PropertyInfo propertyInfo, KeyAttribute dat order = newOrder; keyKind = newKind; } - else if (propertyName.EndsWith("ID", StringComparison.OrdinalIgnoreCase)) +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)) From 2dc678170143e2164ed04f3ecda18adf6ef670fb Mon Sep 17 00:00:00 2001 From: Lance Wynn <69650335+lawynn@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:34:26 -0600 Subject: [PATCH 3/7] Fixed Whitespace --- src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs index 103982769d..f13504efe3 100644 --- a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs +++ b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs @@ -396,7 +396,7 @@ private static KeyKind IsKeyProperty(PropertyInfo propertyInfo, KeyAttribute dat order = newOrder; keyKind = newKind; } -else if (propertyName.Equals(propertyInfo.DeclaringType.Name + "Id", StringComparison.OrdinalIgnoreCase) || propertyName.Equals("Id", StringComparison.OrdinalIgnoreCase)) + 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)) From 133333acc22aa13b733eb4920d85fb425caa430d Mon Sep 17 00:00:00 2001 From: Lance Wynn <69650335+lawynn@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:48:50 -0600 Subject: [PATCH 4/7] Update ODataTypeInfo.cs Fixed casing --- src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs index f13504efe3..c85dc74c05 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. // @@ -399,7 +399,7 @@ private static KeyKind IsKeyProperty(PropertyInfo propertyInfo, KeyAttribute dat 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; From 008d6b40d5dd5dd790a6d87e5727b79cdd72fb81 Mon Sep 17 00:00:00 2001 From: Lance Wynn <69650335+lawynn@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:51:52 -0600 Subject: [PATCH 5/7] Update ClientTypeUtilTests.cs Added unit tests for conventional keys --- .../Metadata/ClientTypeUtilTests.cs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) 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; } + } } } From 36f8508f37e8005daa84d82f4e2a2f42de9f668d Mon Sep 17 00:00:00 2001 From: Lance Wynn <69650335+lawynn@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:03:34 -0600 Subject: [PATCH 6/7] Update ODataTypeInfo.cs Added check for anonymous types when determining if a property is a key property. --- .../Metadata/ODataTypeInfo.cs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs index c85dc74c05..9f6ef0ea87 100644 --- a/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs +++ b/src/Microsoft.OData.Client/Metadata/ODataTypeInfo.cs @@ -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)) { @@ -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; } From 6ea502cae8cb0858e3a93907483bdaf77984f6f7 Mon Sep 17 00:00:00 2001 From: Lance Wynn <69650335+lawynn@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:05:40 -0600 Subject: [PATCH 7/7] Update ClientTypeUtil.cs Added IsAnonymousProperty, and IsAnonymousType extensions --- .../Metadata/ClientTypeUtil.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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; + } } }