Skip to content

Commit 54a8c4e

Browse files
authored
Expanding complex types by default. (#229)
* Expanding complex types by default.
1 parent 6a68bac commit 54a8c4e

36 files changed

+1603
-56
lines changed

AutoMapper.AspNetCore.OData.EF6/AutoMapper.AspNetCore.OData.EF6.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\Constants.cs" Link="Constants.cs" />
3333
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\CustomMethodCache.cs" Link="CustomMethodCache.cs" />
3434
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\EdmTypeStructure.cs" Link="EdmTypeStructure.cs" />
35+
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\Expansions\DefaultExpansionsBuilder.cs" Link="Expansions\DefaultExpansionsBuilder.cs" />
36+
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\Expansions\Expansion.cs" Link="Expansions\Expansion.cs" />
37+
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\Expansions\ExpansionsHelper.cs" Link="Expansions\ExpansionsHelper.cs" />
3538
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\FilterHelper.cs" Link="FilterHelper.cs" />
3639
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\LinqExtensions.cs" Link="LinqExtensions.cs" />
3740
<Compile Include="..\AutoMapper.AspNetCore.OData.EFCore\ODataQueryContextExtentions.cs" Link="ODataQueryContextExtentions.cs" />
@@ -74,6 +77,7 @@
7477
</ItemGroup>
7578

7679
<ItemGroup>
80+
<Folder Include="Expansions\" />
7781
<Folder Include="Visitors\" />
7882
<Folder Include="Properties\" />
7983
</ItemGroup>

AutoMapper.AspNetCore.OData.EF6/QueryableExtensions.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using AutoMapper.Extensions.ExpressionMapping;
2-
using LogicBuilder.Expressions.Utils.Expansions;
32
using Microsoft.AspNetCore.OData.Query;
43
using System;
54
using System.Collections.Generic;
@@ -186,16 +185,18 @@ private static IQueryable<TModel> GetQueryable<TModel, TData>(this IQueryable<TD
186185
where TModel : class
187186
{
188187
var expansions = options.SelectExpand.GetExpansions(typeof(TModel));
189-
188+
Expansions.ExpansionsHelper helper = new Expansions.ExpansionsHelper(options.Context);
189+
IEnumerable<Expression<Func<TModel, object>>> linqExpansions = helper.BuildExplicitExpansions<TModel>
190+
(
191+
expansions.Select(list => new List<Expansions.Expansion>(list)),
192+
options.SelectExpand.GetSelects()
193+
);
190194
return query.GetQuery
191195
(
192196
mapper,
193197
filter,
194198
options.GetQueryableExpression(querySettings?.ODataSettings),
195-
expansions
196-
.Select(list => new List<Expansion>(list))
197-
.BuildIncludes<TModel>(options.SelectExpand.GetSelects())
198-
.ToList(),
199+
linqExpansions,
199200
querySettings?.ProjectionSettings
200201
).UpdateQueryableExpression(expansions, options.Context, mapper);
201202
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using Microsoft.AspNetCore.OData.Query;
2+
using Microsoft.OData.Edm;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Globalization;
6+
using System.Linq;
7+
using System.Linq.Expressions;
8+
using System.Reflection;
9+
10+
namespace AutoMapper.AspNet.OData.Expansions
11+
{
12+
internal class DefaultExpansionsBuilder(ODataQueryContext context)
13+
{
14+
private readonly ODataQueryContext context = context;
15+
const BindingFlags instanceBindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
16+
17+
public LambdaExpression[] Build(ParameterExpression param, Expression parentBody)
18+
{
19+
List<LambdaExpression> memberSelectors = [];
20+
Build(memberSelectors, param, parentBody);
21+
return [.. memberSelectors];
22+
}
23+
24+
public void Build(List<LambdaExpression> memberSelectors, ParameterExpression param, Expression parentBody)
25+
{
26+
HashSet<string> navigationProperties = GetNavigationProperties(parentBody.Type);
27+
MemberInfo[] infos = GetMemberInfos(parentBody.Type).Where
28+
(
29+
info => (info.MemberType == MemberTypes.Field || info.MemberType == MemberTypes.Property)
30+
&& (!navigationProperties.Contains(info.Name))
31+
).ToArray();
32+
33+
foreach (MemberInfo info in infos)
34+
{
35+
Type memberType = LogicBuilder.Expressions.Utils.TypeExtensions.GetMemberType(info);
36+
Expression selector = Expression.MakeMemberAccess(parentBody, info);
37+
if (selector.Type.IsValueType)
38+
selector = Expression.Convert(selector, typeof(object));
39+
40+
memberSelectors.Add
41+
(
42+
Expression.Lambda
43+
(
44+
typeof(Func<,>).MakeGenericType([param.Type, typeof(object)]),
45+
selector,
46+
param
47+
)
48+
);
49+
50+
if (LogicBuilder.Expressions.Utils.TypeExtensions.IsLiteralType(memberType))
51+
continue;
52+
53+
if (LogicBuilder.Expressions.Utils.TypeExtensions.IsList(memberType)
54+
&& LogicBuilder.Expressions.Utils.TypeExtensions.IsLiteralType
55+
(
56+
LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(memberType)
57+
) == false)
58+
{
59+
List<LambdaExpression> childMemberSelectors = [];
60+
ParameterExpression childParam = Expression.Parameter
61+
(
62+
LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(memberType),
63+
GetChildParameterName(param.Name)
64+
);
65+
66+
Build(childMemberSelectors, childParam, childParam);
67+
childMemberSelectors.ForEach(childSelector =>
68+
{
69+
memberSelectors.Add
70+
(
71+
Expression.Lambda
72+
(
73+
typeof(Func<,>).MakeGenericType([param.Type, typeof(object)]),
74+
Expression.Call
75+
(
76+
typeof(Enumerable),
77+
"Select",
78+
[LogicBuilder.Expressions.Utils.TypeExtensions.GetUnderlyingElementType(selector), typeof(object)],
79+
selector,
80+
childSelector
81+
),
82+
param
83+
)
84+
);
85+
});
86+
}
87+
else if (LogicBuilder.Expressions.Utils.TypeExtensions.IsList(memberType) == false)
88+
{
89+
Build(memberSelectors, param, selector);
90+
}
91+
}
92+
}
93+
94+
private static string GetChildParameterName(string currentParameterName)
95+
{
96+
string lastChar = currentParameterName.Substring(currentParameterName.Length - 1);
97+
if (short.TryParse(lastChar, out short lastCharShort))
98+
{
99+
return string.Concat
100+
(
101+
currentParameterName.Substring(0, currentParameterName.Length - 1),
102+
(lastCharShort++).ToString(CultureInfo.CurrentCulture)
103+
);
104+
}
105+
else
106+
{
107+
return currentParameterName += "0";
108+
}
109+
}
110+
111+
private static MemberInfo[] GetMemberInfos(Type parentType)
112+
=> parentType.GetMembers(instanceBindingFlags).Select
113+
(
114+
member =>
115+
{
116+
if (member.DeclaringType != parentType)
117+
return member.DeclaringType.GetMember(member.Name, instanceBindingFlags).FirstOrDefault();
118+
119+
return member;
120+
}
121+
).ToArray();
122+
123+
private HashSet<string> GetNavigationProperties(Type type)
124+
{
125+
IEdmEntityType entityType = context.Model.SchemaElements.OfType<IEdmEntityType>()
126+
.SingleOrDefault(e => GetClrTypeFromEntityType(e).FullName == type.FullName);
127+
if (entityType != null)
128+
return entityType.NavigationProperties().Select(p => p.Name).ToHashSet();
129+
130+
IEdmComplexType complexType = context.Model.SchemaElements.OfType<IEdmComplexType>()
131+
.SingleOrDefault(e => GetClrTypeFromComplexType(e).FullName == type.FullName);
132+
if (complexType != null)
133+
return complexType.NavigationProperties().Select(p => p.Name).ToHashSet();
134+
135+
return [];
136+
137+
Type GetClrTypeFromEntityType(IEdmEntityType entityType)
138+
=> TypeExtensions.GetClrType(new EdmEntityTypeReference(entityType, true), context.Model, TypeExtensions.GetEdmToClrTypeMappings());
139+
140+
Type GetClrTypeFromComplexType(IEdmComplexType complexType)
141+
=> TypeExtensions.GetClrType(new EdmComplexTypeReference(complexType, true), context.Model, TypeExtensions.GetEdmToClrTypeMappings());
142+
}
143+
}
144+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace AutoMapper.AspNet.OData.Expansions
5+
{
6+
abstract public class Expansion
7+
{
8+
public string MemberName { get; set; }
9+
public Type MemberType { get; set; }
10+
public Type ParentType { get; set; }
11+
public List<string> Selects { get; set; }
12+
}
13+
}

0 commit comments

Comments
 (0)