Skip to content

Commit 3a314ae

Browse files
alexmo16BlaiseD
andauthored
Default expansion of dictionaries supported (#240)
* Dictionaries supported within DefaultExpansionsBuilder. * Handle dictionaries the same way lists are handled. Add an implementation of GetUnderlyingElementType inside AutoMapper.AspNet.OData instead of using the one from LogicBuilder.Expressions.Utils. * Add tests for dictionaries of complex types. * fix configuration validation test * Staying with the previously referenced LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType in LinqExtensions. --------- Co-authored-by: Blaise Taylor <[email protected]>
1 parent 32a8ee5 commit 3a314ae

File tree

8 files changed

+90
-10
lines changed

8 files changed

+90
-10
lines changed

AutoMapper.AspNetCore.OData.EFCore/Expansions/DefaultExpansionsBuilder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,17 @@ public void Build(List<LambdaExpression> memberSelectors, ParameterExpression pa
5050

5151
if (LogicBuilder.Expressions.Utils.TypeExtensions.IsLiteralType(memberType))
5252
continue;
53-
53+
5454
if (LogicBuilder.Expressions.Utils.TypeExtensions.IsList(memberType)
5555
&& LogicBuilder.Expressions.Utils.TypeExtensions.IsLiteralType
5656
(
57-
LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(memberType)
57+
TypeExtensions.GetUnderlyingElementType(memberType)
5858
) == false)
5959
{
6060
List<LambdaExpression> childMemberSelectors = [];
6161
ParameterExpression childParam = Expression.Parameter
6262
(
63-
LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(memberType),
63+
TypeExtensions.GetUnderlyingElementType(memberType),
6464
GetChildParameterName(param.Name)
6565
);
6666

@@ -76,7 +76,7 @@ public void Build(List<LambdaExpression> memberSelectors, ParameterExpression pa
7676
(
7777
typeof(Enumerable),
7878
"Select",
79-
[LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(selector), typeof(object)],
79+
[TypeExtensions.GetUnderlyingElementType(selector), typeof(object)],
8080
selector,
8181
childSelector
8282
),

AutoMapper.AspNetCore.OData.EFCore/LinqExtensions.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ public static Expression GetSkipCall(this Expression expression, int? skip)
398398
(
399399
expression.Type.IsIQueryable() ? typeof(Queryable) : typeof(Enumerable),
400400
"Skip",
401-
new[] { expression.GetUnderlyingElementType() },
401+
new[] { LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression) },
402402
expression,
403403
Expression.Constant(skip.Value)
404404
);
@@ -412,7 +412,7 @@ public static Expression GetTakeCall(this Expression expression, int? top)
412412
(
413413
expression.Type.IsIQueryable() ? typeof(Queryable) : typeof(Enumerable),
414414
"Take",
415-
new[] { expression.GetUnderlyingElementType() },
415+
new[] { LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression) },
416416
expression,
417417
Expression.Constant(top.Value)
418418
);
@@ -433,15 +433,15 @@ public static Expression GetOrderByCountCall(this Expression expression, CountNo
433433

434434
public static Expression GetOrderByCountCall(this Expression expression, CountNode countNode, string methodName, ODataQueryContext context, string selectorParameterName = "a")
435435
{
436-
Type sourceType = expression.GetUnderlyingElementType();
436+
Type sourceType = LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression);
437437
ParameterExpression param = Expression.Parameter(sourceType, selectorParameterName);
438438

439439
Expression countSelector;
440440

441441
if (countNode.FilterClause is not null)
442442
{
443443
string memberFullName = countNode.GetPropertyPath();
444-
Type filterType = sourceType.GetMemberInfoFromFullName(memberFullName).GetMemberType().GetUnderlyingElementType();
444+
Type filterType = LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(sourceType.GetMemberInfoFromFullName(memberFullName).GetMemberType());
445445
LambdaExpression filterExpression = countNode.FilterClause.GetFilterExpression(filterType, context);
446446
countSelector = param.MakeSelector(memberFullName).GetCountCall(filterExpression);
447447
}
@@ -465,7 +465,7 @@ public static Expression GetOrderByCountCall(this Expression expression, CountNo
465465

466466
public static Expression GetOrderByCall(this Expression expression, string memberFullName, string methodName, string selectorParameterName = "a")
467467
{
468-
Type sourceType = expression.GetUnderlyingElementType();
468+
Type sourceType = LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(expression);
469469
MemberInfo memberInfo = sourceType.GetMemberInfoFromFullName(memberFullName);
470470
return Expression.Call
471471
(

AutoMapper.AspNetCore.OData.EFCore/TypeExtensions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using System.Linq.Expressions;
89
using System.Reflection;
910

1011
namespace AutoMapper.AspNet.OData
@@ -102,6 +103,31 @@ List<Type> DoLoad(List<Type> allTypes)
102103

103104
public static Dictionary<EdmTypeStructure, Type> GetEdmToClrTypeMappings() => Constants.EdmToClrTypeMappings;
104105

106+
public static Type GetUnderlyingElementType(this Type type)
107+
{
108+
TypeInfo typeInfo = type.GetTypeInfo();
109+
if (typeInfo.IsArray)
110+
return typeInfo.GetElementType();
111+
112+
if (!type.IsGenericType)
113+
throw new ArgumentException(nameof(type));
114+
115+
Type[] genericArguments = type.GetGenericArguments();
116+
Type genericTypeDefinition = type.GetGenericTypeDefinition();
117+
118+
if (genericTypeDefinition == typeof(IGrouping<,>))
119+
return genericArguments[1];
120+
else if (typeof(IDictionary<,>).IsAssignableFrom(genericTypeDefinition))
121+
return typeof(KeyValuePair<,>).MakeGenericType(genericArguments[0], genericArguments[1]);
122+
else if (genericArguments.Length == 1)
123+
return genericArguments[0];
124+
else
125+
throw new ArgumentException(nameof(type));
126+
}
127+
128+
public static Type GetUnderlyingElementType(this Expression expression)
129+
=> GetUnderlyingElementType(expression.Type);
130+
105131
private class AssemblyResolver : IAssemblyResolver
106132
{
107133
private List<Assembly> _assemblides;

AutoMapper.OData.EFCore.Tests/AirVinylData/AirVinylDatabaseInitializer.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,10 +302,16 @@ public static void SeedDatabase(AirVinylDbContext context)
302302

303303
context.DynamicVinylRecordProperties.Add(new DynamicProperty()
304304
{
305-
VinylRecordId = vinylRecords.First(r => r.Title == "Nevermind").VinylRecordId,//1,
305+
VinylRecordId = vinylRecords.First(r => r.Title == "Nevermind").VinylRecordId,//1
306306
Key = "Publisher",
307307
Value = "Geffen"
308308
});
309+
context.DynamicVinylRecordProperties.Add(new DynamicProperty()
310+
{
311+
VinylRecordId = vinylRecords.First(r => r.Title == "Nevermind").VinylRecordId,//1
312+
Key = "SomeData",
313+
Value = new { TestProp = "value" }
314+
});
309315
context.SaveChanges();
310316

311317
context.DoorManufacturers.AddRange(
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace AutoMapper.OData.EFCore.Tests.AirVinylModel
2+
{
3+
public class VinylLinkModel
4+
{
5+
public string Href { get; set; }
6+
}
7+
}

AutoMapper.OData.EFCore.Tests/AirVinylModel/VinylRecordModel.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,22 @@ public class VinylRecordModel
3333
= new List<DynamicPropertyModel>();
3434

3535
public IDictionary<string, object> Properties { get; set; }
36+
37+
private Dictionary<string, VinylLinkModel> _links;
38+
public IDictionary<string, VinylLinkModel> Links {
39+
get
40+
{
41+
if (_links is null)
42+
{
43+
_links = new Dictionary<string, VinylLinkModel>()
44+
{
45+
{ "buyingLink", new VinylLinkModel { Href = $"http://test/buy/{VinylRecordId}" } },
46+
{ "reviewLink", new VinylLinkModel { Href = $"http://test/review/{VinylRecordId}" } }
47+
};
48+
}
49+
50+
return _links;
51+
}
52+
}
3653
}
3754
}

AutoMapper.OData.EFCore.Tests/ExpansionTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using AutoMapper.AspNet.OData;
22
using AutoMapper.OData.EFCore.Tests.AirVinylData;
33
using AutoMapper.OData.EFCore.Tests.AirVinylModel;
4+
using LogicBuilder.Expressions.Utils;
45
using Microsoft.AspNetCore.Builder;
56
using Microsoft.AspNetCore.OData;
67
using Microsoft.AspNetCore.OData.Query;
@@ -42,6 +43,28 @@ void Test(ICollection<PersonModel> collection)
4243
}
4344
}
4445

46+
[Fact]
47+
public async Task GetVinylRecordsExpandsComplexTypesByDefault()
48+
{
49+
string query = "/vinylrecordmodel";
50+
Test(await GetAsync<VinylRecordModel, VinylRecord>(query));
51+
52+
void Test(ICollection<VinylRecordModel> collection)
53+
{
54+
Assert.True(collection.Count > 0);
55+
56+
//Navigation properties
57+
Assert.True(collection.All(vinyl => vinyl.Person is null));
58+
Assert.True(collection.All(vinyl => vinyl.PressingDetail is null));
59+
60+
//Complex types
61+
Assert.Contains(collection, vinyl => vinyl.Properties.Count != 0);
62+
Assert.Contains(collection, vinyl => vinyl.Properties.Any(p => !p.Value.GetType().IsLiteralType()));
63+
Assert.Contains(collection, vinyl => vinyl.DynamicVinylRecordProperties.Count != 0);
64+
Assert.Contains(collection, vinyl => vinyl.Links.Count != 0);
65+
}
66+
}
67+
4568
[Fact]
4669
public async Task GetRecordStoresExpandsComplexTypesByDefault()
4770
{

AutoMapper.OData.EFCore.Tests/Mappings/AirVinylMappings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public AirVinylMappings()
3030
CreateMap<SpecializedRecordStore, SpecializedRecordStoreModel>()
3131
.ForAllMembers(o => o.ExplicitExpansion());
3232
CreateMap<VinylRecord, VinylRecordModel>()
33+
.ForMember(dest => dest.Links, o => o.Ignore())
3334
.ForAllMembers(o => o.ExplicitExpansion());
3435
}
3536
}

0 commit comments

Comments
 (0)