diff --git a/AutoMapper.AspNetCore.OData.EFCore/Expansions/DefaultExpansionsBuilder.cs b/AutoMapper.AspNetCore.OData.EFCore/Expansions/DefaultExpansionsBuilder.cs index 22f6467..42ef037 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/Expansions/DefaultExpansionsBuilder.cs +++ b/AutoMapper.AspNetCore.OData.EFCore/Expansions/DefaultExpansionsBuilder.cs @@ -50,17 +50,17 @@ public void Build(List memberSelectors, ParameterExpression pa if (LogicBuilder.Expressions.Utils.TypeExtensions.IsLiteralType(memberType)) continue; - + if (LogicBuilder.Expressions.Utils.TypeExtensions.IsList(memberType) && LogicBuilder.Expressions.Utils.TypeExtensions.IsLiteralType ( - LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(memberType) + TypeExtensions.GetUnderlyingElementType(memberType) ) == false) { List childMemberSelectors = []; ParameterExpression childParam = Expression.Parameter ( - LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(memberType), + TypeExtensions.GetUnderlyingElementType(memberType), GetChildParameterName(param.Name) ); @@ -76,7 +76,7 @@ public void Build(List memberSelectors, ParameterExpression pa ( typeof(Enumerable), "Select", - [LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(selector), typeof(object)], + [TypeExtensions.GetUnderlyingElementType(selector), typeof(object)], selector, childSelector ), diff --git a/AutoMapper.AspNetCore.OData.EFCore/LinqExtensions.cs b/AutoMapper.AspNetCore.OData.EFCore/LinqExtensions.cs index b8948de..bf0f88d 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/LinqExtensions.cs +++ b/AutoMapper.AspNetCore.OData.EFCore/LinqExtensions.cs @@ -398,7 +398,7 @@ public static Expression GetSkipCall(this Expression expression, int? skip) ( expression.Type.IsIQueryable() ? typeof(Queryable) : typeof(Enumerable), "Skip", - new[] { expression.GetUnderlyingElementType() }, + new[] { LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression) }, expression, Expression.Constant(skip.Value) ); @@ -412,7 +412,7 @@ public static Expression GetTakeCall(this Expression expression, int? top) ( expression.Type.IsIQueryable() ? typeof(Queryable) : typeof(Enumerable), "Take", - new[] { expression.GetUnderlyingElementType() }, + new[] { LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression) }, expression, Expression.Constant(top.Value) ); @@ -433,7 +433,7 @@ public static Expression GetOrderByCountCall(this Expression expression, CountNo public static Expression GetOrderByCountCall(this Expression expression, CountNode countNode, string methodName, ODataQueryContext context, string selectorParameterName = "a") { - Type sourceType = expression.GetUnderlyingElementType(); + Type sourceType = LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression); ParameterExpression param = Expression.Parameter(sourceType, selectorParameterName); Expression countSelector; @@ -441,7 +441,7 @@ public static Expression GetOrderByCountCall(this Expression expression, CountNo if (countNode.FilterClause is not null) { string memberFullName = countNode.GetPropertyPath(); - Type filterType = sourceType.GetMemberInfoFromFullName(memberFullName).GetMemberType().GetUnderlyingElementType(); + Type filterType = LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(sourceType.GetMemberInfoFromFullName(memberFullName).GetMemberType()); LambdaExpression filterExpression = countNode.FilterClause.GetFilterExpression(filterType, context); countSelector = param.MakeSelector(memberFullName).GetCountCall(filterExpression); } @@ -465,7 +465,7 @@ public static Expression GetOrderByCountCall(this Expression expression, CountNo public static Expression GetOrderByCall(this Expression expression, string memberFullName, string methodName, string selectorParameterName = "a") { - Type sourceType = expression.GetUnderlyingElementType(); + Type sourceType = LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression); MemberInfo memberInfo = sourceType.GetMemberInfoFromFullName(memberFullName); return Expression.Call ( diff --git a/AutoMapper.AspNetCore.OData.EFCore/TypeExtensions.cs b/AutoMapper.AspNetCore.OData.EFCore/TypeExtensions.cs index d0d78d8..6932a42 100644 --- a/AutoMapper.AspNetCore.OData.EFCore/TypeExtensions.cs +++ b/AutoMapper.AspNetCore.OData.EFCore/TypeExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reflection; namespace AutoMapper.AspNet.OData @@ -102,6 +103,31 @@ List DoLoad(List allTypes) public static Dictionary GetEdmToClrTypeMappings() => Constants.EdmToClrTypeMappings; + public static Type GetUnderlyingElementType(this Type type) + { + TypeInfo typeInfo = type.GetTypeInfo(); + if (typeInfo.IsArray) + return typeInfo.GetElementType(); + + if (!type.IsGenericType) + throw new ArgumentException(nameof(type)); + + Type[] genericArguments = type.GetGenericArguments(); + Type genericTypeDefinition = type.GetGenericTypeDefinition(); + + if (genericTypeDefinition == typeof(IGrouping<,>)) + return genericArguments[1]; + else if (typeof(IDictionary<,>).IsAssignableFrom(genericTypeDefinition)) + return typeof(KeyValuePair<,>).MakeGenericType(genericArguments[0], genericArguments[1]); + else if (genericArguments.Length == 1) + return genericArguments[0]; + else + throw new ArgumentException(nameof(type)); + } + + public static Type GetUnderlyingElementType(this Expression expression) + => GetUnderlyingElementType(expression.Type); + private class AssemblyResolver : IAssemblyResolver { private List _assemblides; diff --git a/AutoMapper.OData.EFCore.Tests/AirVinylData/AirVinylDatabaseInitializer.cs b/AutoMapper.OData.EFCore.Tests/AirVinylData/AirVinylDatabaseInitializer.cs index 1c3ba43..7f15a6e 100644 --- a/AutoMapper.OData.EFCore.Tests/AirVinylData/AirVinylDatabaseInitializer.cs +++ b/AutoMapper.OData.EFCore.Tests/AirVinylData/AirVinylDatabaseInitializer.cs @@ -302,10 +302,16 @@ public static void SeedDatabase(AirVinylDbContext context) context.DynamicVinylRecordProperties.Add(new DynamicProperty() { - VinylRecordId = vinylRecords.First(r => r.Title == "Nevermind").VinylRecordId,//1, + VinylRecordId = vinylRecords.First(r => r.Title == "Nevermind").VinylRecordId,//1 Key = "Publisher", Value = "Geffen" }); + context.DynamicVinylRecordProperties.Add(new DynamicProperty() + { + VinylRecordId = vinylRecords.First(r => r.Title == "Nevermind").VinylRecordId,//1 + Key = "SomeData", + Value = new { TestProp = "value" } + }); context.SaveChanges(); context.DoorManufacturers.AddRange( diff --git a/AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylLinkModel.cs b/AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylLinkModel.cs new file mode 100644 index 0000000..8ab7e6a --- /dev/null +++ b/AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylLinkModel.cs @@ -0,0 +1,7 @@ +namespace AutoMapper.OData.EFCore.Tests.AirVinylModel +{ + public class VinylLinkModel + { + public string Href { get; set; } + } +} diff --git a/AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylRecordModel.cs b/AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylRecordModel.cs index 52b8f93..93e0d15 100644 --- a/AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylRecordModel.cs +++ b/AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylRecordModel.cs @@ -33,5 +33,22 @@ public class VinylRecordModel = new List(); public IDictionary Properties { get; set; } + + private Dictionary _links; + public IDictionary Links { + get + { + if (_links is null) + { + _links = new Dictionary() + { + { "buyingLink", new VinylLinkModel { Href = $"http://test/buy/{VinylRecordId}" } }, + { "reviewLink", new VinylLinkModel { Href = $"http://test/review/{VinylRecordId}" } } + }; + } + + return _links; + } + } } } diff --git a/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs b/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs index 0fcbcec..01a30b9 100644 --- a/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs +++ b/AutoMapper.OData.EFCore.Tests/ExpansionTests.cs @@ -1,6 +1,7 @@ using AutoMapper.AspNet.OData; using AutoMapper.OData.EFCore.Tests.AirVinylData; using AutoMapper.OData.EFCore.Tests.AirVinylModel; +using LogicBuilder.Expressions.Utils; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.OData; using Microsoft.AspNetCore.OData.Query; @@ -42,6 +43,28 @@ void Test(ICollection collection) } } + [Fact] + public async Task GetVinylRecordsExpandsComplexTypesByDefault() + { + string query = "/vinylrecordmodel"; + Test(await GetAsync(query)); + + void Test(ICollection collection) + { + Assert.True(collection.Count > 0); + + //Navigation properties + Assert.True(collection.All(vinyl => vinyl.Person is null)); + Assert.True(collection.All(vinyl => vinyl.PressingDetail is null)); + + //Complex types + Assert.Contains(collection, vinyl => vinyl.Properties.Count != 0); + Assert.Contains(collection, vinyl => vinyl.Properties.Any(p => !p.Value.GetType().IsLiteralType())); + Assert.Contains(collection, vinyl => vinyl.DynamicVinylRecordProperties.Count != 0); + Assert.Contains(collection, vinyl => vinyl.Links.Count != 0); + } + } + [Fact] public async Task GetRecordStoresExpandsComplexTypesByDefault() { diff --git a/AutoMapper.OData.EFCore.Tests/Mappings/AirVinylMappings.cs b/AutoMapper.OData.EFCore.Tests/Mappings/AirVinylMappings.cs index 01e716a..748dc64 100644 --- a/AutoMapper.OData.EFCore.Tests/Mappings/AirVinylMappings.cs +++ b/AutoMapper.OData.EFCore.Tests/Mappings/AirVinylMappings.cs @@ -30,6 +30,7 @@ public AirVinylMappings() CreateMap() .ForAllMembers(o => o.ExplicitExpansion()); CreateMap() + .ForMember(dest => dest.Links, o => o.Ignore()) .ForAllMembers(o => o.ExplicitExpansion()); } }