From 92edec58e85d8f39c9466be118b9d730816e9e26 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Thu, 21 Oct 2021 16:32:07 -0700 Subject: [PATCH 1/2] Add IODataTypeMapper --- .../Abstracts/ETagActionFilterAttribute.cs | 2 +- .../Common/TypeHelper.cs | 24 + .../Edm/DefaultODataTypeMapper.cs | 444 +++++++++++++++ .../Edm/EdmClrTypeMapExtensions.cs | 363 ++---------- .../Edm/EdmModelAnnotationExtensions.cs | 42 ++ .../Edm/IODataTypeMapper.cs | 52 ++ .../Edm/IODataTypeMapperExtensions.cs | 93 ++++ .../Edm/TypeCacheItem.cs | 84 +++ .../Formatter/ConventionsHelpers.cs | 7 +- .../CollectionDeserializationHelper.cs | 16 +- .../Deserialization/DeserializationHelper.cs | 10 +- .../ODataDeserializerProvider.cs | 6 +- .../Formatter/EdmLibHelper.cs | 2 +- .../Formatter/ODataModelBinder.cs | 3 +- .../Formatter/ODataModelBinderConverter.cs | 8 +- .../Serialization/ODataSerializerContext.cs | 7 +- .../Serialization/ODataSerializerProvider.cs | 3 +- .../Microsoft.AspNetCore.OData.xml | 208 ++++++- .../PublicAPI.Unshipped.txt | 19 + .../Query/EnableQueryAttribute.cs | 6 +- .../Query/Expressions/AggregationBinder.cs | 5 +- .../Query/Expressions/ExpressionBinderBase.cs | 8 +- .../Expressions/ExpressionBinderHelper.cs | 3 +- .../Query/ODataQueryContext.cs | 2 +- .../Query/Query/DefaultSkipTokenHandler.cs | 2 +- .../Query/Wrapper/SelectExpandWrapper.cs | 2 +- .../Results/ResultHelpers.cs | 2 +- .../Edm/DefaultODataTypeMapperTests.cs | 523 ++++++++++++++++++ .../Edm/EdmClrTypeMapExtensionsTests.cs | 456 ++++----------- .../Edm/EdmModelAnnotationExtensionsTests.cs | 42 ++ .../Edm/IODataTypeMapperExtensionsTests.cs | 130 +++++ .../Edm/TypeCacheItemTests.cs | 126 +++++ .../CollectionDeserializationHelpersTest.cs | 16 +- .../DeserializationHelpersTest.cs | 10 +- ...rosoft.AspNetCore.OData.PublicApi.Net5.bsl | 51 ++ ...t.AspNetCore.OData.PublicApi.NetCore31.bsl | 51 ++ 36 files changed, 2077 insertions(+), 751 deletions(-) create mode 100644 src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs create mode 100644 src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapper.cs create mode 100644 src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapperExtensions.cs create mode 100644 src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs create mode 100644 test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs create mode 100644 test/Microsoft.AspNetCore.OData.Tests/Edm/IODataTypeMapperExtensionsTests.cs create mode 100644 test/Microsoft.AspNetCore.OData.Tests/Edm/TypeCacheItemTests.cs diff --git a/src/Microsoft.AspNetCore.OData/Abstracts/ETagActionFilterAttribute.cs b/src/Microsoft.AspNetCore.OData/Abstracts/ETagActionFilterAttribute.cs index 025765972..c4e252903 100644 --- a/src/Microsoft.AspNetCore.OData/Abstracts/ETagActionFilterAttribute.cs +++ b/src/Microsoft.AspNetCore.OData/Abstracts/ETagActionFilterAttribute.cs @@ -128,7 +128,7 @@ private static IEdmEntityTypeReference GetTypeReference(IEdmModel model, IEdmEnt return edmTypeReference.AsEntity(); } - IEdmTypeReference reference = model.GetTypeMappingCache().GetEdmType(value.GetType(), model); + IEdmTypeReference reference = model.GetEdmTypeReference(value.GetType()); if (reference != null && reference.Definition.IsOrInheritsFrom(edmType)) { return (IEdmEntityTypeReference)reference; diff --git a/src/Microsoft.AspNetCore.OData/Common/TypeHelper.cs b/src/Microsoft.AspNetCore.OData/Common/TypeHelper.cs index 488f24be7..b0472e8da 100644 --- a/src/Microsoft.AspNetCore.OData/Common/TypeHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Common/TypeHelper.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; +using Microsoft.AspNetCore.OData.Deltas; using Microsoft.AspNetCore.OData.Query.Wrapper; using Microsoft.OData.ModelBuilder; @@ -32,6 +33,29 @@ public static bool IsDynamicTypeWrapper(this Type type) return (type != null && typeof(DynamicTypeWrapper).IsAssignableFrom(type)); } + public static bool IsDeltaSetWrapper(this Type type, out Type entityType) => IsTypeWrapper(typeof(DeltaSet<>), type, out entityType); + + public static bool IsSelectExpandWrapper(this Type type, out Type entityType) => IsTypeWrapper(typeof(SelectExpandWrapper<>), type, out entityType); + + public static bool IsComputeWrapper(this Type type, out Type entityType) => IsTypeWrapper(typeof(ComputeWrapper<>), type, out entityType); + + private static bool IsTypeWrapper(Type wrappedType, Type type, out Type entityType) + { + if (type == null) + { + entityType = null; + return false; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition() == wrappedType) + { + entityType = type.GetGenericArguments()[0]; + return true; + } + + return IsTypeWrapper(wrappedType, type.BaseType, out entityType); + } + /// /// Return the collection element type. /// diff --git a/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs b/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs new file mode 100644 index 000000000..aa6a31040 --- /dev/null +++ b/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs @@ -0,0 +1,444 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Microsoft.AspNetCore.OData.Abstracts; +using Microsoft.AspNetCore.OData.Common; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; +using Microsoft.Spatial; + +namespace Microsoft.AspNetCore.OData.Edm +{ + /// + /// The default implementation for . + /// + public class DefaultODataTypeMapper : IODataTypeMapper + { + /// + /// Creates a static instance for the Default type mapper. + /// + internal static DefaultODataTypeMapper Default = new DefaultODataTypeMapper(); + + #region Default_PrimitiveTypeMapping + /// + /// The default mapping between Edm primitive type and Clr primitive type. + /// Primitive types are cross Edm models. + /// + private static ConcurrentDictionary ClrPrimitiveTypes + = new ConcurrentDictionary(); + + /// + /// Item1 --> non-nullable + /// Item2 --> nullable + /// + private static ConcurrentDictionary EdmPrimitiveTypes + = new ConcurrentDictionary(); + + static DefaultODataTypeMapper() + { + // Do not change the order for the nullable or non-nullable. Put nullable ahead of non-nullable. + // By design: non-nullable will overwrite the item1. + BuildTypeMapping(EdmPrimitiveTypeKind.String); + BuildTypeMapping(EdmPrimitiveTypeKind.Boolean); + BuildTypeMapping(EdmPrimitiveTypeKind.Boolean); + BuildTypeMapping(EdmPrimitiveTypeKind.Byte); + BuildTypeMapping(EdmPrimitiveTypeKind.Byte); + BuildTypeMapping(EdmPrimitiveTypeKind.Decimal); + BuildTypeMapping(EdmPrimitiveTypeKind.Decimal); + BuildTypeMapping(EdmPrimitiveTypeKind.Double); + BuildTypeMapping(EdmPrimitiveTypeKind.Double); + BuildTypeMapping(EdmPrimitiveTypeKind.Guid); + BuildTypeMapping(EdmPrimitiveTypeKind.Guid); + BuildTypeMapping(EdmPrimitiveTypeKind.Int16); + BuildTypeMapping(EdmPrimitiveTypeKind.Int16); + BuildTypeMapping(EdmPrimitiveTypeKind.Int32); + BuildTypeMapping(EdmPrimitiveTypeKind.Int32); + BuildTypeMapping(EdmPrimitiveTypeKind.Int64); + BuildTypeMapping(EdmPrimitiveTypeKind.Int64); + BuildTypeMapping(EdmPrimitiveTypeKind.SByte); + BuildTypeMapping(EdmPrimitiveTypeKind.SByte); + BuildTypeMapping(EdmPrimitiveTypeKind.Single); + BuildTypeMapping(EdmPrimitiveTypeKind.Single); + BuildTypeMapping(EdmPrimitiveTypeKind.Binary); + BuildTypeMapping(EdmPrimitiveTypeKind.Stream); + BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset); + BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset); + BuildTypeMapping(EdmPrimitiveTypeKind.Duration); + BuildTypeMapping(EdmPrimitiveTypeKind.Duration); + BuildTypeMapping(EdmPrimitiveTypeKind.Date); + BuildTypeMapping(EdmPrimitiveTypeKind.Date); + BuildTypeMapping(EdmPrimitiveTypeKind.TimeOfDay); + BuildTypeMapping(EdmPrimitiveTypeKind.TimeOfDay); + + BuildTypeMapping(EdmPrimitiveTypeKind.Geography); + BuildTypeMapping(EdmPrimitiveTypeKind.GeographyPoint); + BuildTypeMapping(EdmPrimitiveTypeKind.GeographyLineString); + BuildTypeMapping(EdmPrimitiveTypeKind.GeographyPolygon); + BuildTypeMapping(EdmPrimitiveTypeKind.GeographyCollection); + BuildTypeMapping(EdmPrimitiveTypeKind.GeographyMultiLineString); + BuildTypeMapping(EdmPrimitiveTypeKind.GeographyMultiPoint); + BuildTypeMapping(EdmPrimitiveTypeKind.GeographyMultiPolygon); + BuildTypeMapping(EdmPrimitiveTypeKind.Geometry); + BuildTypeMapping(EdmPrimitiveTypeKind.GeometryPoint); + BuildTypeMapping(EdmPrimitiveTypeKind.GeometryLineString); + BuildTypeMapping(EdmPrimitiveTypeKind.GeometryPolygon); + BuildTypeMapping(EdmPrimitiveTypeKind.GeometryCollection); + BuildTypeMapping(EdmPrimitiveTypeKind.GeometryMultiLineString); + BuildTypeMapping(EdmPrimitiveTypeKind.GeometryMultiPoint); + BuildTypeMapping(EdmPrimitiveTypeKind.GeometryMultiPolygon); + + // non-standard mappings + BuildTypeMapping(EdmPrimitiveTypeKind.String, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.Int32, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.Int32, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.Int64, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.Int64, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.Int64, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.Int64, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.String, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.String, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.String, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset, isStandard: false); + BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset, isStandard: false); + } + #endregion + + #region IODataTypeMapper.GetPrimitiveType + /// + /// Gets the corresponding Edm primitive type for a given type. + /// + /// The given CLR type. + /// Null or the Edm primitive type. + public virtual IEdmPrimitiveTypeReference GetPrimitiveType(Type clrType) + { + if (clrType == null) + { + return null; + } + + return ClrPrimitiveTypes.TryGetValue(clrType, out IEdmPrimitiveTypeReference primitive) ? primitive : null; + } + + /// + /// Gets the corresponding type for a given Edm primitive type . + /// + /// The given Edm primitive type. + /// The nullable or not. + /// Null or the CLR type. + public virtual Type GetPrimitiveType(IEdmPrimitiveType primitiveType, bool nullable) + { + if (primitiveType == null) + { + return null; + } + + if (EdmPrimitiveTypes.TryGetValue(primitiveType, out (Type, Type) types)) + { + if (nullable) + { + return types.Item2; + } + else + { + return types.Item1; + } + } + + return null; + } + #endregion + + /// + /// The cache used to hold the type mapping between and . + /// + private ConcurrentDictionary _cache = new ConcurrentDictionary(); + + #region ClrType -> EdmType + /// + /// Gets the corresponding Edm type for the given CLR type . + /// + /// The given Edm model. + /// The given CLR type. + /// Null or the corresponding Edm type reference. + public virtual IEdmTypeReference GetEdmTypeReference(IEdmModel edmModel, Type clrType) + { + if (clrType == null) + { + throw Error.ArgumentNull(nameof(clrType)); + } + + IEdmPrimitiveTypeReference primitiveType = GetPrimitiveType(clrType); + if (primitiveType != null) + { + return primitiveType; + } + + if (edmModel == null) + { + throw Error.ArgumentNull(nameof(edmModel)); + } + + TypeCacheItem map = GetOrCreateCacheItem(edmModel); + // Search from cache + if (map.TryFindEdmType(clrType, out IEdmTypeReference edmTypeRef)) + { + return edmTypeRef; + } + + // Not in the cache, let's build the Edm type reference. + IEdmType edmType = GetEdmType(edmModel, clrType, testCollections: true); + if (edmType != null) + { + bool isNullable = clrType.IsNullable(); + edmTypeRef = edmType.ToEdmTypeReference(isNullable); + } + else + { + edmTypeRef = null; + } + + map.AddClrToEdmMap(clrType, edmTypeRef); + return edmTypeRef; + } + + private IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollections) + { + Contract.Assert(edmModel != null); + Contract.Assert(clrType != null); + + IEdmPrimitiveTypeReference primitiveType = GetPrimitiveType(clrType); + if (primitiveType != null) + { + return primitiveType.Definition; + } + else + { + if (testCollections) + { + Type entityType; + if (clrType.IsDeltaSetWrapper(out entityType)) + { + IEdmType elementType = GetEdmType(edmModel, entityType, testCollections: false); + if (elementType != null) + { + return new EdmCollectionType(elementType.ToEdmTypeReference(entityType.IsNullable())); + } + } + + Type enumerableOfT = ExtractGenericInterface(clrType, typeof(IEnumerable<>)); + if (enumerableOfT != null) + { + Type elementClrType = enumerableOfT.GetGenericArguments()[0]; + + // IEnumerable> is a collection of T. + if (elementClrType.IsSelectExpandWrapper(out entityType)) + { + elementClrType = entityType; + } + + if (elementClrType.IsComputeWrapper(out entityType)) + { + elementClrType = entityType; + } + + IEdmType elementType = GetEdmType(edmModel, elementClrType, testCollections: false); + if (elementType != null) + { + return new EdmCollectionType(elementType.ToEdmTypeReference(elementClrType.IsNullable())); + } + } + } + + Type underlyingType = TypeHelper.GetUnderlyingTypeOrSelf(clrType); + if (TypeHelper.IsEnum(underlyingType)) + { + clrType = underlyingType; + } + + // search for the ClrTypeAnnotation and return it if present + IEdmType returnType = + edmModel + .SchemaElements + .OfType() + .Select(edmType => new { EdmType = edmType, Annotation = edmModel.GetAnnotationValue(edmType) }) + .Where(tuple => tuple.Annotation != null && tuple.Annotation.ClrType == clrType) + .Select(tuple => tuple.EdmType) + .SingleOrDefault(); + + // default to the EdmType with the same name as the ClrType name + returnType = returnType ?? edmModel.FindType(clrType.EdmFullName()); + + if (clrType.BaseType != null) + { + // go up the inheritance tree to see if we have a mapping defined for the base type. + returnType = returnType ?? GetEdmType(edmModel, clrType.BaseType, testCollections); + } + + return returnType; + } + } + #endregion + + #region EdmType -> ClrType + /// + /// Gets the corresponding for a given Edm type . + /// + /// The Edm model. + /// The Edm type. + /// The nullable or not. + /// The assembly resolver. if it's null, will use the default resolver. + /// Null or the CLR type. + public virtual Type GetClrType(IEdmModel edmModel, IEdmType edmType, bool nullable, IAssemblyResolver assembliesResolver) + { + if (edmType == null) + { + throw Error.ArgumentNull(nameof(edmType)); + } + + if (edmType.TypeKind == EdmTypeKind.Primitive) + { + return GetPrimitiveType((IEdmPrimitiveType)edmType, nullable); + } + + if (edmModel == null) + { + throw Error.ArgumentNull(nameof(edmModel)); + } + + assembliesResolver = assembliesResolver ?? AssemblyResolverHelper.Default; + + // Let's search from cache + TypeCacheItem map = GetOrCreateCacheItem(edmModel); + if (map.TryFindClrType(edmType, nullable, out Type clrType)) + { + return clrType; + } + + // If not cached, find the CLR type from the model. + clrType = FindClrType(edmModel, edmType, assembliesResolver); + + if (clrType != null && nullable && clrType.IsEnum) + { + clrType = TypeHelper.ToNullable(clrType); + } + + map.AddEdmToClrMap(edmType, nullable, clrType); + + return clrType; + } + + /// + /// Finds the corresponding CLR type for a given Edm type reference. + /// + /// The Edm model. + /// The Edm type. + /// The assembly resolver. + /// Null or the CLR type. + internal static Type FindClrType(IEdmModel edmModel, IEdmType edmType, IAssemblyResolver assembliesResolver) + { + if (edmModel == null) + { + throw Error.ArgumentNull(nameof(edmModel)); + } + + if (edmType == null) + { + throw Error.ArgumentNull(nameof(edmType)); + } + + if (assembliesResolver == null) + { + throw Error.ArgumentNull(nameof(assembliesResolver)); + } + + IEdmSchemaType edmSchemaType = edmType as IEdmSchemaType; + if (edmSchemaType == null) + { + return null; + } + + // by default, retrieve it from Clr type annotation. + ClrTypeAnnotation annotation = edmModel.GetAnnotationValue(edmSchemaType); + if (annotation != null) + { + return annotation.ClrType; + } + + string typeName = edmSchemaType.FullName(); + IEnumerable matchingTypes = GetMatchingTypes(typeName, assembliesResolver); + + if (matchingTypes.Count() > 1) + { + throw Error.Argument("edmTypeReference", SRResources.MultipleMatchingClrTypesForEdmType, + typeName, string.Join(",", matchingTypes.Select(type => type.AssemblyQualifiedName))); + } + + Type clrType = matchingTypes.SingleOrDefault(); + + // TODO: shall we save it back to model because we will cache it? + // I think we should not save it back to model, since we will cache it + // edmModel.SetAnnotationValue(edmSchemaType, new ClrTypeAnnotation(clrType)); + return clrType; + } + #endregion + + private TypeCacheItem GetOrCreateCacheItem(IEdmModel model) + { + if (!_cache.TryGetValue(model, out TypeCacheItem map)) + { + map = new TypeCacheItem(); + _cache[model] = map; + } + + return map; + } + + private static Type ExtractGenericInterface(Type queryType, Type interfaceType) + { + Func matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType; + return matchesInterface(queryType) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface); + } + + private static IEnumerable GetMatchingTypes(string edmFullName, IAssemblyResolver assembliesResolver) + => TypeHelper.GetLoadedTypes(assembliesResolver).Where(t => t.IsPublic && t.EdmFullName() == edmFullName); + + private static KeyValuePair BuildTypeMapping1(EdmPrimitiveTypeKind primitiveKind) + => new KeyValuePair(typeof(T), EdmCoreModel.Instance.GetPrimitive(primitiveKind, typeof(T).IsNullable())); + + private static void BuildTypeMapping(EdmPrimitiveTypeKind primitiveKind, bool isStandard = true) + { + Type type = typeof(T); + bool isNullable = type.IsNullable(); + IEdmPrimitiveTypeReference edmPrimitiveTypeReference = EdmCoreModel.Instance.GetPrimitive(primitiveKind, isNullable); + ClrPrimitiveTypes[type] = edmPrimitiveTypeReference; + + if (isStandard) + { + IEdmPrimitiveType primitiveType = edmPrimitiveTypeReference.PrimitiveDefinition(); + if (isNullable) + { + // for nullable, for example System.String, we don't have non-nullable string. + // so, let's save it for both. + // And since we make the order un-changable, it means 'nullable' coming first. + // Therefore, for simplicity, we can safe call "TryAdd". + EdmPrimitiveTypes.TryAdd(primitiveType, (type, type)); + } + else + { + EdmPrimitiveTypes.AddOrUpdate(primitiveType, t => (type, null), (t, o) => (type, o.Item2)); + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.OData/Edm/EdmClrTypeMapExtensions.cs b/src/Microsoft.AspNetCore.OData/Edm/EdmClrTypeMapExtensions.cs index a32f83c5b..a42bf89d9 100644 --- a/src/Microsoft.AspNetCore.OData/Edm/EdmClrTypeMapExtensions.cs +++ b/src/Microsoft.AspNetCore.OData/Edm/EdmClrTypeMapExtensions.cs @@ -6,19 +6,12 @@ //------------------------------------------------------------------------------ using System; -using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Globalization; -using System.IO; using System.Linq; -using System.Xml.Linq; using Microsoft.AspNetCore.OData.Abstracts; -using Microsoft.AspNetCore.OData.Common; -using Microsoft.AspNetCore.OData.Deltas; -using Microsoft.AspNetCore.OData.Query.Wrapper; using Microsoft.OData.Edm; using Microsoft.OData.ModelBuilder; -using Microsoft.Spatial; namespace Microsoft.AspNetCore.OData.Edm { @@ -27,134 +20,75 @@ namespace Microsoft.AspNetCore.OData.Edm /// internal static class EdmClrTypeMapExtensions { - #region PrimitiveTypeMapping /// - /// The mapping between Edm primitive type and Clr primitive type. - /// - private static IDictionary _builtInPrimitiveTypes = new[] - { - BuildTypeMapping(EdmPrimitiveTypeKind.String), - BuildTypeMapping(EdmPrimitiveTypeKind.Boolean), - BuildTypeMapping(EdmPrimitiveTypeKind.Boolean), - BuildTypeMapping(EdmPrimitiveTypeKind.Byte), - BuildTypeMapping(EdmPrimitiveTypeKind.Byte), - BuildTypeMapping(EdmPrimitiveTypeKind.Decimal), - BuildTypeMapping(EdmPrimitiveTypeKind.Decimal), - BuildTypeMapping(EdmPrimitiveTypeKind.Double), - BuildTypeMapping(EdmPrimitiveTypeKind.Double), - BuildTypeMapping(EdmPrimitiveTypeKind.Guid), - BuildTypeMapping(EdmPrimitiveTypeKind.Guid), - BuildTypeMapping(EdmPrimitiveTypeKind.Int16), - BuildTypeMapping(EdmPrimitiveTypeKind.Int16), - BuildTypeMapping(EdmPrimitiveTypeKind.Int32), - BuildTypeMapping(EdmPrimitiveTypeKind.Int32), - BuildTypeMapping(EdmPrimitiveTypeKind.Int64), - BuildTypeMapping(EdmPrimitiveTypeKind.Int64), - BuildTypeMapping(EdmPrimitiveTypeKind.SByte), - BuildTypeMapping(EdmPrimitiveTypeKind.SByte), - BuildTypeMapping(EdmPrimitiveTypeKind.Single), - BuildTypeMapping(EdmPrimitiveTypeKind.Single), - BuildTypeMapping(EdmPrimitiveTypeKind.Binary), - BuildTypeMapping(EdmPrimitiveTypeKind.Stream), - BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset), - BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset), - BuildTypeMapping(EdmPrimitiveTypeKind.Duration), - BuildTypeMapping(EdmPrimitiveTypeKind.Duration), - BuildTypeMapping(EdmPrimitiveTypeKind.Date), - BuildTypeMapping(EdmPrimitiveTypeKind.Date), - BuildTypeMapping(EdmPrimitiveTypeKind.TimeOfDay), - BuildTypeMapping(EdmPrimitiveTypeKind.TimeOfDay), - BuildTypeMapping(EdmPrimitiveTypeKind.Geography), - BuildTypeMapping(EdmPrimitiveTypeKind.GeographyPoint), - BuildTypeMapping(EdmPrimitiveTypeKind.GeographyLineString), - BuildTypeMapping(EdmPrimitiveTypeKind.GeographyPolygon), - BuildTypeMapping(EdmPrimitiveTypeKind.GeographyCollection), - BuildTypeMapping(EdmPrimitiveTypeKind.GeographyMultiLineString), - BuildTypeMapping(EdmPrimitiveTypeKind.GeographyMultiPoint), - BuildTypeMapping(EdmPrimitiveTypeKind.GeographyMultiPolygon), - BuildTypeMapping(EdmPrimitiveTypeKind.Geometry), - BuildTypeMapping(EdmPrimitiveTypeKind.GeometryPoint), - BuildTypeMapping(EdmPrimitiveTypeKind.GeometryLineString), - BuildTypeMapping(EdmPrimitiveTypeKind.GeometryPolygon), - BuildTypeMapping(EdmPrimitiveTypeKind.GeometryCollection), - BuildTypeMapping(EdmPrimitiveTypeKind.GeometryMultiLineString), - BuildTypeMapping(EdmPrimitiveTypeKind.GeometryMultiPoint), - BuildTypeMapping(EdmPrimitiveTypeKind.GeometryMultiPolygon), - - // non-standard mappings - BuildTypeMapping(EdmPrimitiveTypeKind.String), - BuildTypeMapping(EdmPrimitiveTypeKind.Int32), - BuildTypeMapping(EdmPrimitiveTypeKind.Int32), - BuildTypeMapping(EdmPrimitiveTypeKind.Int64), - BuildTypeMapping(EdmPrimitiveTypeKind.Int64), - BuildTypeMapping(EdmPrimitiveTypeKind.Int64), - BuildTypeMapping(EdmPrimitiveTypeKind.Int64), - BuildTypeMapping(EdmPrimitiveTypeKind.String), - BuildTypeMapping(EdmPrimitiveTypeKind.String), - BuildTypeMapping(EdmPrimitiveTypeKind.String), - BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset), - BuildTypeMapping(EdmPrimitiveTypeKind.DateTimeOffset) - } - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - - /// - /// Gets the corresponding Edm primitive type for the given CLR type. + /// Gets the corresponding Edm primitive type for a given type. /// /// The given CLR type. /// Null or the Edm primitive type. public static IEdmPrimitiveTypeReference GetEdmPrimitiveTypeReference(this Type clrType) { - return _builtInPrimitiveTypes.TryGetValue(clrType, out IEdmPrimitiveTypeReference primitive) ? primitive : null; + return DefaultODataTypeMapper.Default.GetPrimitiveType(clrType); } /// - /// Gets the corresponding Edm primitive type for the given CLR type. + /// Gets the corresponding Edm primitive type for a given type. /// + /// The Edm model. /// The given CLR type. /// Null or the Edm primitive type. - public static IEdmPrimitiveType GetEdmPrimitiveType(this Type clrType) + public static IEdmPrimitiveTypeReference GetEdmPrimitiveTypeReference(this IEdmModel edmModel, Type clrType) { - return _builtInPrimitiveTypes.TryGetValue(clrType, out IEdmPrimitiveTypeReference primitive) ? - (IEdmPrimitiveType)primitive.Definition : null; + if (edmModel == null || edmModel is EdmCoreModel) + { + return DefaultODataTypeMapper.Default.GetPrimitiveType(clrType); + } + + return edmModel.GetTypeMapper().GetPrimitiveType(clrType); } /// /// Gets the corresponding CLR type for a given Edm primitive type. /// + /// The Edm model. /// The given Edm primitive type. /// Null or the CLR type. - public static Type GetClrPrimitiveType(this IEdmPrimitiveTypeReference edmPrimitiveType) + public static Type GetClrPrimitiveType(this IEdmModel edmModel, IEdmPrimitiveTypeReference edmPrimitiveType) { - return _builtInPrimitiveTypes - .Where(kvp => edmPrimitiveType.Definition.IsEquivalentTo(kvp.Value.Definition) && (!edmPrimitiveType.IsNullable || IsNullable(kvp.Key))) - .Select(kvp => kvp.Key) - .FirstOrDefault(); + if (edmPrimitiveType == null) + { + return null; + } + + if (edmModel == null || edmModel is EdmCoreModel) + { + return DefaultODataTypeMapper.Default.GetPrimitiveType(edmPrimitiveType.PrimitiveDefinition(), edmPrimitiveType.IsNullable); + } + + return edmModel.GetTypeMapper().GetPrimitiveType(edmPrimitiveType); } /// /// Figures out if the given clr type is nonstandard edm primitive like uint, ushort, char[] etc. /// and returns the corresponding clr type to which we map like uint => long. /// + /// The Edm model. /// The potential non-standard CLR type. /// A boolean value out to indicate whether the input CLR type is standard OData primitive type. /// The standard CLR type or the input CLR type itself. - public static Type IsNonstandardEdmPrimitive(this Type clrType, out bool isNonstandardEdmPrimitive) + public static Type IsNonstandardEdmPrimitive(this IEdmModel edmModel, Type clrType, out bool isNonstandardEdmPrimitive) { - IEdmPrimitiveTypeReference edmType = clrType?.GetEdmPrimitiveTypeReference(); + IEdmPrimitiveTypeReference edmType = edmModel.GetEdmPrimitiveTypeReference(clrType); if (edmType == null) { isNonstandardEdmPrimitive = false; return clrType; } - Type reverseLookupClrType = edmType.GetClrPrimitiveType(); + Type reverseLookupClrType = edmModel.GetClrPrimitiveType(edmType); isNonstandardEdmPrimitive = (clrType != reverseLookupClrType); return reverseLookupClrType; } - #endregion - - #region ClrType -> EdmType /// /// Gets the Edm type reference from the CLR type. @@ -164,14 +98,7 @@ public static Type IsNonstandardEdmPrimitive(this Type clrType, out bool isNonst /// null or the Edm type reference. public static IEdmTypeReference GetEdmTypeReference(this IEdmModel edmModel, Type clrType) { - IEdmType edmType = edmModel.GetEdmType(clrType); - if (edmType != null) - { - bool isNullable = IsNullable(clrType); - return edmType.ToEdmTypeReference(isNullable); - } - - return null; + return edmModel.GetTypeMapper().GetEdmTypeReference(edmModel, clrType); } /// @@ -182,97 +109,8 @@ public static IEdmTypeReference GetEdmTypeReference(this IEdmModel edmModel, Typ /// null or the Edm type. public static IEdmType GetEdmType(this IEdmModel edmModel, Type clrType) { - if (edmModel == null) - { - throw Error.ArgumentNull(nameof(edmModel)); - } - - if (clrType == null) - { - throw Error.ArgumentNull(nameof(clrType)); - } - - return GetEdmType(edmModel, clrType, testCollections: true); - } - - private static IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCollections) - { - Contract.Assert(edmModel != null); - Contract.Assert(clrType != null); - - IEdmPrimitiveType primitiveType = clrType.GetEdmPrimitiveType(); - if (primitiveType != null) - { - return primitiveType; - } - else - { - if (testCollections) - { - Type entityType; - if (IsDeltaSetWrapper(clrType, out entityType)) - { - IEdmType elementType = GetEdmType(edmModel, entityType, testCollections: false); - if (elementType != null) - { - return new EdmCollectionType(elementType.ToEdmTypeReference(IsNullable(entityType))); - } - } - - Type enumerableOfT = ExtractGenericInterface(clrType, typeof(IEnumerable<>)); - if (enumerableOfT != null) - { - Type elementClrType = enumerableOfT.GetGenericArguments()[0]; - - // IEnumerable> is a collection of T. - if (IsSelectExpandWrapper(elementClrType, out entityType)) - { - elementClrType = entityType; - } - - if (IsComputeWrapper(elementClrType, out entityType)) - { - elementClrType = entityType; - } - - IEdmType elementType = GetEdmType(edmModel, elementClrType, testCollections: false); - if (elementType != null) - { - return new EdmCollectionType(elementType.ToEdmTypeReference(IsNullable(elementClrType))); - } - } - } - - Type underlyingType = TypeHelper.GetUnderlyingTypeOrSelf(clrType); - if (TypeHelper.IsEnum(underlyingType)) - { - clrType = underlyingType; - } - - // search for the ClrTypeAnnotation and return it if present - IEdmType returnType = - edmModel - .SchemaElements - .OfType() - .Select(edmType => new { EdmType = edmType, Annotation = edmModel.GetAnnotationValue(edmType) }) - .Where(tuple => tuple.Annotation != null && tuple.Annotation.ClrType == clrType) - .Select(tuple => tuple.EdmType) - .SingleOrDefault(); - - // default to the EdmType with the same name as the ClrType name - returnType = returnType ?? edmModel.FindType(clrType.EdmFullName()); - - if (clrType.BaseType != null) - { - // go up the inheritance tree to see if we have a mapping defined for the base type. - returnType = returnType ?? GetEdmType(edmModel, clrType.BaseType, testCollections); - } - return returnType; - } + return edmModel.GetEdmTypeReference(clrType)?.Definition; } - #endregion - - #region EdmType -> ClrType /// /// Gets the corresponding CLR type for a given Edm type reference. @@ -282,7 +120,7 @@ private static IEdmType GetEdmType(IEdmModel edmModel, Type clrType, bool testCo /// Null or the CLR type. public static Type GetClrType(this IEdmModel edmModel, IEdmTypeReference edmTypeReference) { - return edmModel.GetClrType(edmTypeReference, AssemblyResolverHelper.Default); + return edmModel.GetTypeMapper().GetClrType(edmModel, edmTypeReference, AssemblyResolverHelper.Default); } /// @@ -294,99 +132,30 @@ public static Type GetClrType(this IEdmModel edmModel, IEdmTypeReference edmType /// Null or the CLR type. public static Type GetClrType(this IEdmModel edmModel, IEdmTypeReference edmTypeReference, IAssemblyResolver assembliesResolver) { - if (edmTypeReference == null) - { - throw Error.ArgumentNull(nameof(edmTypeReference)); - } - - if (edmTypeReference.IsPrimitive()) - { - return GetClrPrimitiveType((IEdmPrimitiveTypeReference)edmTypeReference); - } - else - { - Type clrType = edmModel.GetClrType(edmTypeReference.Definition, assembliesResolver); - if (clrType != null && clrType.IsEnum && edmTypeReference.IsNullable) - { - return TypeHelper.ToNullable(clrType); - } - - return clrType; - } + return edmModel.GetTypeMapper().GetClrType(edmModel, edmTypeReference, assembliesResolver); } /// - /// Gets the corresponding CLR type for a given Edm type reference. + /// Gets the corresponding CLR type for a given Edm type. /// /// The Edm model. /// The Edm type. /// Null or the CLR type. - internal static Type GetClrType(this IEdmModel edmModel, IEdmType edmType) + public static Type GetClrType(this IEdmModel edmModel, IEdmType edmType) { return edmModel.GetClrType(edmType, AssemblyResolverHelper.Default); } /// - /// Gets the corresponding CLR type for a given Edm type reference. + /// Gets the corresponding CLR type for a given Edm type. /// /// The Edm model. /// The Edm type. /// The assembly resolver. /// Null or the CLR type. - internal static Type GetClrType(this IEdmModel edmModel, IEdmType edmType, IAssemblyResolver assembliesResolver) + public static Type GetClrType(this IEdmModel edmModel, IEdmType edmType, IAssemblyResolver assembliesResolver) { - if (edmType == null) - { - throw Error.ArgumentNull(nameof(edmType)); - } - - IEdmSchemaType edmSchemaType = edmType as IEdmSchemaType; - Contract.Assert(edmSchemaType != null); - - ClrTypeAnnotation annotation = edmModel.GetAnnotationValue(edmSchemaType); - if (annotation != null) - { - return annotation.ClrType; - } - - string typeName = edmSchemaType.FullName(); - IEnumerable matchingTypes = GetMatchingTypes(typeName, assembliesResolver); - - if (matchingTypes.Count() > 1) - { - throw Error.Argument("edmTypeReference", SRResources.MultipleMatchingClrTypesForEdmType, - typeName, string.Join(",", matchingTypes.Select(type => type.AssemblyQualifiedName))); - } - - Type type = matchingTypes.SingleOrDefault(); - if (type == null) - { - return null; - } - - edmModel.SetAnnotationValue(edmSchemaType, new ClrTypeAnnotation(matchingTypes.SingleOrDefault())); - return matchingTypes.SingleOrDefault(); - } - - #endregion - - internal static ClrTypeCache GetTypeMappingCache(this IEdmModel model) - { - Contract.Assert(model != null); - - ClrTypeCache typeMappingCache = model.GetAnnotationValue(model); - if (typeMappingCache == null) - { - typeMappingCache = new ClrTypeCache(); - model.SetAnnotationValue(model, typeMappingCache); - } - - return typeMappingCache; - } - - private static IEnumerable GetMatchingTypes(string edmFullName, IAssemblyResolver assembliesResolver) - { - return TypeHelper.GetLoadedTypes(assembliesResolver).Where(t => t.IsPublic && t.EdmFullName() == edmFullName); + return edmModel.GetTypeMapper().GetClrType(edmModel, edmType, true, assembliesResolver); } internal static string EdmFullName(this Type clrType) @@ -420,63 +189,5 @@ private static string MangleClrTypeName(Type type) String.Join("_", type.GetGenericArguments().Select(t => MangleClrTypeName(t)))); } } - - private static Type ExtractGenericInterface(Type queryType, Type interfaceType) - { - Func matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType; - return matchesInterface(queryType) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface); - } - - private static bool IsDeltaSetWrapper(Type type, out Type entityType) => IsTypeWrapper(typeof(DeltaSet<>), type, out entityType); - - private static bool IsSelectExpandWrapper(Type type, out Type entityType) => IsTypeWrapper(typeof(SelectExpandWrapper<>), type, out entityType); - - internal static bool IsComputeWrapper(Type type, out Type entityType) => IsTypeWrapper(typeof(ComputeWrapper<>), type, out entityType); - - private static bool IsTypeWrapper(Type wrappedType, Type type, out Type entityType) - { - if (type == null) - { - entityType = null; - return false; - } - - if (type.IsGenericType && type.GetGenericTypeDefinition() == wrappedType) - { - entityType = type.GetGenericArguments()[0]; - return true; - } - - return IsTypeWrapper(wrappedType, type.BaseType, out entityType); - } - - private static KeyValuePair BuildTypeMapping(EdmPrimitiveTypeKind primitiveKind) - => new KeyValuePair(typeof(T), EdmCoreModel.Instance.GetPrimitive(primitiveKind, IsNullable())); - - /// - /// Check the input type is nullable type or not. - /// - /// The input CLR type. - /// True/False. - private static bool IsNullable(Type type) - { - if (type == null) - { - return false; - } - - return !type.IsValueType || Nullable.GetUnderlyingType(type) != null; - } - - /// - /// Check the input type is nullable or not. - /// - /// The test CRL type. - /// True/False. - private static bool IsNullable() - { - Type type = typeof(T); - return IsNullable(type); - } } } diff --git a/src/Microsoft.AspNetCore.OData/Edm/EdmModelAnnotationExtensions.cs b/src/Microsoft.AspNetCore.OData/Edm/EdmModelAnnotationExtensions.cs index a6841d522..b452e079c 100644 --- a/src/Microsoft.AspNetCore.OData/Edm/EdmModelAnnotationExtensions.cs +++ b/src/Microsoft.AspNetCore.OData/Edm/EdmModelAnnotationExtensions.cs @@ -261,6 +261,48 @@ public static void SetModelName(this IEdmModel model, string name) model.SetAnnotationValue(model, new ModelNameAnnotation(name)); } + /// + /// Gets the OData type mapping provider from the model. + /// + /// The Edm model. + /// The . + public static IODataTypeMapper GetTypeMapper(this IEdmModel model) + { + // use the default one if no model or no mapper registered. + if (model == null) + { + return DefaultODataTypeMapper.Default; + } + + IODataTypeMapper provider = model.GetAnnotationValue(model); + if (provider == null) + { + return DefaultODataTypeMapper.Default; + } + + return provider; + } + + /// + /// Sets the OData type mapping provider to the model. + /// + /// The Edm model. + /// The given mapper. + public static void SetTypeMapper(this IEdmModel model, IODataTypeMapper mapper) + { + if (model == null) + { + throw Error.ArgumentNull(nameof(model)); + } + + if (mapper == null) + { + throw Error.ArgumentNull(nameof(mapper)); + } + + model.SetAnnotationValue(model, mapper); + } + /// /// Gets the declared alternate keys of the most defined entity with a declared key present. /// Each entity type could define a set of alternate keys. diff --git a/src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapper.cs b/src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapper.cs new file mode 100644 index 000000000..089fffbc8 --- /dev/null +++ b/src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapper.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; + +namespace Microsoft.AspNetCore.OData.Edm +{ + /// + /// Provides the mapping between CLR type and Edm type. + /// + public interface IODataTypeMapper + { + /// + /// Gets the corresponding Edm primitive type for a given . + /// + /// The given CLR type. + /// Null or the Edm primitive type. + IEdmPrimitiveTypeReference GetPrimitiveType(Type clrType); + + /// + /// Gets the corresponding for a given Edm primitive type . + /// + /// The given Edm primitive type. + /// The nullable or not. + /// Null or the CLR type. + Type GetPrimitiveType(IEdmPrimitiveType primitiveType, bool nullable); + + /// + /// Gets the corresponding Edm type for the given CLR type . + /// + /// The given Edm model. + /// The given CLR type. + /// Null or the corresponding Edm type reference. + IEdmTypeReference GetEdmTypeReference(IEdmModel edmModel, Type clrType); + + /// + /// Gets the corresponding for a given Edm type . + /// + /// The given Edm model. + /// The given Edm type. + /// The nullable or not. + /// The assembly resolver. + /// Null or the CLR type. + Type GetClrType(IEdmModel edmModel, IEdmType edmType, bool nullable, IAssemblyResolver assembliesResolver); + } +} diff --git a/src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapperExtensions.cs b/src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapperExtensions.cs new file mode 100644 index 000000000..f7b638f77 --- /dev/null +++ b/src/Microsoft.AspNetCore.OData/Edm/IODataTypeMapperExtensions.cs @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using Microsoft.AspNetCore.OData.Abstracts; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; + +namespace Microsoft.AspNetCore.OData.Edm +{ + /// + /// Extension methods for . + /// + public static class IODataTypeMapperExtensions + { + /// + /// Gets the corresponding for a given Edm primitive type . + /// + /// The type mapper. + /// The Edm primitive type reference. + /// Null or the CLR type. + public static Type GetPrimitiveType(this IODataTypeMapper mapper, IEdmPrimitiveTypeReference primitiveType) + { + if (mapper == null) + { + throw Error.ArgumentNull(nameof(mapper)); + } + + if (primitiveType == null) + { + throw Error.ArgumentNull(nameof(primitiveType)); + } + + return mapper.GetPrimitiveType(primitiveType.PrimitiveDefinition(), primitiveType.IsNullable); + } + + /// + /// Gets the corresponding Edm type for the given CLR type . + /// + /// The type mapper. + /// The given Edm model. + /// The given CLR type. + /// Null or the corresponding Edm type. + public static IEdmType GetEdmType(this IODataTypeMapper mapper, IEdmModel edmModel, Type clrType) + { + if (mapper == null) + { + throw Error.ArgumentNull(nameof(mapper)); + } + + return mapper.GetEdmTypeReference(edmModel, clrType)?.Definition; + } + + /// + /// Gets the corresponding for a given Edm type . + /// + /// The type mapper. + /// The Edm model. + /// The Edm type reference. + /// Null or the CLR type. + public static Type GetClrType(this IODataTypeMapper mapper, IEdmModel edmModel, IEdmTypeReference edmType) + { + return mapper.GetClrType(edmModel, edmType, AssemblyResolverHelper.Default); + } + + /// + /// Gets the corresponding for a given Edm type . + /// + /// The type mapper. + /// The Edm model. + /// The Edm type. + /// The assembly resolver. + /// Null or the CLR type. + public static Type GetClrType(this IODataTypeMapper mapper, IEdmModel edmModel, IEdmTypeReference edmType, IAssemblyResolver assembliesResolver) + { + if (mapper == null) + { + throw Error.ArgumentNull(nameof(mapper)); + } + + if (edmType == null) + { + throw Error.ArgumentNull(nameof(edmType)); + } + + return mapper.GetClrType(edmModel, edmType.Definition, edmType.IsNullable, assembliesResolver); + } + } +} diff --git a/src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs b/src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs new file mode 100644 index 000000000..3fa53dc94 --- /dev/null +++ b/src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Concurrent; +using Microsoft.OData.Edm; + +namespace Microsoft.AspNetCore.OData.Edm +{ + internal class TypeCacheItem + { + #region ClrType => EdmType + /// + /// to . + /// + public ConcurrentDictionary ClrToEdmTypeCache = new ConcurrentDictionary(); + + public bool TryFindEdmType(Type clrType, out IEdmTypeReference edmType) + { + edmType = null; + if (clrType == null) + { + return false; + } + + return ClrToEdmTypeCache.TryGetValue(clrType, out edmType); + } + + public void AddClrToEdmMap(Type clrType, IEdmTypeReference edmType) + { + ClrToEdmTypeCache[clrType] = edmType; + } + #endregion + + #region EdmType => ClrType + /// + /// to . + /// item1: non-nullable + /// item2: nullable + /// + public ConcurrentDictionary EdmToClrTypeCache = new ConcurrentDictionary(); + + public bool TryFindClrType(IEdmType edmType, bool isNullable, out Type clrType) + { + if (edmType == null) + { + clrType = null; + return false; + } + + clrType = null; + if (EdmToClrTypeCache.TryGetValue(edmType, out (Type, Type) clrTypes)) + { + if (isNullable) + { + clrType = clrTypes.Item2; + } + else + { + clrType = clrTypes.Item1; + } + } + + return clrType != null; + } + + public void AddEdmToClrMap(IEdmType edmType, bool isNullable, Type clrType) + { + if (isNullable) + { + EdmToClrTypeCache.AddOrUpdate(edmType, (null, clrType), (k, v) => (v.Item1, clrType)); + } + else + { + EdmToClrTypeCache.AddOrUpdate(edmType, (clrType, null), (k, v) => (clrType, v.Item2)); + } + } + #endregion + } +} diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ConventionsHelpers.cs b/src/Microsoft.AspNetCore.OData/Formatter/ConventionsHelpers.cs index 9f24c8f4c..1f3b63e6b 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/ConventionsHelpers.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/ConventionsHelpers.cs @@ -49,10 +49,10 @@ private static object GetKeyValue(IEdmProperty key, ResourceContext resourceCont throw Error.InvalidOperation(SRResources.KeyValueCannotBeNull, key.Name, edmType.Definition); } - return ConvertValue(value, resourceContext.TimeZone); + return ConvertValue(value, resourceContext.TimeZone, resourceContext.EdmModel); } - public static object ConvertValue(object value, TimeZoneInfo timeZone) + public static object ConvertValue(object value, TimeZoneInfo timeZone, IEdmModel model) { Contract.Assert(value != null); @@ -63,7 +63,7 @@ public static object ConvertValue(object value, TimeZoneInfo timeZone) } else { - Contract.Assert(type.GetEdmPrimitiveType() != null); + Contract.Assert(model.GetEdmPrimitiveTypeReference(type) != null); value = ODataPrimitiveSerializer.ConvertUnsupportedPrimitives(value, timeZone); } @@ -113,7 +113,6 @@ public static string GetUriRepresentationForValue(object value, TimeZoneInfo tim } else { - Contract.Assert(type.GetEdmPrimitiveType() != null); value = ODataPrimitiveSerializer.ConvertUnsupportedPrimitives(value, timeZone); } diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/CollectionDeserializationHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/CollectionDeserializationHelper.cs index 9ec11ca21..a139df107 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/CollectionDeserializationHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/CollectionDeserializationHelper.cs @@ -12,7 +12,6 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using Microsoft.AspNetCore.OData.Common; using Microsoft.AspNetCore.OData.Edm; using Microsoft.AspNetCore.OData.Formatter.Value; using Microsoft.OData.Edm; @@ -26,7 +25,7 @@ internal static class CollectionDeserializationHelpers private static readonly MethodInfo _toArrayMethodInfo = typeof(Enumerable).GetMethod("ToArray"); public static void AddToCollection(this IEnumerable items, IEnumerable collection, Type elementType, - Type resourceType, string propertyName, Type propertyType, TimeZoneInfo timeZoneInfo = null) + Type resourceType, string propertyName, Type propertyType, ODataDeserializerContext context = null) { Contract.Assert(items != null); Contract.Assert(collection != null); @@ -53,10 +52,10 @@ public static void AddToCollection(this IEnumerable items, IEnumerable collectio throw new SerializationException(message); } - items.AddToCollectionCore(collection, elementType, list, addMethod, timeZoneInfo); + items.AddToCollectionCore(collection, elementType, list, addMethod, context); } - public static void AddToCollection(this IEnumerable items, IEnumerable collection, Type elementType, string paramName, Type paramType, TimeZoneInfo timeZoneInfo = null) + public static void AddToCollection(this IEnumerable items, IEnumerable collection, Type elementType, string paramName, Type paramType, ODataDeserializerContext context = null) { Contract.Assert(items != null); Contract.Assert(collection != null); @@ -76,13 +75,14 @@ public static void AddToCollection(this IEnumerable items, IEnumerable collectio } } - items.AddToCollectionCore(collection, elementType, list, addMethod, timeZoneInfo); + items.AddToCollectionCore(collection, elementType, list, addMethod, context); } - private static void AddToCollectionCore(this IEnumerable items, IEnumerable collection, Type elementType, IList list, MethodInfo addMethod, TimeZoneInfo timeZoneInfo = null) + private static void AddToCollectionCore(this IEnumerable items, IEnumerable collection, Type elementType, IList list, MethodInfo addMethod, ODataDeserializerContext context = null) { + IEdmModel model = context?.Model; bool isNonstandardEdmPrimitiveCollection; - elementType.IsNonstandardEdmPrimitive(out isNonstandardEdmPrimitiveCollection); + model.IsNonstandardEdmPrimitive(elementType, out isNonstandardEdmPrimitiveCollection); foreach (object item in items) { @@ -91,7 +91,7 @@ private static void AddToCollectionCore(this IEnumerable items, IEnumerable coll if (isNonstandardEdmPrimitiveCollection && element != null) { // convert non-standard edm primitives if required. - element = EdmPrimitiveHelper.ConvertPrimitiveValue(element, elementType, timeZoneInfo); + element = EdmPrimitiveHelper.ConvertPrimitiveValue(element, elementType, context?.TimeZone); } if (list != null) diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs index 10cea2303..a020f9152 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/DeserializationHelper.cs @@ -103,15 +103,15 @@ internal static void SetDeclaredProperty(object resource, EdmTypeKind propertyKi } } - internal static void SetCollectionProperty(object resource, IEdmProperty edmProperty, object value, string propertyName, TimeZoneInfo timeZoneInfo = null) + internal static void SetCollectionProperty(object resource, IEdmProperty edmProperty, object value, string propertyName, ODataDeserializerContext context = null) { Contract.Assert(edmProperty != null); - SetCollectionProperty(resource, propertyName, edmProperty.Type.AsCollection(), value, clearCollection: false, timeZoneInfo: timeZoneInfo); + SetCollectionProperty(resource, propertyName, edmProperty.Type.AsCollection(), value, clearCollection: false, context: context); } internal static void SetCollectionProperty(object resource, string propertyName, - IEdmCollectionTypeReference edmPropertyType, object value, bool clearCollection, TimeZoneInfo timeZoneInfo = null) + IEdmCollectionTypeReference edmPropertyType, object value, bool clearCollection, ODataDeserializerContext context = null) { if (value != null) { @@ -134,7 +134,7 @@ internal static void SetCollectionProperty(object resource, string propertyName, CollectionDeserializationHelpers.TryCreateInstance(propertyType, edmPropertyType, elementType, out newCollection)) { // settable collections - collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType, timeZoneInfo); + collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType, context); if (propertyType.IsArray) { newCollection = CollectionDeserializationHelpers.ToArray(newCollection, elementType); @@ -157,7 +157,7 @@ internal static void SetCollectionProperty(object resource, string propertyName, newCollection.Clear(propertyName, resourceType); } - collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType, timeZoneInfo); + collection.AddToCollection(newCollection, elementType, resourceType, propertyName, propertyType, context); } } } diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs index d0392c8f9..0a7421f65 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Deserialization/ODataDeserializerProvider.cs @@ -103,11 +103,7 @@ public virtual IODataDeserializer GetODataDeserializer(Type type, HttpRequest re } IEdmModel model = request.GetModel(); - //IODataTypeMappingProvider typeMappingProvider = _serviceProvider.GetRequiredService(); - - ClrTypeCache typeMappingCache = model.GetTypeMappingCache(); - IEdmTypeReference edmType = typeMappingCache.GetEdmType(type, model); - //IEdmTypeReference edmType = typeMappingProvider.GetEdmType(model, type); + IEdmTypeReference edmType = model.GetEdmTypeReference(type); if (edmType == null) { diff --git a/src/Microsoft.AspNetCore.OData/Formatter/EdmLibHelper.cs b/src/Microsoft.AspNetCore.OData/Formatter/EdmLibHelper.cs index fbb6cbf3b..0c584233c 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/EdmLibHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/EdmLibHelper.cs @@ -50,7 +50,7 @@ internal static IEdmTypeReference GetExpectedPayloadType(Type type, ODataPath pa else { TryGetInnerTypeForDelta(ref type); - expectedPayloadType = model.GetTypeMappingCache().GetEdmType(type, model); + expectedPayloadType = model.GetEdmTypeReference(type); } return expectedPayloadType; diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs index 9606ca230..d72b413e4 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinder.cs @@ -86,7 +86,8 @@ public Task BindModelAsync(ModelBindingContext bindingContext) HttpRequest request = bindingContext.HttpContext.Request; TimeZoneInfo timeZone = request.GetTimeZoneInfo(); - object model = ODataModelBinderConverter.ConvertTo(valueProviderResult.FirstValue, bindingContext.ModelType, timeZone); + IEdmModel edmModel = request.GetModel(); + object model = ODataModelBinderConverter.ConvertTo(valueProviderResult.FirstValue, bindingContext.ModelType, timeZone, edmModel); if (model != null) { bindingContext.Result = ModelBindingResult.Success(model); diff --git a/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinderConverter.cs b/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinderConverter.cs index a04af505b..bfd4b5051 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinderConverter.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/ODataModelBinderConverter.cs @@ -94,7 +94,7 @@ public static object Convert(object graph, IEdmTypeReference edmTypeReference, return ConvertResourceOrResourceSet(graph, edmTypeReference, readContext); } - internal static object ConvertTo(string valueString, Type type, TimeZoneInfo timeZone) + internal static object ConvertTo(string valueString, Type type, TimeZoneInfo timeZone, IEdmModel edmModel = null) { if (valueString == null) { @@ -137,8 +137,8 @@ internal static object ConvertTo(string valueString, Type type, TimeZoneInfo tim // can return the correct Date object. if (type == typeof(Date) || type == typeof(Date?)) { - EdmCoreModel model = EdmCoreModel.Instance; - IEdmPrimitiveTypeReference dateTypeReference = type.GetEdmPrimitiveTypeReference(); + IEdmModel model = edmModel ?? EdmCoreModel.Instance; + IEdmPrimitiveTypeReference dateTypeReference = model.GetEdmPrimitiveTypeReference(type); return ODataUriUtils.ConvertFromUriLiteral(valueString, ODataVersion.V4, model, dateTypeReference); } @@ -158,7 +158,7 @@ internal static object ConvertTo(string valueString, Type type, TimeZoneInfo tim } bool isNonStandardEdmPrimitive; - type.IsNonstandardEdmPrimitive(out isNonStandardEdmPrimitive); + edmModel.IsNonstandardEdmPrimitive(type, out isNonStandardEdmPrimitive); if (isNonStandardEdmPrimitive) { diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs index de37ac662..cfea75d5a 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerContext.cs @@ -303,14 +303,13 @@ internal IEdmTypeReference GetEdmType(object instance, Type type) throw Error.InvalidOperation(SRResources.RequestMustHaveModel); } - var typeMappingCache = Model.GetTypeMappingCache(); - edmType = typeMappingCache.GetEdmType(type, Model); + edmType = Model.GetEdmTypeReference(type); if (edmType == null) { if (instance != null) { - edmType = typeMappingCache.GetEdmType(instance.GetType(), Model); + edmType = Model.GetEdmTypeReference(instance.GetType()); } if (edmType == null) @@ -320,7 +319,7 @@ internal IEdmTypeReference GetEdmType(object instance, Type type) } else if (instance != null) { - IEdmTypeReference actualType = typeMappingCache.GetEdmType(instance.GetType(), Model); + IEdmTypeReference actualType = Model.GetEdmTypeReference(instance.GetType()); if (actualType != null && actualType != edmType) { edmType = actualType; diff --git a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs index 91407c940..ba53316c6 100644 --- a/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs +++ b/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataSerializerProvider.cs @@ -123,8 +123,7 @@ public virtual IODataSerializer GetODataPayloadSerializer(Type type, HttpRequest IEdmModel model = request.GetModel(); // if it is not a special type, assume it has a corresponding EdmType. - ClrTypeCache typeMappingCache = model.GetTypeMappingCache(); - IEdmTypeReference edmType = typeMappingCache.GetEdmType(type, model); + IEdmTypeReference edmType = model.GetEdmTypeReference(type); if (edmType != null) { diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml index 46b2fceaa..4a1366781 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml @@ -1920,42 +1920,109 @@ The output of method info. True if the method info was found, false otherwise. - + - The extensions used to map between C# types and Edm types. + The default implementation for . + + + + + Creates a static instance for the Default type mapper. + + + + + The default mapping between Edm primitive type and Clr primitive type. + Primitive types are cross Edm models. - + - The mapping between Edm primitive type and Clr primitive type. + Item1 --> non-nullable + Item2 --> nullable + + + + + Gets the corresponding Edm primitive type for a given type. + + The given CLR type. + Null or the Edm primitive type. + + + + Gets the corresponding type for a given Edm primitive type . + + The given Edm primitive type. + The nullable or not. + Null or the CLR type. + + + + The cache used to hold the type mapping between and . + + + + + Gets the corresponding Edm type for the given CLR type . + + The given Edm model. + The given CLR type. + Null or the corresponding Edm type reference. + + + + Gets the corresponding for a given Edm type . + + The Edm model. + The Edm type. + The nullable or not. + The assembly resolver. if it's null, will use the default resolver. + Null or the CLR type. + + + + Finds the corresponding CLR type for a given Edm type reference. + + The Edm model. + The Edm type. + The assembly resolver. + Null or the CLR type. + + + + The extensions used to map between C# types and Edm types. - Gets the corresponding Edm primitive type for the given CLR type. + Gets the corresponding Edm primitive type for a given type. The given CLR type. Null or the Edm primitive type. - + - Gets the corresponding Edm primitive type for the given CLR type. + Gets the corresponding Edm primitive type for a given type. + The Edm model. The given CLR type. Null or the Edm primitive type. - + Gets the corresponding CLR type for a given Edm primitive type. + The Edm model. The given Edm primitive type. Null or the CLR type. - + Figures out if the given clr type is nonstandard edm primitive like uint, ushort, char[] etc. and returns the corresponding clr type to which we map like uint => long. + The Edm model. The potential non-standard CLR type. A boolean value out to indicate whether the input CLR type is standard OData primitive type. The standard CLR type or the input CLR type itself. @@ -1995,7 +2062,7 @@ - Gets the corresponding CLR type for a given Edm type reference. + Gets the corresponding CLR type for a given Edm type. The Edm model. The Edm type. @@ -2003,27 +2070,13 @@ - Gets the corresponding CLR type for a given Edm type reference. + Gets the corresponding CLR type for a given Edm type. The Edm model. The Edm type. The assembly resolver. Null or the CLR type. - - - Check the input type is nullable type or not. - - The input CLR type. - True/False. - - - - Check the input type is nullable or not. - - The test CRL type. - True/False. - Provides the functionalities related to the Edm type. @@ -2118,6 +2171,20 @@ The Edm model. The Edm model name. + + + Gets the OData type mapping provider from the model. + + The Edm model. + The . + + + + Sets the OData type mapping provider to the model. + + The Edm model. + The given mapper. + Gets the declared alternate keys of the most defined entity with a declared key present. @@ -2345,6 +2412,85 @@ Gets the whole expand path. + + + Provides the mapping between CLR type and Edm type. + + + + + Gets the corresponding Edm primitive type for a given . + + The given CLR type. + Null or the Edm primitive type. + + + + Gets the corresponding for a given Edm primitive type . + + The given Edm primitive type. + The nullable or not. + Null or the CLR type. + + + + Gets the corresponding Edm type for the given CLR type . + + The given Edm model. + The given CLR type. + Null or the corresponding Edm type reference. + + + + Gets the corresponding for a given Edm type . + + The given Edm model. + The given Edm type. + The nullable or not. + The assembly resolver. + Null or the CLR type. + + + + Extension methods for . + + + + + Gets the corresponding for a given Edm primitive type . + + The type mapper. + The Edm primitive type reference. + Null or the CLR type. + + + + Gets the corresponding Edm type for the given CLR type . + + The type mapper. + The given Edm model. + The given CLR type. + Null or the corresponding Edm type. + + + + Gets the corresponding for a given Edm type . + + The type mapper. + The Edm model. + The Edm type reference. + Null or the CLR type. + + + + Gets the corresponding for a given Edm type . + + The type mapper. + The Edm model. + The Edm type. + The assembly resolver. + Null or the CLR type. + The Edm model name annotation. @@ -2584,6 +2730,18 @@ Gets a boolean indicating whether the link factory follows OData conventions or not. + + + to . + + + + + to . + item1: non-nullable + item2: nullable + + The extension methods for . diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt index 5601247d8..6b5a688f8 100644 --- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt +++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt @@ -222,6 +222,8 @@ Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation.AddMethod(string methodToken, System.Collections.Generic.IDictionary methods) -> Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation.CustomAggregateMethodAnnotation() -> void Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation.GetMethodInfo(string methodToken, System.Type returnType, out System.Reflection.MethodInfo methodInfo) -> bool +Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper +Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper.DefaultODataTypeMapper() -> void Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions Microsoft.AspNetCore.OData.Edm.EntitySelfLinks @@ -232,6 +234,12 @@ Microsoft.AspNetCore.OData.Edm.EntitySelfLinks.IdLink.get -> System.Uri Microsoft.AspNetCore.OData.Edm.EntitySelfLinks.IdLink.set -> void Microsoft.AspNetCore.OData.Edm.EntitySelfLinks.ReadLink.get -> System.Uri Microsoft.AspNetCore.OData.Edm.EntitySelfLinks.ReadLink.set -> void +Microsoft.AspNetCore.OData.Edm.IODataTypeMapper +Microsoft.AspNetCore.OData.Edm.IODataTypeMapper.GetClrType(Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmType edmType, bool nullable, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) -> System.Type +Microsoft.AspNetCore.OData.Edm.IODataTypeMapper.GetEdmTypeReference(Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) -> Microsoft.OData.Edm.IEdmTypeReference +Microsoft.AspNetCore.OData.Edm.IODataTypeMapper.GetPrimitiveType(Microsoft.OData.Edm.IEdmPrimitiveType primitiveType, bool nullable) -> System.Type +Microsoft.AspNetCore.OData.Edm.IODataTypeMapper.GetPrimitiveType(System.Type clrType) -> Microsoft.OData.Edm.IEdmPrimitiveTypeReference +Microsoft.AspNetCore.OData.Edm.IODataTypeMapperExtensions Microsoft.AspNetCore.OData.Edm.ModelNameAnnotation Microsoft.AspNetCore.OData.Edm.ModelNameAnnotation.ModelName.get -> string Microsoft.AspNetCore.OData.Edm.ModelNameAnnotation.ModelNameAnnotation(string name) -> void @@ -1450,7 +1458,9 @@ static Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions.GetClrPropert static Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions.GetConcurrencyProperties(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmNavigationSource navigationSource) -> System.Collections.Generic.IEnumerable static Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions.GetDynamicPropertyDictionary(this Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmStructuredType edmType) -> System.Reflection.PropertyInfo static Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions.GetModelName(this Microsoft.OData.Edm.IEdmModel model) -> string +static Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions.GetTypeMapper(this Microsoft.OData.Edm.IEdmModel model) -> Microsoft.AspNetCore.OData.Edm.IODataTypeMapper static Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions.SetModelName(this Microsoft.OData.Edm.IEdmModel model, string name) -> void +static Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions.SetTypeMapper(this Microsoft.OData.Edm.IEdmModel model, Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper) -> void static Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions.GetNavigationSourceLinkBuilder(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmNavigationSource navigationSource) -> Microsoft.AspNetCore.OData.Edm.NavigationSourceLinkBuilderAnnotation static Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions.GetOperationLinkBuilder(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmOperation operation) -> Microsoft.AspNetCore.OData.Edm.OperationLinkBuilder static Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions.HasEditLink(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmNavigationSource navigationSource, Microsoft.AspNetCore.OData.Edm.SelfLinkBuilder editLinkBuilder) -> void @@ -1459,6 +1469,10 @@ static Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions.HasNavigatio static Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions.HasReadLink(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmNavigationSource navigationSource, Microsoft.AspNetCore.OData.Edm.SelfLinkBuilder readLinkBuilder) -> void static Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions.SetNavigationSourceLinkBuilder(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmNavigationSource navigationSource, Microsoft.AspNetCore.OData.Edm.NavigationSourceLinkBuilderAnnotation navigationSourceLinkBuilder) -> void static Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions.SetOperationLinkBuilder(this Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmOperation operation, Microsoft.AspNetCore.OData.Edm.OperationLinkBuilder operationLinkBuilder) -> void +static Microsoft.AspNetCore.OData.Edm.IODataTypeMapperExtensions.GetClrType(this Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmTypeReference edmType) -> System.Type +static Microsoft.AspNetCore.OData.Edm.IODataTypeMapperExtensions.GetClrType(this Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmTypeReference edmType, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) -> System.Type +static Microsoft.AspNetCore.OData.Edm.IODataTypeMapperExtensions.GetEdmType(this Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) -> Microsoft.OData.Edm.IEdmType +static Microsoft.AspNetCore.OData.Edm.IODataTypeMapperExtensions.GetPrimitiveType(this Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmPrimitiveTypeReference primitiveType) -> System.Type static Microsoft.AspNetCore.OData.Extensions.ActionModelExtensions.AddSelector(this Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel action, string httpMethods, string prefix, Microsoft.OData.Edm.IEdmModel model, Microsoft.AspNetCore.OData.Routing.Template.ODataPathTemplate path, Microsoft.AspNetCore.OData.Routing.ODataRouteOptions options = null) -> void static Microsoft.AspNetCore.OData.Extensions.ActionModelExtensions.GetAttribute(this Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel action) -> T static Microsoft.AspNetCore.OData.Extensions.ActionModelExtensions.HasODataKeyParameter(this Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel action, Microsoft.OData.Edm.IEdmEntityType entityType, string keyPrefix = "key") -> bool @@ -1467,6 +1481,7 @@ static Microsoft.AspNetCore.OData.Extensions.ActionModelExtensions.IsODataIgnore static Microsoft.AspNetCore.OData.Extensions.ControllerModelExtensions.GetAttribute(this Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel controller) -> T static Microsoft.AspNetCore.OData.Extensions.ControllerModelExtensions.HasAttribute(this Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel controller) -> bool static Microsoft.AspNetCore.OData.Extensions.ControllerModelExtensions.IsODataIgnored(this Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel controller) -> bool +static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.GetTypeMappingProvider(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.Edm.IODataTypeMapper static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.ODataBatchFeature(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.Abstracts.IODataBatchFeature static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.ODataFeature(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.Abstracts.IODataFeature static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.ODataOptions(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.ODataOptions @@ -1555,6 +1570,10 @@ virtual Microsoft.AspNetCore.OData.Batch.UnbufferedODataBatchHandler.ExecuteChan virtual Microsoft.AspNetCore.OData.Batch.UnbufferedODataBatchHandler.ExecuteOperationAsync(Microsoft.OData.ODataBatchReader batchReader, System.Guid batchId, Microsoft.AspNetCore.Http.HttpRequest originalRequest, Microsoft.AspNetCore.Http.RequestDelegate handler) -> System.Threading.Tasks.Task virtual Microsoft.AspNetCore.OData.Deltas.Delta.ExpectedClrType.get -> System.Type virtual Microsoft.AspNetCore.OData.Deltas.Delta.StructuredType.get -> System.Type +virtual Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper.GetClrType(Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmType edmType, bool nullable, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) -> System.Type +virtual Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper.GetEdmTypeReference(Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) -> Microsoft.OData.Edm.IEdmTypeReference +virtual Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper.GetPrimitiveType(Microsoft.OData.Edm.IEdmPrimitiveType primitiveType, bool nullable) -> System.Type +virtual Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper.GetPrimitiveType(System.Type clrType) -> Microsoft.OData.Edm.IEdmPrimitiveTypeReference virtual Microsoft.AspNetCore.OData.Edm.NavigationSourceLinkBuilderAnnotation.BuildEditLink(Microsoft.AspNetCore.OData.Formatter.ResourceContext instanceContext, Microsoft.AspNetCore.OData.Formatter.ODataMetadataLevel metadataLevel, System.Uri idLink) -> System.Uri virtual Microsoft.AspNetCore.OData.Edm.NavigationSourceLinkBuilderAnnotation.BuildEntitySelfLinks(Microsoft.AspNetCore.OData.Formatter.ResourceContext instanceContext, Microsoft.AspNetCore.OData.Formatter.ODataMetadataLevel metadataLevel) -> Microsoft.AspNetCore.OData.Edm.EntitySelfLinks virtual Microsoft.AspNetCore.OData.Edm.NavigationSourceLinkBuilderAnnotation.BuildIdLink(Microsoft.AspNetCore.OData.Formatter.ResourceContext instanceContext, Microsoft.AspNetCore.OData.Formatter.ODataMetadataLevel metadataLevel) -> System.Uri diff --git a/src/Microsoft.AspNetCore.OData/Query/EnableQueryAttribute.cs b/src/Microsoft.AspNetCore.OData/Query/EnableQueryAttribute.cs index a41e87aea..cf673463c 100644 --- a/src/Microsoft.AspNetCore.OData/Query/EnableQueryAttribute.cs +++ b/src/Microsoft.AspNetCore.OData/Query/EnableQueryAttribute.cs @@ -88,9 +88,7 @@ public override void OnActionExecuting(ActionExecutingContext actionExecutingCon return; } - Type clrType = edmModel.GetTypeMappingCache().GetClrType( - elementType.ToEdmTypeReference(isNullable: false), - edmModel); + Type clrType = edmModel.GetClrType(elementType.ToEdmTypeReference(isNullable: false)); // CLRType can be missing if untyped registrations were made. if (clrType != null) @@ -726,7 +724,7 @@ private static bool ContainsAutoSelectExpandProperty(object responseValue, IQuer { throw Error.InvalidOperation(SRResources.QueryGetModelMustNotReturnNull); } - IEdmType edmType = model.GetTypeMappingCache().GetEdmType(elementClrType, model)?.Definition; + IEdmType edmType = model.GetEdmTypeReference(elementClrType)?.Definition; IEdmStructuredType structuredType = edmType as IEdmStructuredType; ODataPath path = request.ODataFeature().Path; diff --git a/src/Microsoft.AspNetCore.OData/Query/Expressions/AggregationBinder.cs b/src/Microsoft.AspNetCore.OData/Query/Expressions/AggregationBinder.cs index 2288b2e90..982060916 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Expressions/AggregationBinder.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Expressions/AggregationBinder.cs @@ -104,7 +104,10 @@ private AggregateExpression FixCustomMethodReturnType(AggregateExpression expres } var customMethod = GetCustomMethod(expression); - var typeReference = customMethod.ReturnType.GetEdmPrimitiveTypeReference(); + + // var typeReference = customMethod.ReturnType.GetEdmPrimitiveTypeReference(); + var typeReference = Model.GetEdmPrimitiveTypeReference(customMethod.ReturnType); + return new AggregateExpression(expression.Expression, expression.MethodDefinition, expression.Alias, typeReference); } diff --git a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs index bad9ee3fa..0985ff1cb 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderBase.cs @@ -337,10 +337,10 @@ private Expression BindIsOf(SingleValueFunctionCallNode node) return FalseConstant; } - bool isSourcePrimitiveOrEnum = source.Type.GetEdmPrimitiveType() != null || + bool isSourcePrimitiveOrEnum = Model.GetEdmPrimitiveTypeReference(source.Type) != null || TypeHelper.IsEnum(source.Type); - bool isTargetPrimitiveOrEnum = clrType.GetEdmPrimitiveType() != null || + bool isTargetPrimitiveOrEnum = Model.GetEdmPrimitiveTypeReference(clrType) != null || TypeHelper.IsEnum(clrType); if (isSourcePrimitiveOrEnum && isTargetPrimitiveOrEnum) @@ -731,7 +731,7 @@ private Expression BindCastSingleValue(SingleValueFunctionCallNode node) } if ((!targetEdmTypeReference.IsPrimitive() && !targetEdmTypeReference.IsEnum()) || - (source.Type.GetEdmPrimitiveType() == null && !TypeHelper.IsEnum(source.Type))) + (Model.GetEdmPrimitiveTypeReference(source.Type) == null && !TypeHelper.IsEnum(source.Type))) { // Cast fails and return null. return NullConstant; @@ -1065,7 +1065,7 @@ internal static Expression GetPropertyExpression(Expression source, string prope internal Expression ConvertNonStandardPrimitives(Expression source) { bool isNonstandardEdmPrimitive; - Type conversionType = source.Type.IsNonstandardEdmPrimitive(out isNonstandardEdmPrimitive); + Type conversionType = Model.IsNonstandardEdmPrimitive(source.Type, out isNonstandardEdmPrimitive); if (isNonstandardEdmPrimitive) { diff --git a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs index 5e13f6499..bd0b29dcb 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Expressions/ExpressionBinderHelper.cs @@ -154,8 +154,7 @@ public static Expression CreateBinaryExpression(BinaryOperatorKind binaryOperato case ExpressionType.NotEqual: return Expression.MakeBinary(binaryExpressionType, left, right, liftToNull, method: Linq2ObjectsComparisonMethods.AreByteArraysNotEqualMethodInfo); default: - IEdmPrimitiveType binaryType = typeof(byte[]).GetEdmPrimitiveType(); - throw new ODataException(Error.Format(SRResources.BinaryOperatorNotSupported, binaryType.FullName(), binaryType.FullName(), binaryOperator)); + throw new ODataException(Error.Format(SRResources.BinaryOperatorNotSupported, "Edm.Binary", "Edm.Binary", binaryOperator)); } } else diff --git a/src/Microsoft.AspNetCore.OData/Query/ODataQueryContext.cs b/src/Microsoft.AspNetCore.OData/Query/ODataQueryContext.cs index 3f2905a84..003499123 100644 --- a/src/Microsoft.AspNetCore.OData/Query/ODataQueryContext.cs +++ b/src/Microsoft.AspNetCore.OData/Query/ODataQueryContext.cs @@ -52,7 +52,7 @@ public ODataQueryContext(IEdmModel model, Type elementClrType, ODataPath path) throw Error.ArgumentNull(nameof(elementClrType)); } - ElementType = model.GetTypeMappingCache().GetEdmType(elementClrType, model)?.Definition; + ElementType = model.GetEdmTypeReference(elementClrType)?.Definition; if (ElementType == null) { diff --git a/src/Microsoft.AspNetCore.OData/Query/Query/DefaultSkipTokenHandler.cs b/src/Microsoft.AspNetCore.OData/Query/Query/DefaultSkipTokenHandler.cs index 36f31a68b..479c9ec8a 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Query/DefaultSkipTokenHandler.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Query/DefaultSkipTokenHandler.cs @@ -427,7 +427,7 @@ internal static IEdmType GetTypeFromObject(object value, IEdmModel model) } Type clrType = value.GetType(); - return model.GetTypeMappingCache().GetEdmType(clrType, model)?.Definition; + return model.GetEdmTypeReference(clrType)?.Definition; } private static IList ParseValue(string value, char delim) diff --git a/src/Microsoft.AspNetCore.OData/Query/Wrapper/SelectExpandWrapper.cs b/src/Microsoft.AspNetCore.OData/Query/Wrapper/SelectExpandWrapper.cs index 14dc2d7f8..d74171d1a 100644 --- a/src/Microsoft.AspNetCore.OData/Query/Wrapper/SelectExpandWrapper.cs +++ b/src/Microsoft.AspNetCore.OData/Query/Wrapper/SelectExpandWrapper.cs @@ -68,7 +68,7 @@ public IEdmTypeReference GetEdmType() Type elementType = GetElementType(); - return model.GetTypeMappingCache().GetEdmType(elementType, model); + return model.GetEdmTypeReference(elementType); } /// diff --git a/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs b/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs index a851c8260..029d833f5 100644 --- a/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs +++ b/src/Microsoft.AspNetCore.OData/Results/ResultHelpers.cs @@ -171,7 +171,7 @@ private static Uri GenerateContainmentODataPathSegments(ResourceContext resource private static IEdmEntityTypeReference GetEntityType(IEdmModel model, object entity) { Type entityType = entity.GetType(); - IEdmTypeReference edmType = model.GetTypeMappingCache().GetEdmType(entityType, model); + IEdmTypeReference edmType = model.GetEdmTypeReference(entityType); if (edmType == null) { throw Error.InvalidOperation(SRResources.ResourceTypeNotInModel, entityType.FullName); diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs new file mode 100644 index 000000000..3064e0933 --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs @@ -0,0 +1,523 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml.Linq; +using Microsoft.AspNetCore.OData.Edm; +using Microsoft.AspNetCore.OData.Tests.Commons; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; +using Microsoft.Spatial; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.OData.Tests.Edm +{ + public class DefaultODataTypeMapperTests + { + private static IEdmModel EdmModel = GetEdmModel(); + private DefaultODataTypeMapper _mapper = new DefaultODataTypeMapper(); + + #region PrimitiveType + [Theory] + [InlineData(typeof(string), "Edm.String", true)] + [InlineData(typeof(bool), "Edm.Boolean", false)] + [InlineData(typeof(bool?), "Edm.Boolean", true)] + [InlineData(typeof(byte), "Edm.Byte", false)] + [InlineData(typeof(byte?), "Edm.Byte", true)] + [InlineData(typeof(decimal), "Edm.Decimal", false)] + [InlineData(typeof(decimal?), "Edm.Decimal", true)] + [InlineData(typeof(double), "Edm.Double", false)] + [InlineData(typeof(double?), "Edm.Double", true)] + [InlineData(typeof(Guid), "Edm.Guid", false)] + [InlineData(typeof(Guid?), "Edm.Guid", true)] + [InlineData(typeof(short), "Edm.Int16", false)] + [InlineData(typeof(short?), "Edm.Int16", true)] + [InlineData(typeof(int), "Edm.Int32", false)] + [InlineData(typeof(int?), "Edm.Int32", true)] + [InlineData(typeof(long), "Edm.Int64", false)] + [InlineData(typeof(long?), "Edm.Int64", true)] + [InlineData(typeof(sbyte), "Edm.SByte", false)] + [InlineData(typeof(sbyte?), "Edm.SByte", true)] + [InlineData(typeof(float), "Edm.Single", false)] + [InlineData(typeof(float?), "Edm.Single", true)] + [InlineData(typeof(DateTimeOffset), "Edm.DateTimeOffset", false)] + [InlineData(typeof(DateTimeOffset?), "Edm.DateTimeOffset", true)] + [InlineData(typeof(TimeSpan), "Edm.Duration", false)] + [InlineData(typeof(TimeSpan?), "Edm.Duration", true)] + [InlineData(typeof(Date), "Edm.Date", false)] + [InlineData(typeof(Date?), "Edm.Date", true)] + [InlineData(typeof(TimeOfDay), "Edm.TimeOfDay", false)] + [InlineData(typeof(TimeOfDay?), "Edm.TimeOfDay", true)] + [InlineData(typeof(byte[]), "Edm.Binary", true)] + [InlineData(typeof(Stream), "Edm.Stream", true)] + public void GetPrimitiveType_ForClrType_WorksAsExpected_ForStandardPrimitive(Type clrType, string name, bool nullable) + { + // Arrange & Act + IEdmPrimitiveTypeReference primitiveTypeReference = _mapper.GetPrimitiveType(clrType); + + // Assert + Assert.NotNull(primitiveTypeReference); + Assert.Equal(name, primitiveTypeReference.FullName()); + Assert.Equal(nullable, primitiveTypeReference.IsNullable); + } + + [Theory] + [InlineData(typeof(XElement), "Edm.String", true)] + [InlineData(typeof(ushort), "Edm.Int32", false)] + [InlineData(typeof(ushort?), "Edm.Int32", true)] + [InlineData(typeof(uint), "Edm.Int64", false)] + [InlineData(typeof(uint?), "Edm.Int64", true)] + [InlineData(typeof(ulong), "Edm.Int64", false)] + [InlineData(typeof(ulong?), "Edm.Int64", true)] + [InlineData(typeof(char[]), "Edm.String", true)] + [InlineData(typeof(char), "Edm.String", false)] + [InlineData(typeof(char?), "Edm.String", true)] + [InlineData(typeof(DateTime), "Edm.DateTimeOffset", false)] + [InlineData(typeof(DateTime?), "Edm.DateTimeOffset", true)] + public void GetPrimitiveType_ForClrType_WorksAsExpected_ForNonStandardPrimitive(Type clrType, string name, bool nullable) + { + // Arrange & Act + IEdmPrimitiveTypeReference primitiveTypeReference = _mapper.GetPrimitiveType(clrType); + + // Assert + Assert.NotNull(primitiveTypeReference); + Assert.Equal(name, primitiveTypeReference.FullName()); + Assert.Equal(nullable, primitiveTypeReference.IsNullable); + } + + [Theory] + [InlineData(typeof(Geography), "Edm.Geography")] + [InlineData(typeof(GeographyPoint), "Edm.GeographyPoint")] + [InlineData(typeof(GeographyLineString), "Edm.GeographyLineString")] + [InlineData(typeof(GeographyPolygon), "Edm.GeographyPolygon")] + [InlineData(typeof(GeographyCollection), "Edm.GeographyCollection")] + [InlineData(typeof(GeographyMultiLineString), "Edm.GeographyMultiLineString")] + [InlineData(typeof(GeographyMultiPoint), "Edm.GeographyMultiPoint")] + [InlineData(typeof(GeographyMultiPolygon), "Edm.GeographyMultiPolygon")] + [InlineData(typeof(Geometry), "Edm.Geometry")] + [InlineData(typeof(GeometryPoint), "Edm.GeometryPoint")] + [InlineData(typeof(GeometryLineString), "Edm.GeometryLineString")] + [InlineData(typeof(GeometryPolygon), "Edm.GeometryPolygon")] + [InlineData(typeof(GeometryCollection), "Edm.GeometryCollection")] + [InlineData(typeof(GeometryMultiLineString), "Edm.GeometryMultiLineString")] + [InlineData(typeof(GeometryMultiPoint), "Edm.GeometryMultiPoint")] + [InlineData(typeof(GeometryMultiPolygon), "Edm.GeometryMultiPolygon")] + public void GetPrimitiveType_ForClrType_WorksAsExpected_ForSpatialPrimitive(Type clrType, string name) + { + // Arrange & Act + IEdmPrimitiveTypeReference primitiveTypeReference = _mapper.GetPrimitiveType(clrType); + + // Assert + Assert.NotNull(primitiveTypeReference); + Assert.Equal(name, primitiveTypeReference.FullName()); + Assert.True(primitiveTypeReference.IsNullable); + } + + //[Theory] + //[InlineData(null, null, false)] + //[InlineData(typeof(int), typeof(int), false)] + //[InlineData(typeof(int?), typeof(int?), false)] + //[InlineData(typeof(object), typeof(object), false)] + //[InlineData(typeof(MyAddress), typeof(MyAddress), false)] + //// non-standard primitive types + //[InlineData(typeof(XElement), typeof(string), true)] + //[InlineData(typeof(ushort), typeof(int), true)] + //[InlineData(typeof(ushort?), typeof(int?), true)] + //[InlineData(typeof(uint), typeof(long), true)] + //[InlineData(typeof(uint?), typeof(long?), true)] + //[InlineData(typeof(ulong), typeof(long), true)] + //[InlineData(typeof(ulong?), typeof(long?), true)] + //[InlineData(typeof(char[]), typeof(string), true)] + //[InlineData(typeof(char), typeof(string), true)] + //[InlineData(typeof(char?), typeof(string), true)] + //[InlineData(typeof(DateTime), typeof(DateTimeOffset), true)] + //[InlineData(typeof(DateTime?), typeof(DateTimeOffset?), true)] + //public void IsNonstandardEdmPrimitiveWorksAsExpectedForNonstandardType(Type clrType, Type expectType, bool isNonstandard) + //{ + // // Arrange & Act + // Type actual = _provider.IsNonstandardEdmPrimitive(clrType, out bool isNonstandardEdmPrimtive); + + // // Assert + // Assert.Equal(expectType, actual); + // Assert.Equal(isNonstandard, isNonstandardEdmPrimtive); + //} + + [Theory] + [InlineData(EdmPrimitiveTypeKind.String, typeof(string), typeof(string))] + [InlineData(EdmPrimitiveTypeKind.Boolean, typeof(bool?), typeof(bool))] + [InlineData(EdmPrimitiveTypeKind.Byte, typeof(byte?), typeof(byte))] + [InlineData(EdmPrimitiveTypeKind.Decimal, typeof(decimal?), typeof(decimal))] + [InlineData(EdmPrimitiveTypeKind.Double, typeof(double?), typeof(double))] + [InlineData(EdmPrimitiveTypeKind.Guid, typeof(Guid?), typeof(Guid))] + [InlineData(EdmPrimitiveTypeKind.Int16, typeof(short?), typeof(short))] + [InlineData(EdmPrimitiveTypeKind.Int32, typeof(int?), typeof(int))] + [InlineData(EdmPrimitiveTypeKind.Int64, typeof(long?), typeof(long))] + [InlineData(EdmPrimitiveTypeKind.SByte, typeof(sbyte?), typeof(sbyte))] + [InlineData(EdmPrimitiveTypeKind.Single, typeof(float?), typeof(float))] + [InlineData(EdmPrimitiveTypeKind.DateTimeOffset, typeof(DateTimeOffset?), typeof(DateTimeOffset))] + [InlineData(EdmPrimitiveTypeKind.Duration, typeof(TimeSpan?), typeof(TimeSpan))] + [InlineData(EdmPrimitiveTypeKind.Date, typeof(Date?), typeof(Date))] + [InlineData(EdmPrimitiveTypeKind.TimeOfDay, typeof(TimeOfDay?), typeof(TimeOfDay))] + [InlineData(EdmPrimitiveTypeKind.Binary, typeof(byte[]), typeof(byte[]))] + [InlineData(EdmPrimitiveTypeKind.Stream, typeof(Stream), typeof(Stream))] + public void GetPrimitiveType_ForEdmType_WorksAsExpected_ForStandardPrimitive(EdmPrimitiveTypeKind kind, Type nullExpected, Type nonNullExpected) + { + // Arrange & Act & Assert + IEdmPrimitiveType primitiveType = EdmCoreModel.Instance.GetPrimitiveType(kind); + Type clrType = _mapper.GetPrimitiveType(primitiveType, true); + Assert.Equal(nullExpected, clrType); + + // Arrange & Act & Assert + clrType = _mapper.GetPrimitiveType(primitiveType, false); + Assert.Equal(nonNullExpected, clrType); + } + + [Theory] + [InlineData(EdmPrimitiveTypeKind.Geography, typeof(Geography))] + [InlineData(EdmPrimitiveTypeKind.GeographyPoint, typeof(GeographyPoint))] + [InlineData(EdmPrimitiveTypeKind.GeographyLineString, typeof(GeographyLineString))] + [InlineData(EdmPrimitiveTypeKind.GeographyPolygon, typeof(GeographyPolygon))] + [InlineData(EdmPrimitiveTypeKind.GeographyCollection, typeof(GeographyCollection))] + [InlineData(EdmPrimitiveTypeKind.GeographyMultiLineString, typeof(GeographyMultiLineString))] + [InlineData(EdmPrimitiveTypeKind.GeographyMultiPoint, typeof(GeographyMultiPoint))] + [InlineData(EdmPrimitiveTypeKind.GeographyMultiPolygon, typeof(GeographyMultiPolygon))] + [InlineData(EdmPrimitiveTypeKind.Geometry, typeof(Geometry))] + [InlineData(EdmPrimitiveTypeKind.GeometryPoint, typeof(GeometryPoint))] + [InlineData(EdmPrimitiveTypeKind.GeometryLineString, typeof(GeometryLineString))] + [InlineData(EdmPrimitiveTypeKind.GeometryPolygon, typeof(GeometryPolygon))] + [InlineData(EdmPrimitiveTypeKind.GeometryCollection, typeof(GeometryCollection))] + [InlineData(EdmPrimitiveTypeKind.GeometryMultiLineString, typeof(GeometryMultiLineString))] + [InlineData(EdmPrimitiveTypeKind.GeometryMultiPoint, typeof(GeometryMultiPoint))] + [InlineData(EdmPrimitiveTypeKind.GeometryMultiPolygon, typeof(GeometryMultiPolygon))] + public void GetPrimitiveType_ForEdmType_WorksAsExpected_ForSpatialPrimitive(EdmPrimitiveTypeKind kind, Type expected) + { + // Arrange & Act & Assert + IEdmPrimitiveType primitiveType = EdmCoreModel.Instance.GetPrimitiveType(kind); + Type clrType = _mapper.GetPrimitiveType(primitiveType, true); + Assert.Equal(expected, clrType); + + // Arrange & Act & Assert + clrType = _mapper.GetPrimitiveType(primitiveType, false); + Assert.Equal(expected, clrType); + } + + [Theory] + [InlineData(EdmPrimitiveTypeKind.None)] + [InlineData(EdmPrimitiveTypeKind.PrimitiveType)] + public void GetPrimitiveType_ForEdmType_WorksAsExpected_ForNotUsedKind(EdmPrimitiveTypeKind kind) + { + // Arrange & Act & Assert + IEdmPrimitiveType primitiveType = EdmCoreModel.Instance.GetPrimitiveType(kind); + Type clrType = _mapper.GetPrimitiveType(primitiveType, true); + Assert.Null(clrType); + + // Arrange & Act & Assert + clrType = _mapper.GetPrimitiveType(primitiveType, false); + Assert.Null(clrType); + } + #endregion + + #region GetClrType + [Fact] + public void GetClrType_ThrowsArgumentNull_ForInputParameters() + { + // Arrange & Act & Assert + Mock edmType = new Mock(); + edmType.Setup(x => x.TypeKind).Returns(EdmTypeKind.Entity); + ExceptionAssert.ThrowsArgumentNull(() => _mapper.GetClrType(null, edmType.Object, true, null), "edmModel"); + + IEdmModel model = new Mock().Object; + IAssemblyResolver resolver = new Mock().Object; + ExceptionAssert.ThrowsArgumentNull(() => _mapper.GetClrType(model, null, true, resolver), "edmType"); + } + + [Theory] + [InlineData(EdmPrimitiveTypeKind.String, typeof(string))] + [InlineData(EdmPrimitiveTypeKind.Boolean, typeof(bool))] + [InlineData(EdmPrimitiveTypeKind.Byte, typeof(byte))] + [InlineData(EdmPrimitiveTypeKind.Decimal, typeof(decimal))] + [InlineData(EdmPrimitiveTypeKind.Double, typeof(double))] + [InlineData(EdmPrimitiveTypeKind.Guid, typeof(Guid))] + [InlineData(EdmPrimitiveTypeKind.Int16, typeof(short))] + [InlineData(EdmPrimitiveTypeKind.Int32, typeof(int))] + [InlineData(EdmPrimitiveTypeKind.Int64, typeof(long))] + [InlineData(EdmPrimitiveTypeKind.SByte, typeof(sbyte))] + [InlineData(EdmPrimitiveTypeKind.Single, typeof(float))] + [InlineData(EdmPrimitiveTypeKind.Binary, typeof(byte[]))] + [InlineData(EdmPrimitiveTypeKind.Stream, typeof(Stream))] + [InlineData(EdmPrimitiveTypeKind.DateTimeOffset, typeof(DateTimeOffset))] + [InlineData(EdmPrimitiveTypeKind.Duration, typeof(TimeSpan))] + [InlineData(EdmPrimitiveTypeKind.Date, typeof(Date))] + [InlineData(EdmPrimitiveTypeKind.TimeOfDay, typeof(TimeOfDay))] + public void GetClrType_WorksAsExpected_ForStandardPrimitive(EdmPrimitiveTypeKind kind, Type expected) + { + // #1 Arrange & Act & Assert for nullable equals to false + IEdmPrimitiveType primitiveType = EdmCoreModel.Instance.GetPrimitiveType(kind); + Type clrType = _mapper.GetClrType(EdmModel, primitiveType, false, assembliesResolver: null); + Assert.Equal(expected, clrType); + + // #2 Arrange & Act & Assert for nullable equals to true + clrType = _mapper.GetClrType(EdmModel, primitiveType, true, assembliesResolver: null); + if (expected.IsValueType) + { + Type generic = typeof(Nullable<>); + expected = generic.MakeGenericType(expected); + Assert.Same(expected, clrType); + } + else + { + Assert.Same(expected, clrType); + } + } + + [Theory] + [InlineData(EdmPrimitiveTypeKind.Geography, typeof(Geography))] + [InlineData(EdmPrimitiveTypeKind.GeographyPoint, typeof(GeographyPoint))] + [InlineData(EdmPrimitiveTypeKind.GeographyLineString, typeof(GeographyLineString))] + [InlineData(EdmPrimitiveTypeKind.GeographyPolygon, typeof(GeographyPolygon))] + [InlineData(EdmPrimitiveTypeKind.GeographyCollection, typeof(GeographyCollection))] + [InlineData(EdmPrimitiveTypeKind.GeographyMultiLineString, typeof(GeographyMultiLineString))] + [InlineData(EdmPrimitiveTypeKind.GeographyMultiPoint, typeof(GeographyMultiPoint))] + [InlineData(EdmPrimitiveTypeKind.GeographyMultiPolygon, typeof(GeographyMultiPolygon))] + [InlineData(EdmPrimitiveTypeKind.Geometry, typeof(Geometry))] + [InlineData(EdmPrimitiveTypeKind.GeometryPoint, typeof(GeometryPoint))] + [InlineData(EdmPrimitiveTypeKind.GeometryLineString, typeof(GeometryLineString))] + [InlineData(EdmPrimitiveTypeKind.GeometryPolygon, typeof(GeometryPolygon))] + [InlineData(EdmPrimitiveTypeKind.GeometryCollection, typeof(GeometryCollection))] + [InlineData(EdmPrimitiveTypeKind.GeometryMultiLineString, typeof(GeometryMultiLineString))] + [InlineData(EdmPrimitiveTypeKind.GeometryMultiPoint, typeof(GeometryMultiPoint))] + [InlineData(EdmPrimitiveTypeKind.GeometryMultiPolygon, typeof(GeometryMultiPolygon))] + public void GetClrType_WorksAsExpected_ForSpatialPrimitive(EdmPrimitiveTypeKind kind, Type type) + { + // Arrange + IEdmPrimitiveType primitiveType = EdmCoreModel.Instance.GetPrimitiveType(kind); + + // Act + Type clrType1 = _mapper.GetClrType(EdmModel, primitiveType, false, assembliesResolver: null); + Type clrType2 = _mapper.GetClrType(EdmModel, primitiveType, true, assembliesResolver: null); + + // Assert + Assert.Same(clrType1, clrType2); + Assert.Same(type, clrType1); + } + + [Theory] + [InlineData("NS.Address", typeof(MyAddress))] // use ClrTypeAnnotation + [InlineData("NS.CnAddress", typeof(CnMyAddress))] + [InlineData("Microsoft.AspNetCore.OData.Tests.Edm.MyCustomer", typeof(MyCustomer))] // use the full name match + public void GetClrType_WorksAsExpected_ForSchemaStrucutralType(string typeName, Type expected) + { + // Arrange + IEdmType edmType = EdmModel.FindType(typeName); + Assert.NotNull(edmType); // Guard + + // #1. Act & Assert + Type clrType = _mapper.GetClrType(EdmModel, edmType, true, new AssemblyResolver()); + Assert.Same(expected, clrType); + + // #2. Act & Assert + clrType = _mapper.GetClrType(EdmModel, edmType, false, new AssemblyResolver()); + Assert.Same(expected, clrType); + } + + [Fact] + public void GetClrType_WorksAsExpected_ForSchemaEnumType() + { + // Arrange + IEdmType edmType = EdmModel.FindType("NS.Color"); + Assert.NotNull(edmType); // Guard + + // #1. Act & Assert + Type clrType = _mapper.GetClrType(EdmModel, edmType, true, null); + Assert.Same(typeof(MyColor?), clrType); + + // #2. Act & Assert + clrType = _mapper.GetClrType(EdmModel, edmType, false, null); + Assert.Same(typeof(MyColor), clrType); + } + + #endregion + + #region GetEdmType + [Fact] + public void GetEdmType_ThrowsArgumentNull_ModelAndClrType() + { + // Arrange & Act + IEdmModel model = null; + ExceptionAssert.ThrowsArgumentNull(() => _mapper.GetEdmTypeReference(model, typeof(TypeNotInModel)), "edmModel"); + + model = new Mock().Object; + ExceptionAssert.ThrowsArgumentNull(() => _mapper.GetEdmTypeReference(model, null), "clrType"); + } + + [Fact] + public void GetEdmTypeReference_ReturnsNull_ForUnknownType() + { + // Arrange & Act & Assert + Assert.Null(_mapper.GetEdmTypeReference(EdmModel, typeof(TypeNotInModel))); + } + + [Theory] + [InlineData(typeof(IEnumerable), "NS.BaseType")] + [InlineData(typeof(IEnumerable), "NS.Derived1Type")] + [InlineData(typeof(Derived2Type[]), "NS.Derived2Type")] + public void GetEdmTypeReference_ReturnsCollection_ForIEnumerableOfT(Type clrType, string typeName) + { + // Arrange & Act + IEdmType edmType = _mapper.GetEdmType(EdmModel, clrType); + + // Assert + Assert.Equal(EdmTypeKind.Collection, edmType.TypeKind); + Assert.Equal(typeName, (edmType as IEdmCollectionType).ElementType.FullName()); + } + + [Theory] + [InlineData(typeof(string), "Edm.String")] + [InlineData(typeof(int?), "Edm.Int32")] + [InlineData(typeof(MyAddress), "NS.Address")] + [InlineData(typeof(CnMyAddress), "NS.CnAddress")] + [InlineData(typeof(MyCustomer), "Microsoft.AspNetCore.OData.Tests.Edm.MyCustomer")] + [InlineData(typeof(BaseType), "NS.BaseType")] + [InlineData(typeof(Derived1Type), "NS.Derived1Type")] + [InlineData(typeof(Derived2Type), "NS.Derived2Type")] + [InlineData(typeof(SubDerivedType), "NS.SubDerivedType")] + public void GetEdmTypeReference_WorksAsExpected_ForEdmType(Type clrType, string typeName) + { + // Arrange + IEdmType expectedEdmType = EdmModel.FindType(typeName); + Assert.NotNull(expectedEdmType); // Guard + + // Arrange & Act + IEdmTypeReference edmTypeRef = _mapper.GetEdmTypeReference(EdmModel, clrType); + IEdmType edmType = _mapper.GetEdmType(EdmModel, clrType); + + // Assert + Assert.NotNull(edmTypeRef); + Assert.Same(expectedEdmType, edmTypeRef.Definition); + Assert.Same(expectedEdmType, edmType); + Assert.True(edmTypeRef.IsNullable); + } + + [Fact] + public void GetEdmTypeReference_WorksAsExpected_ForSchemaEnumType() + { + // Arrange + IEdmType expectedType = EdmModel.FindType("NS.Color"); + Assert.NotNull(expectedType); // Guard + + // #1. Act & Assert + IEdmTypeReference colorType = _mapper.GetEdmTypeReference(EdmModel, typeof(MyColor)); + Assert.Same(expectedType, colorType.Definition); + Assert.False(colorType.IsNullable); + + // #2. Act & Assert + colorType = _mapper.GetEdmTypeReference(EdmModel, typeof(MyColor?)); + Assert.Same(expectedType, colorType.Definition); + Assert.True(colorType.IsNullable); + } + #endregion + + [Theory] + [InlineData(typeof(MyCustomer), "MyCustomer")] + [InlineData(typeof(int), "Int32")] + [InlineData(typeof(IEnumerable), "IEnumerable_1OfInt32")] + [InlineData(typeof(IEnumerable>), "IEnumerable_1OfFunc_2OfInt32_String")] + [InlineData(typeof(List>), "List_1OfFunc_2OfInt32_String")] + public void EdmFullName(Type clrType, string expectedName) + { + // Arrange & Act & Assert + Assert.Equal(expectedName, clrType.EdmName()); + } + + private static IEdmModel GetEdmModel() + { + EdmModel model = new EdmModel(); + + // ComplexType: Address + EdmComplexType address = new EdmComplexType("NS", "Address"); + address.AddStructuralProperty("City", EdmPrimitiveTypeKind.String); + model.AddElement(address); + model.SetAnnotationValue(address, new ClrTypeAnnotation(typeof(MyAddress))); + + // ComplexType: CnAddress + var cnAddress = new EdmComplexType("NS", "CnAddress", address); + cnAddress.AddStructuralProperty("Zipcode", EdmPrimitiveTypeKind.String); + model.AddElement(cnAddress); + model.SetAnnotationValue(cnAddress, new ClrTypeAnnotation(typeof(CnMyAddress))); + + // EnumType: Color + var color = new EdmEnumType("NS", "Color"); + model.AddElement(color); + model.SetAnnotationValue(color, new ClrTypeAnnotation(typeof(MyColor))); + + // EntityType: MyCustomer + var customer = new EdmEntityType("Microsoft.AspNetCore.OData.Tests.Edm", "MyCustomer"); + model.AddElement(customer); + + // Inheritance EntityType + var baseEntity = new EdmEntityType("NS", "BaseType"); + var derived1Entity = new EdmEntityType("NS", "Derived1Type", baseEntity); + var derived2Entity = new EdmEntityType("NS", "Derived2Type", baseEntity); + var subDerivedEntity = new EdmEntityType("NS", "SubDerivedType", derived1Entity); + model.AddElements(new[] { baseEntity, derived1Entity, derived2Entity, subDerivedEntity }); + model.SetAnnotationValue(baseEntity, new ClrTypeAnnotation(typeof(BaseType))); + model.SetAnnotationValue(derived1Entity, new ClrTypeAnnotation(typeof(Derived1Type))); + model.SetAnnotationValue(derived2Entity, new ClrTypeAnnotation(typeof(Derived2Type))); + model.SetAnnotationValue(subDerivedEntity, new ClrTypeAnnotation(typeof(SubDerivedType))); + + return model; + } + + public class MyAddress + { + public string City { get; set; } + } + + public class CnMyAddress : MyAddress + { + public string Zipcode { get; set; } + } + + public enum MyColor + { + Red + } + + public class BaseType + { } + + public class Derived1Type : BaseType + { } + + public class Derived2Type : BaseType + { } + + public class SubDerivedType : Derived1Type + { } + + public class TypeNotInModel + { } + + public class AssemblyResolver : IAssemblyResolver + { + public IEnumerable Assemblies + { + get + { + yield return typeof(AssemblyResolver).Assembly; + } + } + } + } + + public class MyCustomer + { } +} diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmClrTypeMapExtensionsTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmClrTypeMapExtensionsTests.cs index da2d394d7..aedca1046 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmClrTypeMapExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmClrTypeMapExtensionsTests.cs @@ -7,14 +7,11 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Reflection; using System.Xml.Linq; +using Microsoft.AspNetCore.OData.Abstracts; using Microsoft.AspNetCore.OData.Edm; -using Microsoft.AspNetCore.OData.Tests.Commons; using Microsoft.OData.Edm; using Microsoft.OData.ModelBuilder; -using Microsoft.Spatial; using Moq; using Xunit; @@ -22,108 +19,44 @@ namespace Microsoft.AspNetCore.OData.Tests.Edm { public class EdmClrTypeMapExtensionsTests { - private static IEdmModel EdmModel = GetEdmModel(); - - #region PrimitiveType - [Theory] - [InlineData(typeof(string), "Edm.String", true)] - [InlineData(typeof(bool), "Edm.Boolean", false)] - [InlineData(typeof(bool?), "Edm.Boolean", true)] - [InlineData(typeof(byte), "Edm.Byte", false)] - [InlineData(typeof(byte?), "Edm.Byte", true)] - [InlineData(typeof(decimal), "Edm.Decimal", false)] - [InlineData(typeof(decimal?), "Edm.Decimal", true)] - [InlineData(typeof(double), "Edm.Double", false)] - [InlineData(typeof(double?), "Edm.Double", true)] - [InlineData(typeof(Guid), "Edm.Guid", false)] - [InlineData(typeof(Guid?), "Edm.Guid", true)] - [InlineData(typeof(short), "Edm.Int16", false)] - [InlineData(typeof(short?), "Edm.Int16", true)] - [InlineData(typeof(int), "Edm.Int32", false)] - [InlineData(typeof(int?), "Edm.Int32", true)] - [InlineData(typeof(long), "Edm.Int64", false)] - [InlineData(typeof(long?), "Edm.Int64", true)] - [InlineData(typeof(sbyte), "Edm.SByte", false)] - [InlineData(typeof(sbyte?), "Edm.SByte", true)] - [InlineData(typeof(float), "Edm.Single", false)] - [InlineData(typeof(float?), "Edm.Single", true)] - [InlineData(typeof(DateTimeOffset), "Edm.DateTimeOffset", false)] - [InlineData(typeof(DateTimeOffset?), "Edm.DateTimeOffset", true)] - [InlineData(typeof(TimeSpan), "Edm.Duration", false)] - [InlineData(typeof(TimeSpan?), "Edm.Duration", true)] - [InlineData(typeof(Date), "Edm.Date", false)] - [InlineData(typeof(Date?), "Edm.Date", true)] - [InlineData(typeof(TimeOfDay), "Edm.TimeOfDay", false)] - [InlineData(typeof(TimeOfDay?), "Edm.TimeOfDay", true)] - [InlineData(typeof(byte[]), "Edm.Binary", true)] - [InlineData(typeof(Stream), "Edm.Stream", true)] - public void GetEdmPrimitiveTypeReferenceWorksAsExpectedForStandardPrimitive(Type clrType, string name, bool nullable) + [Fact] + public void GetEdmPrimitiveTypeReference_Calls_GetPrimitiveTypeOnMapper() { - // Arrange & Act - IEdmPrimitiveTypeReference primitiveTypeReference = clrType.GetEdmPrimitiveTypeReference(); - IEdmPrimitiveType primitiveType = clrType.GetEdmPrimitiveType(); + // Arrange + Type type = typeof(int); + Mock mapper = new Mock(); + mapper.Setup(x => x.GetPrimitiveType(type)).Verifiable(); - // Assert - Assert.NotNull(primitiveTypeReference); - Assert.Same(primitiveTypeReference.Definition, primitiveType); - Assert.Equal(name, primitiveTypeReference.FullName()); - Assert.Equal(nullable, primitiveTypeReference.IsNullable); - } + EdmModel model = new EdmModel(); + model.SetTypeMapper(mapper.Object); - [Theory] - [InlineData(typeof(XElement), "Edm.String", true)] - [InlineData(typeof(ushort), "Edm.Int32", false)] - [InlineData(typeof(ushort?), "Edm.Int32", true)] - [InlineData(typeof(uint), "Edm.Int64", false)] - [InlineData(typeof(uint?), "Edm.Int64", true)] - [InlineData(typeof(ulong), "Edm.Int64", false)] - [InlineData(typeof(ulong?), "Edm.Int64", true)] - [InlineData(typeof(char[]), "Edm.String", true)] - [InlineData(typeof(char), "Edm.String", false)] - [InlineData(typeof(char?), "Edm.String", true)] - [InlineData(typeof(DateTime), "Edm.DateTimeOffset", false)] - [InlineData(typeof(DateTime?), "Edm.DateTimeOffset", true)] - public void GetEdmPrimitiveTypeReferenceWorksAsExpectedForNonStandardPrimitive(Type clrType, string name, bool nullable) - { - // Arrange & Act - IEdmPrimitiveTypeReference primitiveTypeReference = clrType.GetEdmPrimitiveTypeReference(); - IEdmPrimitiveType primitiveType = clrType.GetEdmPrimitiveType(); + // Act + model.GetEdmPrimitiveTypeReference(type); // Assert - Assert.NotNull(primitiveTypeReference); - Assert.Same(primitiveTypeReference.Definition, primitiveType); - Assert.Equal(name, primitiveTypeReference.FullName()); - Assert.Equal(nullable, primitiveTypeReference.IsNullable); + mapper.Verify(); } - [Theory] - [InlineData(typeof(Geography), "Edm.Geography")] - [InlineData(typeof(GeographyPoint), "Edm.GeographyPoint")] - [InlineData(typeof(GeographyLineString), "Edm.GeographyLineString")] - [InlineData(typeof(GeographyPolygon), "Edm.GeographyPolygon")] - [InlineData(typeof(GeographyCollection), "Edm.GeographyCollection")] - [InlineData(typeof(GeographyMultiLineString), "Edm.GeographyMultiLineString")] - [InlineData(typeof(GeographyMultiPoint), "Edm.GeographyMultiPoint")] - [InlineData(typeof(GeographyMultiPolygon), "Edm.GeographyMultiPolygon")] - [InlineData(typeof(Geometry), "Edm.Geometry")] - [InlineData(typeof(GeometryPoint), "Edm.GeometryPoint")] - [InlineData(typeof(GeometryLineString), "Edm.GeometryLineString")] - [InlineData(typeof(GeometryPolygon), "Edm.GeometryPolygon")] - [InlineData(typeof(GeometryCollection), "Edm.GeometryCollection")] - [InlineData(typeof(GeometryMultiLineString), "Edm.GeometryMultiLineString")] - [InlineData(typeof(GeometryMultiPoint), "Edm.GeometryMultiPoint")] - [InlineData(typeof(GeometryMultiPolygon), "Edm.GeometryMultiPolygon")] - public void GetEdmTypeWorksAsExpectedForSpatialPrimitive(Type clrType, string name) + [Fact] + public void GetClrPrimitiveType_Calls_GetPrimitiveTypeOnMapper() { - // Arrange & Act - IEdmPrimitiveTypeReference primitiveTypeReference = clrType.GetEdmPrimitiveTypeReference(); - IEdmPrimitiveType primitiveType = clrType.GetEdmPrimitiveType(); + // Arrange + Mock primitiveType = new Mock(); + Mock edmType = new Mock(); + edmType.Setup(x => x.Definition).Returns(primitiveType.Object); + edmType.Setup(x => x.IsNullable).Returns(true); + + Mock mapper = new Mock(); + mapper.Setup(x => x.GetPrimitiveType(edmType.Object.PrimitiveDefinition(), edmType.Object.IsNullable)).Verifiable(); + + EdmModel model = new EdmModel(); + model.SetTypeMapper(mapper.Object); + + // Act + model.GetClrPrimitiveType(edmType.Object); // Assert - Assert.NotNull(primitiveTypeReference); - Assert.Same(primitiveTypeReference.Definition, primitiveType); - Assert.Equal(name, primitiveTypeReference.FullName()); - Assert.True(primitiveTypeReference.IsNullable); + mapper.Verify(); } [Theory] @@ -131,7 +64,6 @@ public void GetEdmTypeWorksAsExpectedForSpatialPrimitive(Type clrType, string na [InlineData(typeof(int), typeof(int), false)] [InlineData(typeof(int?), typeof(int?), false)] [InlineData(typeof(object), typeof(object), false)] - [InlineData(typeof(MyAddress), typeof(MyAddress), false)] // non-standard primitive types [InlineData(typeof(XElement), typeof(string), true)] [InlineData(typeof(ushort), typeof(int), true)] @@ -145,223 +77,138 @@ public void GetEdmTypeWorksAsExpectedForSpatialPrimitive(Type clrType, string na [InlineData(typeof(char?), typeof(string), true)] [InlineData(typeof(DateTime), typeof(DateTimeOffset), true)] [InlineData(typeof(DateTime?), typeof(DateTimeOffset?), true)] - public void IsNonstandardEdmPrimitiveWorksAsExpectedForNonstandardType(Type clrType, Type expectType, bool isNonstandard) + public void IsNonstandardEdmPrimitive_WorksAsExpected_ForNonstandardType(Type clrType, Type expectType, bool isNonstandard) { - // Arrange & Act - Type actual = clrType.IsNonstandardEdmPrimitive(out bool isNonstandardEdmPrimtive); + // Arrange + EdmModel model = new EdmModel(); + model.SetTypeMapper(DefaultODataTypeMapper.Default); + + // Act + Type actual = model.IsNonstandardEdmPrimitive(clrType, out bool isNonstandardEdmPrimtive); // Assert Assert.Equal(expectType, actual); Assert.Equal(isNonstandard, isNonstandardEdmPrimtive); } - #endregion - - #region GetClrType [Fact] - public void GetClrType_ThrowsArgumentNull_EdmType() + public void GetEdmTypeReference_Calls_GetEdmTypeReferenceOnMapper() { - // Arrange & Act - IAssemblyResolver resolver = new Mock().Object; - IEdmModel model = new Mock().Object; - ExceptionAssert.ThrowsArgumentNull(() => model.GetClrType((IEdmTypeReference)null, resolver), "edmTypeReference"); + // Arrange + Type type = typeof(int); + EdmModel model = new EdmModel(); - ExceptionAssert.ThrowsArgumentNull(() => model.GetClrType((IEdmType)null, resolver), "edmType"); - } + Mock mapper = new Mock(); + mapper.Setup(x => x.GetEdmTypeReference(model, type)).Verifiable(); + model.SetTypeMapper(mapper.Object); - [Theory] - [InlineData(EdmPrimitiveTypeKind.String, typeof(string))] - [InlineData(EdmPrimitiveTypeKind.Boolean, typeof(bool))] - [InlineData(EdmPrimitiveTypeKind.Byte, typeof(byte))] - [InlineData(EdmPrimitiveTypeKind.Decimal, typeof(decimal))] - [InlineData(EdmPrimitiveTypeKind.Double, typeof(double))] - [InlineData(EdmPrimitiveTypeKind.Guid, typeof(Guid))] - [InlineData(EdmPrimitiveTypeKind.Int16, typeof(short))] - [InlineData(EdmPrimitiveTypeKind.Int32, typeof(int))] - [InlineData(EdmPrimitiveTypeKind.Int64, typeof(long))] - [InlineData(EdmPrimitiveTypeKind.SByte, typeof(sbyte))] - [InlineData(EdmPrimitiveTypeKind.Single, typeof(float))] - [InlineData(EdmPrimitiveTypeKind.Binary, typeof(byte[]))] - [InlineData(EdmPrimitiveTypeKind.Stream, typeof(Stream))] - [InlineData(EdmPrimitiveTypeKind.DateTimeOffset, typeof(DateTimeOffset))] - [InlineData(EdmPrimitiveTypeKind.Duration, typeof(TimeSpan))] - [InlineData(EdmPrimitiveTypeKind.Date, typeof(Date))] - [InlineData(EdmPrimitiveTypeKind.TimeOfDay, typeof(TimeOfDay))] - public void GetClrTypeWorksAsExpectedForStandardPrimitive(EdmPrimitiveTypeKind kind, Type expected) - { - // #1 Arrange & Act & Assert for nullable equals to false - IEdmPrimitiveTypeReference primitiveType = EdmCoreModel.Instance.GetPrimitive(kind, false); - Type clrType = EdmModel.GetClrType(primitiveType); - Assert.Equal(expected, clrType); - - // #2 Arrange & Act & Assert for nullable equals to true - primitiveType = EdmCoreModel.Instance.GetPrimitive(kind, true); - clrType = EdmModel.GetClrType(primitiveType); - if (expected.IsValueType) - { - Type generic = typeof(Nullable<>); - expected = generic.MakeGenericType(expected); - Assert.Same(expected, clrType); - } - else - { - Assert.Same(expected, clrType); - } + // Act + model.GetEdmTypeReference(type); + + // Assert + mapper.Verify(); } - [Theory] - [InlineData(EdmPrimitiveTypeKind.Geography, typeof(Geography))] - [InlineData(EdmPrimitiveTypeKind.GeographyPoint, typeof(GeographyPoint))] - [InlineData(EdmPrimitiveTypeKind.GeographyLineString, typeof(GeographyLineString))] - [InlineData(EdmPrimitiveTypeKind.GeographyPolygon, typeof(GeographyPolygon))] - [InlineData(EdmPrimitiveTypeKind.GeographyCollection, typeof(GeographyCollection))] - [InlineData(EdmPrimitiveTypeKind.GeographyMultiLineString, typeof(GeographyMultiLineString))] - [InlineData(EdmPrimitiveTypeKind.GeographyMultiPoint, typeof(GeographyMultiPoint))] - [InlineData(EdmPrimitiveTypeKind.GeographyMultiPolygon, typeof(GeographyMultiPolygon))] - [InlineData(EdmPrimitiveTypeKind.Geometry, typeof(Geometry))] - [InlineData(EdmPrimitiveTypeKind.GeometryPoint, typeof(GeometryPoint))] - [InlineData(EdmPrimitiveTypeKind.GeometryLineString, typeof(GeometryLineString))] - [InlineData(EdmPrimitiveTypeKind.GeometryPolygon, typeof(GeometryPolygon))] - [InlineData(EdmPrimitiveTypeKind.GeometryCollection, typeof(GeometryCollection))] - [InlineData(EdmPrimitiveTypeKind.GeometryMultiLineString, typeof(GeometryMultiLineString))] - [InlineData(EdmPrimitiveTypeKind.GeometryMultiPoint, typeof(GeometryMultiPoint))] - [InlineData(EdmPrimitiveTypeKind.GeometryMultiPolygon, typeof(GeometryMultiPolygon))] - public void GetClrTypeWorksAsExpectedForSpatialPrimitive(EdmPrimitiveTypeKind kind, Type type) + [Fact] + public void GetEdmType_Calls_GetEdmTypeReferenceOnMapper() { // Arrange - IEdmPrimitiveTypeReference primitiveType1 = EdmCoreModel.Instance.GetPrimitive(kind, true); - IEdmPrimitiveTypeReference primitiveType2 = EdmCoreModel.Instance.GetPrimitive(kind, false); + Type type = typeof(int); + EdmModel model = new EdmModel(); + + Mock mapper = new Mock(); + mapper.Setup(x => x.GetEdmTypeReference(model, type)).Verifiable(); + model.SetTypeMapper(mapper.Object); // Act - Type clrType1 = EdmModel.GetClrType(primitiveType1); - Type clrType2 = EdmModel.GetClrType(primitiveType2); + model.GetEdmType(type); // Assert - Assert.Same(clrType1, clrType2); - Assert.Same(type, clrType1); - } - - [Theory] - [InlineData("NS.Address", typeof(MyAddress))] // use ClrTypeAnnotation - [InlineData("NS.CnAddress", typeof(CnMyAddress))] - [InlineData("Microsoft.AspNetCore.OData.Tests.Edm.MyCustomer", typeof(MyCustomer))] // use the full name match - public void GetClrTypeWorksAsExpectedForSchemaStrucutralType(string typeName, Type expected) - { - // Arrange - IEdmType edmType = EdmModel.FindType(typeName); - Assert.NotNull(edmType); // Guard - - // #1. Act & Assert - IEdmTypeReference edmTypeReference = edmType.ToEdmTypeReference(true); - Type clrType = EdmModel.GetClrType(edmTypeReference, new AssemblyResolver()); - Assert.Same(expected, clrType); - - // #2. Act & Assert - edmTypeReference = edmType.ToEdmTypeReference(false); - clrType = EdmModel.GetClrType(edmTypeReference); - Assert.Same(expected, clrType); + mapper.Verify(); } [Fact] - public void GetClrTypeWorksAsExpectedForSchemaEnumType() + public void GetClrType_Calls_GetClrTypeOnMapper() { // Arrange - IEdmType edmType = EdmModel.FindType("NS.Color"); - Assert.NotNull(edmType); // Guard - - // #1. Act & Assert - IEdmTypeReference edmTypeReference = edmType.ToEdmTypeReference(true); - Type clrType = EdmModel.GetClrType(edmTypeReference); - Assert.Same(typeof(MyColor?), clrType); - - // #2. Act & Assert - edmTypeReference = edmType.ToEdmTypeReference(false); - clrType = EdmModel.GetClrType(edmTypeReference); - Assert.Same(typeof(MyColor), clrType); - } + Mock edmType = new Mock(); + Mock edmTypeRef = new Mock(); + edmTypeRef.Setup(x => x.Definition).Returns(edmType.Object); + edmTypeRef.Setup(x => x.IsNullable).Returns(true); - #endregion + EdmModel model = new EdmModel(); - #region GetEdmType + Mock mapper = new Mock(); + mapper.Setup(x => x.GetClrType(model, edmType.Object, true, AssemblyResolverHelper.Default)).Verifiable(); + model.SetTypeMapper(mapper.Object); - [Fact] - public void GetEdmType_ThrowsArgumentNull_ModelAndClrType() - { - // Arrange & Act - IEdmModel model = null; - ExceptionAssert.ThrowsArgumentNull(() => model.GetEdmType(typeof(int)), "edmModel"); + // Act + model.GetClrType(edmTypeRef.Object); - model = new Mock().Object; - ExceptionAssert.ThrowsArgumentNull(() => model.GetEdmType(null), "clrType"); + // Assert + mapper.Verify(); } [Fact] - public void GetEdmTypeReferenceReturnsNullForUnknownType() + public void GetClrTypeWithResolver_Calls_GetClrTypeOnMapper() { - // Arrange & Act & Assert - Assert.Null(EdmModel.GetEdmTypeReference(typeof(TypeNotInModel))); - Assert.Null(EdmModel.GetEdmType(typeof(TypeNotInModel))); - } + // Arrange + Mock resolver = new Mock(); + Mock edmType = new Mock(); + Mock edmTypeRef = new Mock(); + edmTypeRef.Setup(x => x.Definition).Returns(edmType.Object); + edmTypeRef.Setup(x => x.IsNullable).Returns(true); - [Theory] - [InlineData(typeof(IEnumerable), "NS.BaseType")] - [InlineData(typeof(IEnumerable), "NS.Derived1Type")] - [InlineData(typeof(Derived2Type[]), "NS.Derived2Type")] - public void GetEdmTypeReferenceReturnsCollectionForIEnumerableOfT(Type clrType, string typeName) - { - // Arrange & Act - IEdmType edmType = EdmModel.GetEdmType(clrType); + EdmModel model = new EdmModel(); + + Mock mapper = new Mock(); + mapper.Setup(x => x.GetClrType(model, edmType.Object, true, resolver.Object)).Verifiable(); + model.SetTypeMapper(mapper.Object); + + // Act + model.GetClrType(edmTypeRef.Object, resolver.Object); // Assert - Assert.Equal(EdmTypeKind.Collection, edmType.TypeKind); - Assert.Equal(typeName, (edmType as IEdmCollectionType).ElementType.FullName()); + mapper.Verify(); } - [Theory] - [InlineData(typeof(string), "Edm.String")] - [InlineData(typeof(int?), "Edm.Int32")] - [InlineData(typeof(MyAddress), "NS.Address")] - [InlineData(typeof(CnMyAddress), "NS.CnAddress")] - [InlineData(typeof(MyCustomer), "Microsoft.AspNetCore.OData.Tests.Edm.MyCustomer")] - [InlineData(typeof(BaseType), "NS.BaseType")] - [InlineData(typeof(Derived1Type), "NS.Derived1Type")] - [InlineData(typeof(Derived2Type), "NS.Derived2Type")] - [InlineData(typeof(SubDerivedType), "NS.SubDerivedType")] - public void GetEdmTypeReferenceWorksAsExpectedForEdmType(Type clrType, string typeName) + [Fact] + public void GetClrTypeUsingEdmType_Calls_GetClrTypeOnMapper() { // Arrange - IEdmType expectedEdmType = EdmModel.FindType(typeName); - Assert.NotNull(expectedEdmType); // Guard + Mock edmType = new Mock(); + + EdmModel model = new EdmModel(); + Mock mapper = new Mock(); + mapper.Setup(x => x.GetClrType(model, edmType.Object, true, AssemblyResolverHelper.Default)).Verifiable(); + model.SetTypeMapper(mapper.Object); - // Arrange & Act - IEdmTypeReference edmTypeRef = EdmModel.GetEdmTypeReference(clrType); - IEdmType edmType = EdmModel.GetEdmType(clrType); + // Act + model.GetClrType(edmType.Object); // Assert - Assert.NotNull(edmTypeRef); - Assert.Same(expectedEdmType, edmTypeRef.Definition); - Assert.Same(expectedEdmType, edmType); - Assert.True(edmTypeRef.IsNullable); + mapper.Verify(); } [Fact] - public void GetEdmTypeWorksAsExpectedForSchemaEnumType() + public void GetClrTypeUsingEdmTypeWithResolver_Calls_GetClrTypeOnMapper() { // Arrange - IEdmType expectedType = EdmModel.FindType("NS.Color"); - Assert.NotNull(expectedType); // Guard - - // #1. Act & Assert - IEdmTypeReference colorType = EdmModel.GetEdmTypeReference(typeof(MyColor)); - Assert.Same(expectedType, colorType.Definition); - Assert.False(colorType.IsNullable); - - // #2. Act & Assert - colorType = EdmModel.GetEdmTypeReference(typeof(MyColor?)); - Assert.Same(expectedType, colorType.Definition); - Assert.True(colorType.IsNullable); + Mock resolver = new Mock(); + Mock edmType = new Mock(); + + EdmModel model = new EdmModel(); + + Mock mapper = new Mock(); + mapper.Setup(x => x.GetClrType(model, edmType.Object, true, resolver.Object)).Verifiable(); + model.SetTypeMapper(mapper.Object); + + // Act + model.GetClrType(edmType.Object, resolver.Object); + + // Assert + mapper.Verify(); } - #endregion [Theory] [InlineData(typeof(MyCustomer), "MyCustomer")] @@ -374,82 +221,5 @@ public void EdmFullName(Type clrType, string expectedName) // Arrange & Act & Assert Assert.Equal(expectedName, clrType.EdmName()); } - - private static IEdmModel GetEdmModel() - { - EdmModel model = new EdmModel(); - EdmComplexType address = new EdmComplexType("NS", "Address"); - address.AddStructuralProperty("City", EdmPrimitiveTypeKind.String); - model.AddElement(address); - model.SetAnnotationValue(address, new ClrTypeAnnotation(typeof(MyAddress))); - var cnAddress = new EdmComplexType("NS", "CnAddress", address); - cnAddress.AddStructuralProperty("Zipcode", EdmPrimitiveTypeKind.String); - model.AddElement(cnAddress); - model.SetAnnotationValue(cnAddress, new ClrTypeAnnotation(typeof(CnMyAddress))); - - var color = new EdmEnumType("NS", "Color"); - model.AddElement(color); - model.SetAnnotationValue(color, new ClrTypeAnnotation(typeof(MyColor))); - - var customer = new EdmEntityType("Microsoft.AspNetCore.OData.Tests.Edm", "MyCustomer"); - model.AddElement(customer); - - // Inheritance - var baseEntity = new EdmEntityType("NS", "BaseType"); - var derived1Entity = new EdmEntityType("NS", "Derived1Type", baseEntity); - var derived2Entity = new EdmEntityType("NS", "Derived2Type", baseEntity); - var subDerivedEntity = new EdmEntityType("NS", "SubDerivedType", derived1Entity); - model.AddElements(new[] { baseEntity, derived1Entity, derived2Entity, subDerivedEntity }); - model.SetAnnotationValue(baseEntity, new ClrTypeAnnotation(typeof(BaseType))); - model.SetAnnotationValue(derived1Entity, new ClrTypeAnnotation(typeof(Derived1Type))); - model.SetAnnotationValue(derived2Entity, new ClrTypeAnnotation(typeof(Derived2Type))); - model.SetAnnotationValue(subDerivedEntity, new ClrTypeAnnotation(typeof(SubDerivedType))); - - return model; - } - - public class MyAddress - { - public string City { get; set; } - } - - public class CnMyAddress : MyAddress - { - public string Zipcode { get; set; } - } - - public enum MyColor - { - Red - } - - public class BaseType - { } - - public class Derived1Type : BaseType - { } - - public class Derived2Type : BaseType - { } - - public class SubDerivedType : Derived1Type - { } - - public class TypeNotInModel - { } - - public class AssemblyResolver : IAssemblyResolver - { - public IEnumerable Assemblies - { - get - { - yield return typeof(AssemblyResolver).Assembly; - } - } - } } - - public class MyCustomer - { } } diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmModelAnnotationExtensionsTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmModelAnnotationExtensionsTests.cs index 88fea63b3..7fec55a8a 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmModelAnnotationExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/EdmModelAnnotationExtensionsTests.cs @@ -204,6 +204,48 @@ public void GetAndSetModelName_RoundTrip() Assert.Equal(testName, name); } + [Fact] + public void GetTypeMapper_ReturnsDefaultTypeMapper_IfNullModelOrWithoutTypeMapper() + { + // Arrange & Act & Assert + IEdmModel model = null; + Assert.IsType(model.GetTypeMapper()); + + // Arrange & Act & Assert + model = EdmCoreModel.Instance; + Assert.IsType(model.GetTypeMapper()); + + // Arrange & Act & Assert + model = new EdmModel(); + Assert.IsType(model.GetTypeMapper()); + } + + [Fact] + public void SetTypeMapper_ThrowsArugmentNull_Model() + { + // Arrange & Act & Assert + IEdmModel model = null; + ExceptionAssert.ThrowsArgumentNull(() => model.SetTypeMapper(null), "model"); + + model = new Mock().Object; + ExceptionAssert.ThrowsArgumentNull(() => model.SetTypeMapper(null), "mapper"); + } + + [Fact] + public void GetAndSetTypeMapper_RoundTrip() + { + // Arrange + IODataTypeMapper mapper = new Mock().Object; + IEdmModel model = new EdmModel(); + + // Act + model.SetTypeMapper(mapper); + IODataTypeMapper actual = model.GetTypeMapper(); + + // Assert + Assert.Same(mapper, actual); + } + [Fact] public void GetAlternateKeys_ThrowsArugmentNull_ForInputParameters() { diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/IODataTypeMapperExtensionsTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/IODataTypeMapperExtensionsTests.cs new file mode 100644 index 000000000..a9f4ced7c --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/IODataTypeMapperExtensionsTests.cs @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using Microsoft.AspNetCore.OData.Abstracts; +using Microsoft.AspNetCore.OData.Edm; +using Microsoft.AspNetCore.OData.Tests.Commons; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.OData.Tests.Edm +{ + public class IODataTypeMapperExtensionsTests + { + [Fact] + public void GetPrimitiveType_ThrowsArgumentNull_ForInputParameters() + { + // Arrange & Act & Assert + IODataTypeMapper mapper = null; + ExceptionAssert.ThrowsArgumentNull(() => mapper.GetPrimitiveType(primitiveType: null), "mapper"); + + // Arrange & Act & Assert + mapper = new Mock().Object; + ExceptionAssert.ThrowsArgumentNull(() => mapper.GetPrimitiveType(primitiveType: null), "primitiveType"); + } + + [Fact] + public void GetPrimitiveType_Calls_GetPrimitiveTypeOnInterface() + { + // Arrange + Mock primitive = new Mock(); + Mock primitiveRef = new Mock(); + primitiveRef.Setup(x => x.Definition).Returns(primitive.Object); + primitiveRef.SetupGet(x => x.IsNullable).Returns(false); + + Mock mapper = new Mock(); + mapper.Setup(s => s.GetPrimitiveType(primitive.Object, false)).Verifiable(); + + // Act + mapper.Object.GetPrimitiveType(primitiveRef.Object); + + // Assert + mapper.Verify(); + } + + [Fact] + public void GetEdmType_ThrowsArgumentNull_ForInputParameters() + { + // Arrange & Act & Assert + IODataTypeMapper mapper = null; + ExceptionAssert.ThrowsArgumentNull(() => mapper.GetEdmType(null, null), "mapper"); + } + + [Fact] + public void GetEdmType_Calls_GetEdmTypeReferenceOnInterface() + { + // Arrange + Mock model = new Mock(); + Type type = typeof(int); + + Mock mapper = new Mock(); + mapper.Setup(s => s.GetEdmTypeReference(model.Object, type)).Verifiable(); + + // Act + mapper.Object.GetEdmType(model.Object, type); + + // Assert + mapper.Verify(); + } + + [Fact] + public void GetClrType_ThrowsArgumentNull_ForInputParameters() + { + // Arrange & Act & Assert + IODataTypeMapper mapper = null; + ExceptionAssert.ThrowsArgumentNull(() => mapper.GetClrType(null, null), "mapper"); + + // Arrange & Act & Assert + mapper = new Mock().Object; + ExceptionAssert.ThrowsArgumentNull(() => mapper.GetClrType(null, null), "edmType"); + } + + [Fact] + public void GetClrType_Calls_GetClrTypeOnInterface() + { + // Arrange + Mock model = new Mock(); + Mock primitive = new Mock(); + Mock primitiveRef = new Mock(); + primitiveRef.Setup(x => x.Definition).Returns(primitive.Object); + primitiveRef.SetupGet(x => x.IsNullable).Returns(false); + + Mock mapper = new Mock(); + mapper.Setup(s => s.GetClrType(model.Object, primitive.Object, false, AssemblyResolverHelper.Default)).Verifiable(); + + // Act + mapper.Object.GetClrType(model.Object, primitiveRef.Object); + + // Assert + mapper.Verify(); + } + + [Fact] + public void GetClrTypeWithAssemblyResolver_Calls_GetClrTypeOnInterface() + { + // Arrange + Mock resolver = new Mock(); + Mock model = new Mock(); + Mock primitive = new Mock(); + Mock primitiveRef = new Mock(); + primitiveRef.Setup(x => x.Definition).Returns(primitive.Object); + primitiveRef.SetupGet(x => x.IsNullable).Returns(false); + + Mock mapper = new Mock(); + mapper.Setup(s => s.GetClrType(model.Object, primitive.Object, false, resolver.Object)).Verifiable(); + + // Act + mapper.Object.GetClrType(model.Object, primitiveRef.Object, resolver.Object); + + // Assert + mapper.Verify(); + } + } +} diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/TypeCacheItemTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/TypeCacheItemTests.cs new file mode 100644 index 000000000..61599ee69 --- /dev/null +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/TypeCacheItemTests.cs @@ -0,0 +1,126 @@ +//----------------------------------------------------------------------------- +// +// Copyright (c) .NET Foundation and Contributors. All rights reserved. +// See License.txt in the project root for license information. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.OData.Edm; +using Microsoft.OData.Edm; +using Microsoft.OData.ModelBuilder; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.OData.Tests.Edm +{ + public class TypeCacheItemTests + { + #region TryFindEdmType + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + [InlineData(typeof(TypeCacheItemTests))] + public void AddAndFindEdmType_Returns_CachedInstance(Type testType) + { + // Arrange + IEdmTypeReference edmType = new Mock().Object; + TypeCacheItem cache = new TypeCacheItem(); + + // Act + bool found = cache.TryFindEdmType(testType, out _); + Assert.False(found); // not found + + cache.AddClrToEdmMap(testType, edmType); + found = cache.TryFindEdmType(testType, out IEdmTypeReference acutal); + + // Assert + Assert.True(found); + Assert.Same(edmType, acutal); + } + + [Fact] + public void AddClrToEdmMap_Cached_OnlyOneInstance() + { + // Arrange + TypeCacheItem cache = new TypeCacheItem(); + Action cacheCallAndVerify = () => + { + IEdmTypeReference edmType = new Mock().Object; + cache.AddClrToEdmMap(typeof(TypeCacheItemTests), edmType); + Assert.Single(cache.ClrToEdmTypeCache); + }; + + // Act & Assert + cacheCallAndVerify(); + + // 5 is a magic number, it doesn't matter, just want to call it multiple times. + for (int i = 0; i < 5; i++) + { + cacheCallAndVerify(); + } + + cacheCallAndVerify(); + } + + #endregion + + #region TryFindClrType + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(string))] + [InlineData(typeof(TypeCacheItemTests))] + public void AddAndGetClrType_Returns_CorrectType(Type testType) + { + // Arrange + IEdmType edmType = new Mock().Object; + TypeCacheItem cache = new TypeCacheItem(); + + // Act + bool found = cache.TryFindClrType(edmType, true, out _); + Assert.False(found); + + cache.AddEdmToClrMap(edmType, true, testType); + + found = cache.TryFindClrType(edmType, true, out Type actualType); + + // Act & Assert + Assert.True(found); + Assert.Same(testType, actualType); + } + + [Fact] + public void AddEdmToClrMap_Cached_OnlyOneInstance() + { + // Arrange + TypeCacheItem cache = new TypeCacheItem(); + IEdmType edmType = new Mock().Object; + + Action cacheCallAndVerify = () => + { + cache.AddEdmToClrMap(edmType, true, typeof(int?)); + cache.AddEdmToClrMap(edmType, false, typeof(int)); + + KeyValuePair item = Assert.Single(cache.EdmToClrTypeCache); + Assert.Same(edmType, item.Key); + Assert.Equal(typeof(int), item.Value.Item1); + Assert.Equal(typeof(int?), item.Value.Item2); + }; + + // Act & Assert + cacheCallAndVerify(); + + // 5 is a magic number, it doesn't matter, just want to call it multiple times. + for (int i = 0; i < 5; i++) + { + cacheCallAndVerify(); + } + + cacheCallAndVerify(); + } + #endregion + } +} diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/CollectionDeserializationHelpersTest.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/CollectionDeserializationHelpersTest.cs index 07966c4e3..a8d807a8b 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/CollectionDeserializationHelpersTest.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/CollectionDeserializationHelpersTest.cs @@ -90,7 +90,7 @@ public void CopyItemsToCollection_CanConvertUtcDateTime() // Act source.AddToCollection(newCollection, typeof(DateTime), typeof(CollectionDeserializationHelpersTest), - "PropertyName", newCollection.GetType(), timeZoneInfo: null); + "PropertyName", newCollection.GetType(), context: null); // Assert Assert.Equal(expect, newCollection as IEnumerable); @@ -105,10 +105,14 @@ public void CopyItemsToCollection_CanConvertUtcDateTime_ToDestinationTimeZone() IList source = new List { new DateTimeOffset(dt1), new DateTimeOffset(dt2) }; IEnumerable newCollection = new CustomCollectionWithAdd(); TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); // -8:00 / -7:00 + ODataDeserializerContext context = new ODataDeserializerContext + { + TimeZone = timeZoneInfo + }; // Act source.AddToCollection(newCollection, typeof(DateTime), typeof(CollectionDeserializationHelpersTest), - "PropertyName", newCollection.GetType(), timeZoneInfo); + "PropertyName", newCollection.GetType(), context); // Assert Assert.Equal(new[] { dt1.AddHours(-8), dt2.AddHours(-7) }, newCollection as IEnumerable); @@ -130,7 +134,7 @@ public void CopyItemsToCollection_CanConvertLocalDateTime_ToDestinationTimeZone( // Act source.AddToCollection(newCollection, typeof(DateTime), typeof(CollectionDeserializationHelpersTest), - "PropertyName", newCollection.GetType(), timeZoneInfo: null); + "PropertyName", newCollection.GetType(), context: null); // Assert Assert.Equal(expect, newCollection as IEnumerable); @@ -145,10 +149,14 @@ public void CopyItemsToCollection_CanConvertLocalDateTime() IList source = new List { dto1, dto2 }; IEnumerable newCollection = new CustomCollectionWithAdd(); TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); // -8:00 / -7:00 + ODataDeserializerContext context = new ODataDeserializerContext + { + TimeZone = timeZone + }; // Act source.AddToCollection(newCollection, typeof(DateTime), typeof(CollectionDeserializationHelpersTest), - "PropertyName", newCollection.GetType(), timeZone); + "PropertyName", newCollection.GetType(), context); // Assert Assert.Equal(new[] { new DateTime(2014, 12, 15, 9, 2, 3), new DateTime(2014, 12, 15, 19, 2, 3) }, diff --git a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/DeserializationHelpersTest.cs b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/DeserializationHelpersTest.cs index efd562e02..132fd92a3 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/DeserializationHelpersTest.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Formatter/Deserialization/DeserializationHelpersTest.cs @@ -170,7 +170,7 @@ public void SetCollectionProperty_CanConvertDataTime_ByDefault() IEnumerable expects = dtos.Select(e => e.LocalDateTime); // Act - DeserializationHelpers.SetCollectionProperty(source, edmProperty, dtos, edmProperty.Name, timeZoneInfo: null); + DeserializationHelpers.SetCollectionProperty(source, edmProperty, dtos, edmProperty.Name, context: null); // Assert Assert.Equal(expects, source.DateTimeList); @@ -191,9 +191,13 @@ public void SetCollectionProperty_CanConvertDataTime_ByTimeZoneInfo() new DateTimeOffset(dt, new TimeSpan(+7, 0, 0)), new DateTimeOffset(dt, new TimeSpan(-8, 0, 0)) }; + ODataDeserializerContext context = new ODataDeserializerContext + { + TimeZone = tzi + }; // Act - DeserializationHelpers.SetCollectionProperty(source, edmProperty, dtos, edmProperty.Name, timeZoneInfo: tzi); + DeserializationHelpers.SetCollectionProperty(source, edmProperty, dtos, edmProperty.Name, context: context); // Assert Assert.Equal(new List { dt.AddHours(-8), dt.AddHours(-15), dt }, source.DateTimeList); @@ -375,7 +379,7 @@ public void ApplyProperty_FailsWithUsefulErrorMessageOnUnknownProperty() var property = new ODataProperty { Name = "Unknown", Value = "Value" }; var entityType = new EdmComplexType("namespace", "name"); - entityType.AddStructuralProperty("Known", typeof(string).GetEdmPrimitiveTypeReference()); + entityType.AddStructuralProperty("Known", EdmCoreModel.Instance.GetString(true)); var entityTypeReference = new EdmComplexTypeReference(entityType, isNullable: false); diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl index 56f3db83d..fe01d0ba8 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl +++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.Net5.bsl @@ -524,6 +524,13 @@ public class Microsoft.AspNetCore.OData.Deltas.DeltaSet`1 : System.Collections.O System.Type StructuredType { public virtual get; } } +public interface Microsoft.AspNetCore.OData.Edm.IODataTypeMapper { + System.Type GetClrType (Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmType edmType, bool nullable, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) + Microsoft.OData.Edm.IEdmTypeReference GetEdmTypeReference (Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) + Microsoft.OData.Edm.IEdmPrimitiveTypeReference GetPrimitiveType (System.Type clrType) + System.Type GetPrimitiveType (Microsoft.OData.Edm.IEdmPrimitiveType primitiveType, bool nullable) +} + [ ExtensionAttribute(), ] @@ -558,10 +565,20 @@ public sealed class Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions ] public static string GetModelName (Microsoft.OData.Edm.IEdmModel model) + [ + ExtensionAttribute(), + ] + public static Microsoft.AspNetCore.OData.Edm.IODataTypeMapper GetTypeMapper (Microsoft.OData.Edm.IEdmModel model) + [ ExtensionAttribute(), ] public static void SetModelName (Microsoft.OData.Edm.IEdmModel model, string name) + + [ + ExtensionAttribute(), + ] + public static void SetTypeMapper (Microsoft.OData.Edm.IEdmModel model, Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper) } [ @@ -609,6 +626,31 @@ public sealed class Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions public static void SetOperationLinkBuilder (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmOperation operation, Microsoft.AspNetCore.OData.Edm.OperationLinkBuilder operationLinkBuilder) } +[ +ExtensionAttribute(), +] +public sealed class Microsoft.AspNetCore.OData.Edm.IODataTypeMapperExtensions { + [ + ExtensionAttribute(), + ] + public static System.Type GetClrType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmTypeReference edmType) + + [ + ExtensionAttribute(), + ] + public static System.Type GetClrType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmTypeReference edmType, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) + + [ + ExtensionAttribute(), + ] + public static Microsoft.OData.Edm.IEdmType GetEdmType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) + + [ + ExtensionAttribute(), + ] + public static System.Type GetPrimitiveType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmPrimitiveTypeReference primitiveType) +} + public class Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation { public CustomAggregateMethodAnnotation () @@ -616,6 +658,15 @@ public class Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation { public bool GetMethodInfo (string methodToken, System.Type returnType, out System.Reflection.MethodInfo& methodInfo) } +public class Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper : IODataTypeMapper { + public DefaultODataTypeMapper () + + public virtual System.Type GetClrType (Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmType edmType, bool nullable, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) + public virtual Microsoft.OData.Edm.IEdmTypeReference GetEdmTypeReference (Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) + public virtual Microsoft.OData.Edm.IEdmPrimitiveTypeReference GetPrimitiveType (System.Type clrType) + public virtual System.Type GetPrimitiveType (Microsoft.OData.Edm.IEdmPrimitiveType primitiveType, bool nullable) +} + public class Microsoft.AspNetCore.OData.Edm.EntitySelfLinks { public EntitySelfLinks () diff --git a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl index 56f3db83d..fe01d0ba8 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl +++ b/test/Microsoft.AspNetCore.OData.Tests/PublicApi/Microsoft.AspNetCore.OData.PublicApi.NetCore31.bsl @@ -524,6 +524,13 @@ public class Microsoft.AspNetCore.OData.Deltas.DeltaSet`1 : System.Collections.O System.Type StructuredType { public virtual get; } } +public interface Microsoft.AspNetCore.OData.Edm.IODataTypeMapper { + System.Type GetClrType (Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmType edmType, bool nullable, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) + Microsoft.OData.Edm.IEdmTypeReference GetEdmTypeReference (Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) + Microsoft.OData.Edm.IEdmPrimitiveTypeReference GetPrimitiveType (System.Type clrType) + System.Type GetPrimitiveType (Microsoft.OData.Edm.IEdmPrimitiveType primitiveType, bool nullable) +} + [ ExtensionAttribute(), ] @@ -558,10 +565,20 @@ public sealed class Microsoft.AspNetCore.OData.Edm.EdmModelAnnotationExtensions ] public static string GetModelName (Microsoft.OData.Edm.IEdmModel model) + [ + ExtensionAttribute(), + ] + public static Microsoft.AspNetCore.OData.Edm.IODataTypeMapper GetTypeMapper (Microsoft.OData.Edm.IEdmModel model) + [ ExtensionAttribute(), ] public static void SetModelName (Microsoft.OData.Edm.IEdmModel model, string name) + + [ + ExtensionAttribute(), + ] + public static void SetTypeMapper (Microsoft.OData.Edm.IEdmModel model, Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper) } [ @@ -609,6 +626,31 @@ public sealed class Microsoft.AspNetCore.OData.Edm.EdmModelLinkBuilderExtensions public static void SetOperationLinkBuilder (Microsoft.OData.Edm.IEdmModel model, Microsoft.OData.Edm.IEdmOperation operation, Microsoft.AspNetCore.OData.Edm.OperationLinkBuilder operationLinkBuilder) } +[ +ExtensionAttribute(), +] +public sealed class Microsoft.AspNetCore.OData.Edm.IODataTypeMapperExtensions { + [ + ExtensionAttribute(), + ] + public static System.Type GetClrType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmTypeReference edmType) + + [ + ExtensionAttribute(), + ] + public static System.Type GetClrType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmTypeReference edmType, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) + + [ + ExtensionAttribute(), + ] + public static Microsoft.OData.Edm.IEdmType GetEdmType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) + + [ + ExtensionAttribute(), + ] + public static System.Type GetPrimitiveType (Microsoft.AspNetCore.OData.Edm.IODataTypeMapper mapper, Microsoft.OData.Edm.IEdmPrimitiveTypeReference primitiveType) +} + public class Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation { public CustomAggregateMethodAnnotation () @@ -616,6 +658,15 @@ public class Microsoft.AspNetCore.OData.Edm.CustomAggregateMethodAnnotation { public bool GetMethodInfo (string methodToken, System.Type returnType, out System.Reflection.MethodInfo& methodInfo) } +public class Microsoft.AspNetCore.OData.Edm.DefaultODataTypeMapper : IODataTypeMapper { + public DefaultODataTypeMapper () + + public virtual System.Type GetClrType (Microsoft.OData.Edm.IEdmModel edmModel, Microsoft.OData.Edm.IEdmType edmType, bool nullable, Microsoft.OData.ModelBuilder.IAssemblyResolver assembliesResolver) + public virtual Microsoft.OData.Edm.IEdmTypeReference GetEdmTypeReference (Microsoft.OData.Edm.IEdmModel edmModel, System.Type clrType) + public virtual Microsoft.OData.Edm.IEdmPrimitiveTypeReference GetPrimitiveType (System.Type clrType) + public virtual System.Type GetPrimitiveType (Microsoft.OData.Edm.IEdmPrimitiveType primitiveType, bool nullable) +} + public class Microsoft.AspNetCore.OData.Edm.EntitySelfLinks { public EntitySelfLinks () From 9df1b4cfea4e6cc89df4c1ff20c3e1bf441756d0 Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Mon, 1 Nov 2021 10:34:09 -0700 Subject: [PATCH 2/2] Resolve the comments --- .../Edm/DefaultODataTypeMapper.cs | 42 ++++++++----------- .../Edm/TypeCacheItem.cs | 4 +- .../Microsoft.AspNetCore.OData.xml | 4 +- .../PublicAPI.Unshipped.txt | 1 - .../Edm/DefaultODataTypeMapperTests.cs | 29 ------------- 5 files changed, 22 insertions(+), 58 deletions(-) diff --git a/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs b/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs index aa6a31040..1f69b6445 100644 --- a/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs +++ b/src/Microsoft.AspNetCore.OData/Edm/DefaultODataTypeMapper.cs @@ -35,15 +35,15 @@ public class DefaultODataTypeMapper : IODataTypeMapper /// The default mapping between Edm primitive type and Clr primitive type. /// Primitive types are cross Edm models. /// - private static ConcurrentDictionary ClrPrimitiveTypes - = new ConcurrentDictionary(); + private static IDictionary ClrPrimitiveTypes + = new Dictionary(); /// /// Item1 --> non-nullable /// Item2 --> nullable /// - private static ConcurrentDictionary EdmPrimitiveTypes - = new ConcurrentDictionary(); + private static IDictionary EdmPrimitiveTypes + = new Dictionary(); static DefaultODataTypeMapper() { @@ -189,7 +189,8 @@ public virtual IEdmTypeReference GetEdmTypeReference(IEdmModel edmModel, Type cl throw Error.ArgumentNull(nameof(edmModel)); } - TypeCacheItem map = GetOrCreateCacheItem(edmModel); + TypeCacheItem map = _cache.GetOrAdd(edmModel, d => new TypeCacheItem()); + // Search from cache if (map.TryFindEdmType(clrType, out IEdmTypeReference edmTypeRef)) { @@ -319,7 +320,7 @@ public virtual Type GetClrType(IEdmModel edmModel, IEdmType edmType, bool nullab assembliesResolver = assembliesResolver ?? AssemblyResolverHelper.Default; // Let's search from cache - TypeCacheItem map = GetOrCreateCacheItem(edmModel); + TypeCacheItem map = _cache.GetOrAdd(edmModel, d => new TypeCacheItem()); if (map.TryFindClrType(edmType, nullable, out Type clrType)) { return clrType; @@ -380,7 +381,7 @@ internal static Type FindClrType(IEdmModel edmModel, IEdmType edmType, IAssembly if (matchingTypes.Count() > 1) { - throw Error.Argument("edmTypeReference", SRResources.MultipleMatchingClrTypesForEdmType, + throw Error.InvalidOperation(SRResources.MultipleMatchingClrTypesForEdmType, typeName, string.Join(",", matchingTypes.Select(type => type.AssemblyQualifiedName))); } @@ -393,17 +394,6 @@ internal static Type FindClrType(IEdmModel edmModel, IEdmType edmType, IAssembly } #endregion - private TypeCacheItem GetOrCreateCacheItem(IEdmModel model) - { - if (!_cache.TryGetValue(model, out TypeCacheItem map)) - { - map = new TypeCacheItem(); - _cache[model] = map; - } - - return map; - } - private static Type ExtractGenericInterface(Type queryType, Type interfaceType) { Func matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType; @@ -413,9 +403,6 @@ private static Type ExtractGenericInterface(Type queryType, Type interfaceType) private static IEnumerable GetMatchingTypes(string edmFullName, IAssemblyResolver assembliesResolver) => TypeHelper.GetLoadedTypes(assembliesResolver).Where(t => t.IsPublic && t.EdmFullName() == edmFullName); - private static KeyValuePair BuildTypeMapping1(EdmPrimitiveTypeKind primitiveKind) - => new KeyValuePair(typeof(T), EdmCoreModel.Instance.GetPrimitive(primitiveKind, typeof(T).IsNullable())); - private static void BuildTypeMapping(EdmPrimitiveTypeKind primitiveKind, bool isStandard = true) { Type type = typeof(T); @@ -431,12 +418,19 @@ private static void BuildTypeMapping(EdmPrimitiveTypeKind primitiveKind, bool // for nullable, for example System.String, we don't have non-nullable string. // so, let's save it for both. // And since we make the order un-changable, it means 'nullable' coming first. - // Therefore, for simplicity, we can safe call "TryAdd". - EdmPrimitiveTypes.TryAdd(primitiveType, (type, type)); + EdmPrimitiveTypes[primitiveType] = (type, type); } else { - EdmPrimitiveTypes.AddOrUpdate(primitiveType, t => (type, null), (t, o) => (type, o.Item2)); + if (EdmPrimitiveTypes.ContainsKey(primitiveType)) + { + (Type _, Type edmType2) = EdmPrimitiveTypes[primitiveType]; + EdmPrimitiveTypes[primitiveType] = (type, edmType2); + } + else + { + EdmPrimitiveTypes[primitiveType] = (type, null); + } } } } diff --git a/src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs b/src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs index 3fa53dc94..ee9eb39bb 100644 --- a/src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs +++ b/src/Microsoft.AspNetCore.OData/Edm/TypeCacheItem.cs @@ -17,7 +17,7 @@ internal class TypeCacheItem /// /// to . /// - public ConcurrentDictionary ClrToEdmTypeCache = new ConcurrentDictionary(); + public ConcurrentDictionary ClrToEdmTypeCache { get; } = new ConcurrentDictionary(); public bool TryFindEdmType(Type clrType, out IEdmTypeReference edmType) { @@ -42,7 +42,7 @@ public void AddClrToEdmMap(Type clrType, IEdmTypeReference edmType) /// item1: non-nullable /// item2: nullable /// - public ConcurrentDictionary EdmToClrTypeCache = new ConcurrentDictionary(); + public ConcurrentDictionary EdmToClrTypeCache { get; } = new ConcurrentDictionary(); public bool TryFindClrType(IEdmType edmType, bool isNullable, out Type clrType) { diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml index 4a1366781..afa14bee7 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml @@ -2730,12 +2730,12 @@ Gets a boolean indicating whether the link factory follows OData conventions or not. - + to . - + to . item1: non-nullable diff --git a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt index 6b5a688f8..8bfbde015 100644 --- a/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt +++ b/src/Microsoft.AspNetCore.OData/PublicAPI.Unshipped.txt @@ -1481,7 +1481,6 @@ static Microsoft.AspNetCore.OData.Extensions.ActionModelExtensions.IsODataIgnore static Microsoft.AspNetCore.OData.Extensions.ControllerModelExtensions.GetAttribute(this Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel controller) -> T static Microsoft.AspNetCore.OData.Extensions.ControllerModelExtensions.HasAttribute(this Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel controller) -> bool static Microsoft.AspNetCore.OData.Extensions.ControllerModelExtensions.IsODataIgnored(this Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel controller) -> bool -static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.GetTypeMappingProvider(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.Edm.IODataTypeMapper static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.ODataBatchFeature(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.Abstracts.IODataBatchFeature static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.ODataFeature(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.Abstracts.IODataFeature static Microsoft.AspNetCore.OData.Extensions.HttpContextExtensions.ODataOptions(this Microsoft.AspNetCore.Http.HttpContext httpContext) -> Microsoft.AspNetCore.OData.ODataOptions diff --git a/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs index 3064e0933..2e1cd1e1e 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Edm/DefaultODataTypeMapperTests.cs @@ -121,35 +121,6 @@ public void GetPrimitiveType_ForClrType_WorksAsExpected_ForSpatialPrimitive(Type Assert.True(primitiveTypeReference.IsNullable); } - //[Theory] - //[InlineData(null, null, false)] - //[InlineData(typeof(int), typeof(int), false)] - //[InlineData(typeof(int?), typeof(int?), false)] - //[InlineData(typeof(object), typeof(object), false)] - //[InlineData(typeof(MyAddress), typeof(MyAddress), false)] - //// non-standard primitive types - //[InlineData(typeof(XElement), typeof(string), true)] - //[InlineData(typeof(ushort), typeof(int), true)] - //[InlineData(typeof(ushort?), typeof(int?), true)] - //[InlineData(typeof(uint), typeof(long), true)] - //[InlineData(typeof(uint?), typeof(long?), true)] - //[InlineData(typeof(ulong), typeof(long), true)] - //[InlineData(typeof(ulong?), typeof(long?), true)] - //[InlineData(typeof(char[]), typeof(string), true)] - //[InlineData(typeof(char), typeof(string), true)] - //[InlineData(typeof(char?), typeof(string), true)] - //[InlineData(typeof(DateTime), typeof(DateTimeOffset), true)] - //[InlineData(typeof(DateTime?), typeof(DateTimeOffset?), true)] - //public void IsNonstandardEdmPrimitiveWorksAsExpectedForNonstandardType(Type clrType, Type expectType, bool isNonstandard) - //{ - // // Arrange & Act - // Type actual = _provider.IsNonstandardEdmPrimitive(clrType, out bool isNonstandardEdmPrimtive); - - // // Assert - // Assert.Equal(expectType, actual); - // Assert.Equal(isNonstandard, isNonstandardEdmPrimtive); - //} - [Theory] [InlineData(EdmPrimitiveTypeKind.String, typeof(string), typeof(string))] [InlineData(EdmPrimitiveTypeKind.Boolean, typeof(bool?), typeof(bool))]