1+ #if EFCORE
2+ using System ;
3+ using System . Collections . Generic ;
4+ using System . Linq ;
5+ using System . Linq . Expressions ;
6+ using System . Reflection ;
7+ using Microsoft . EntityFrameworkCore ;
8+ using Microsoft . EntityFrameworkCore . Metadata ;
9+
10+ namespace Z . EntityFramework . Plus
11+ {
12+ /// <summary>A query include optimized by path.</summary>
13+ public static class QueryIncludeOptimizedByPath
14+ {
15+ /// <summary>Include optimized by path.</summary>
16+ /// <typeparam name="T">Generic type parameter.</typeparam>
17+ /// <param name="query">The query.</param>
18+ /// <param name="navigationPath">Full pathname of the navigation file.</param>
19+ /// <returns>An IQueryable<T></returns>
20+ public static IQueryable < T > IncludeOptimizedByPath < T > ( IQueryable < T > query , string navigationPath )
21+ {
22+ var elementType = typeof ( T ) ;
23+ var paths = navigationPath . Split ( '.' ) ;
24+
25+ var context = query . IsInMemoryQueryContext ( ) ? null : query . GetDbContext ( ) ;
26+
27+ // CREATE expression x => x.Right
28+ var expression = CreateLambdaExpression ( elementType , paths , 0 , context ) ;
29+
30+ var method = typeof ( QueryIncludeOptimizedExtensions )
31+ . GetMethod ( "IncludeOptimizedSingle" , BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Static )
32+ . MakeGenericMethod ( typeof ( T ) , expression . Type . GetGenericArguments ( ) [ 1 ] ) ;
33+
34+ query = ( IQueryable < T > ) method . Invoke ( null , new object [ ] { query , expression } ) ;
35+
36+ return query ;
37+ }
38+
39+ /// <summary>Creates lambda expression.</summary>
40+ /// <param name="parameterType">Type of the parameter.</param>
41+ /// <param name="paths">The paths.</param>
42+ /// <param name="currentIndex">The current index.</param>
43+ /// <returns>The new lambda expression.</returns>
44+ public static Expression CreateLambdaExpression ( Type parameterType , string [ ] paths , int currentIndex , DbContext context )
45+ {
46+ // CREATE expression [x => x.Right]
47+
48+ // ADD parameter [x =>]
49+ var parameter = Expression . Parameter ( parameterType ) ;
50+ Expression expression = parameter ;
51+
52+ // ADD property [x.Right]
53+ expression = AppendPropertyPath ( expression , paths , currentIndex , context ) ;
54+
55+ // GET function generic type
56+ var funcGenericType = typeof ( Func < , > ) . MakeGenericType ( parameterType , expression . Type ) ;
57+
58+ // GET lambda method
59+ var lambdaMethod = typeof ( Expression ) . GetMethods ( )
60+ . Single ( x => x . Name == "Lambda"
61+ && x . IsGenericMethod
62+ && x . GetParameters ( ) . Length == 2
63+ && ! x . GetParameters ( ) [ 1 ] . ParameterType . IsArray )
64+ . MakeGenericMethod ( funcGenericType ) ;
65+
66+ // CREATE lambda expression
67+ expression = ( Expression ) lambdaMethod . Invoke ( null , new object [ ] { expression , new List < ParameterExpression > { parameter } } ) ;
68+
69+ return expression ;
70+ }
71+
72+ /// <summary>Appends a path.</summary>
73+ /// <param name="expression">The expression.</param>
74+ /// <param name="paths">The paths.</param>
75+ /// <param name="currentIndex">The current index.</param>
76+ /// <returns>An Expression.</returns>
77+ public static Expression AppendPath ( Expression expression , string [ ] paths , int currentIndex , DbContext context )
78+ {
79+ expression = expression . Type . GetGenericArguments ( ) . Length == 0 ?
80+ AppendPropertyPath ( expression , paths , currentIndex , context ) :
81+ AppendSelectPath ( expression , paths , currentIndex , context ) ;
82+
83+ return expression ;
84+ }
85+
86+ /// <summary>Appends a property path.</summary>
87+ /// <exception cref="Exception">Thrown when an exception error condition occurs.</exception>
88+ /// <param name="expression">The expression.</param>
89+ /// <param name="paths">The paths.</param>
90+ /// <param name="currentIndex">The current index.</param>
91+ /// <returns>An Expression.</returns>
92+ public static Expression AppendPropertyPath ( Expression expression , string [ ] paths , int currentIndex , DbContext context )
93+ {
94+ // APPEND [x.PropertyName]
95+ var elementType = expression . Type ;
96+ var property = elementType . GetProperty ( paths [ currentIndex ] , BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance ) ;
97+
98+ // ENSURE property exists
99+ if ( property == null )
100+ {
101+ // Try Again with case insensitive
102+ var properties = elementType . GetProperties ( BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance )
103+ . Where ( x => x . Name . ToLowerInvariant ( ) == paths [ currentIndex ] . ToLowerInvariant ( ) ) . ToList ( ) ;
104+
105+ if ( properties . Count == 1 )
106+ {
107+ property = properties [ 0 ] ;
108+ }
109+
110+ // last try with GetDerivedTypes
111+ if ( property == null && context != null )
112+ {
113+ var entityType = context . Model . FindEntityType ( elementType ) ;
114+
115+ foreach ( var entity in entityType . GetDerivedTypes ( ) )
116+ {
117+ if ( entity . ClrType != null )
118+ {
119+ properties = entity . ClrType . GetProperties ( BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . Instance )
120+ . Where ( x => x . Name . ToLowerInvariant ( ) == paths [ currentIndex ] . ToLowerInvariant ( ) ) . ToList ( ) ;
121+
122+ if ( properties . Count == 1 )
123+ {
124+ property = properties [ 0 ] ;
125+ expression = Expression . Convert ( expression , entity . ClrType ) ;
126+ break ;
127+ }
128+ }
129+
130+ }
131+
132+ if ( property == null )
133+ {
134+ throw new Exception ( string . Format ( ExceptionMessage . QueryIncludeOptimized_ByPath_MissingPath , elementType . FullName , paths [ currentIndex ] ) ) ;
135+ }
136+ }
137+ }
138+
139+ expression = Expression . Property ( expression , property ) ;
140+
141+ // APPEND path childs
142+ currentIndex ++ ;
143+ if ( currentIndex < paths . Length )
144+ {
145+ expression = AppendPath ( expression , paths , currentIndex , context ) ;
146+ }
147+
148+ return expression ;
149+ }
150+
151+ /// <summary>Appends a select path.</summary>
152+ /// <param name="expression">The expression.</param>
153+ /// <param name="paths">The paths.</param>
154+ /// <param name="currentIndex">The current index.</param>
155+ /// <returns>An Expression.</returns>
156+ public static Expression AppendSelectPath ( Expression expression , string [ ] paths , int currentIndex , DbContext context )
157+ {
158+ // APPEND x => x.Rights[.Select(y => y.Right)]
159+ var elementType = expression . Type . GetGenericArguments ( ) [ 0 ] ;
160+
161+ // CREATE lambda expression [y => y.Right]
162+ var lambdaExpression = CreateLambdaExpression ( elementType , paths , currentIndex , context ) ;
163+
164+ // APPEND Method [.Select(y => y.Right)]
165+ var selectMethod = typeof ( Enumerable ) . GetMethods ( )
166+ . Single ( x => x . Name == "Select"
167+ && x . GetParameters ( ) . Length == 2
168+ && x . GetParameters ( ) [ 1 ] . ParameterType . GetGenericArguments ( ) . Length == 2 )
169+ . MakeGenericMethod ( elementType , lambdaExpression . Type . GetGenericArguments ( ) [ 1 ] ) ;
170+ expression = Expression . Call ( null , selectMethod , expression , lambdaExpression ) ;
171+
172+ return expression ;
173+ }
174+ }
175+ }
176+ #endif
0 commit comments