4
4
using System . Globalization ;
5
5
using System . Linq . Expressions ;
6
6
using System . Reflection ;
7
+ using System . Reflection . Emit ;
8
+ using AutoMapper ;
7
9
using AutoMapper . Internal ;
10
+ using AutoMapper . QueryableExtensions ;
8
11
using Microsoft . EntityFrameworkCore ;
9
12
using Microsoft . EntityFrameworkCore . Infrastructure ;
10
13
using NorthwindCRUD ;
16
19
[ SuppressMessage ( "StyleCop.CSharp.SpacingRules" , "SA1025:Code should not contain multiple whitespace in a row" , Justification = "..." ) ]
17
20
public static class QueryExecutor
18
21
{
19
- public static TEntity [ ] Run < TEntity > ( this IQueryable < TEntity > source , Query ? query )
22
+ public static object [ ] Run < TEntity > ( this IQueryable < TEntity > source , Query ? query )
23
+ {
24
+ return source . Run < TEntity , TEntity > ( query ) ;
25
+ }
26
+
27
+ public static object [ ] Run < TSource , TTarget > ( this IQueryable < TSource > source , Query ? query , IMapper ? mapper = null )
20
28
{
21
29
var infrastructure = source as IInfrastructure < IServiceProvider > ;
22
30
var serviceProvider = infrastructure ! . Instance ;
23
31
var currentDbContext = serviceProvider . GetService ( typeof ( ICurrentDbContext ) ) as ICurrentDbContext ;
24
32
var db = currentDbContext ! . Context as DataContext ;
25
- return db is not null ? BuildQuery ( db , source , query ) . ToArray ( ) : Array . Empty < TEntity > ( ) ;
33
+ return db is not null ? BuildQuery < TSource , TTarget > ( db , source , query , mapper ) . ToArray ( ) : Array . Empty < object > ( ) ;
26
34
}
27
35
28
- private static IQueryable < TEntity > BuildQuery < TEntity > ( DataContext db , IQueryable < TEntity > source , Query ? query )
36
+ private static IQueryable < object > BuildQuery < TSource , TTarget > ( DataContext db , IQueryable < TSource > source , Query ? query , IMapper ? mapper = null )
29
37
{
30
38
if ( query is null )
31
39
{
@@ -34,14 +42,26 @@ private static IQueryable<TEntity> BuildQuery<TEntity>(DataContext db, IQueryabl
34
42
35
43
var filterExpression = BuildExpression ( db , source , query . FilteringOperands , query . Operator ) ;
36
44
var filteredQuery = source . Where ( filterExpression ) ;
37
- if ( query . ReturnFields != null && query . ReturnFields . Any ( ) )
45
+ if ( query . ReturnFields != null && query . ReturnFields . Any ( ) && ! query . ReturnFields . Contains ( "*" ) )
46
+ {
47
+ if ( mapper is not null )
48
+ {
49
+ var projectionExpression = BuildProjectionExpression < TTarget , TTarget > ( query . ReturnFields ) ;
50
+ return filteredQuery . ProjectTo < TTarget > ( mapper . ConfigurationProvider ) . Select ( projectionExpression ) ;
51
+ }
52
+ else
53
+ {
54
+ var projectionExpression = BuildProjectionExpression < TSource , TTarget > ( query . ReturnFields ) ;
55
+ return filteredQuery . Select ( projectionExpression ) ;
56
+ }
57
+ }
58
+ else if ( mapper is not null )
38
59
{
39
- var projectionExpression = BuildProjectionExpression < TEntity > ( query . ReturnFields ) ;
40
- return filteredQuery . Select ( projectionExpression ) . Cast < TEntity > ( ) ;
60
+ return ( IQueryable < object > ) filteredQuery . ProjectTo < TTarget > ( mapper . ConfigurationProvider ) ;
41
61
}
42
62
else
43
63
{
44
- return filteredQuery ;
64
+ return filteredQuery . Cast < object > ( ) ;
45
65
}
46
66
}
47
67
@@ -71,7 +91,7 @@ private static Expression<Func<TEntity, bool>> BuildExpression<TEntity>(DataCont
71
91
72
92
private static Expression BuildConditionExpression < TEntity > ( DataContext db , IQueryable < TEntity > source , QueryFilter filter , ParameterExpression parameter )
73
93
{
74
- if ( filter . FieldName is not null && filter . IgnoreCase is not null && filter . Condition is not null )
94
+ if ( filter . FieldName is not null && filter . Condition is not null )
75
95
{
76
96
var property = source . ElementType . GetProperty ( filter . FieldName , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance )
77
97
?? throw new InvalidOperationException ( $ "Property '{ filter . FieldName } ' not found on type '{ source . ElementType } '") ;
@@ -117,7 +137,7 @@ private static Expression BuildConditionExpression<TEntity>(DataContext db, IQue
117
137
"false" => Expression . Equal ( field , Expression . Constant ( false ) ) ,
118
138
_ => throw new NotImplementedException ( "Not implemented" ) ,
119
139
} ;
120
- if ( filter . IgnoreCase . Value && field . Type == typeof ( string ) )
140
+ if ( filter . IgnoreCase == true && field . Type == typeof ( string ) )
121
141
{
122
142
// TODO: Implement case-insensitive comparison
123
143
}
@@ -238,6 +258,12 @@ private static Expression GetSearchValue(dynamic? value, Type targetType)
238
258
}
239
259
240
260
var nonNullableType = Nullable . GetUnderlyingType ( targetType ) ?? targetType ;
261
+
262
+ if ( nonNullableType . IsEnum && value is string )
263
+ {
264
+ return Expression . Constant ( Enum . Parse ( nonNullableType , value ) ) ;
265
+ }
266
+
241
267
var convertedValue = Convert . ChangeType ( value , nonNullableType , CultureInfo . InvariantCulture ) ;
242
268
return Expression . Constant ( convertedValue , targetType ) ;
243
269
}
@@ -247,17 +273,69 @@ private static Expression GetEmptyValue(Type targetType)
247
273
return Expression . Constant ( targetType == typeof ( string ) ? string . Empty : targetType . GetDefaultValue ( ) ) ;
248
274
}
249
275
250
- private static Expression < Func < TEntity , dynamic > > BuildProjectionExpression < TEntity > ( string [ ] returnFields )
276
+ private static Expression < Func < TSource , object > > BuildProjectionExpression < TSource , TTarget > ( string [ ] returnFields )
251
277
{
252
- var parameter = Expression . Parameter ( typeof ( TEntity ) , "entity" ) ;
253
- var bindings = returnFields . Select ( field =>
278
+ var tagetEntityType = typeof ( TTarget ) ;
279
+ var dbEntityType = typeof ( TSource ) ;
280
+
281
+ // Create the anonymous projection type
282
+ var projectionType = CreateProjectionType ( tagetEntityType , returnFields ) ;
283
+
284
+ var parameter = Expression . Parameter ( dbEntityType , "entity" ) ;
285
+
286
+ var bindings = returnFields . Select ( fieldName =>
254
287
{
255
- var property = typeof ( TEntity ) . GetProperty ( field , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance ) ?? throw new InvalidOperationException ( $ "Property '{ field } ' not found on type '{ typeof ( TEntity ) } '") ;
288
+ var property = dbEntityType . GetProperty ( fieldName , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance ) ?? throw new InvalidOperationException ( $ "Property '{ fieldName } ' not found on type '{ dbEntityType } ") ;
289
+ var field = projectionType . GetField ( fieldName , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance ) ?? throw new InvalidOperationException ( $ "Property '{ fieldName } ' not found on type '{ projectionType } '") ;
256
290
var propertyAccess = Expression . Property ( parameter , property ) ;
257
- return Expression . Bind ( property , propertyAccess ) ;
291
+ return Expression . Bind ( field , propertyAccess ) ;
258
292
} ) . ToArray ( ) ;
259
293
260
- var body = Expression . MemberInit ( Expression . New ( typeof ( TEntity ) ) , bindings ) ;
261
- return Expression . Lambda < Func < TEntity , dynamic > > ( body , parameter ) ;
294
+ // Get Microsoft.CSharp assembly where anonymous types are defined
295
+ var dynamicAssembly = typeof ( Microsoft . CSharp . RuntimeBinder . Binder ) . Assembly ;
296
+
297
+ var createExpression = Expression . MemberInit ( Expression . New ( projectionType ) , bindings ) ;
298
+
299
+ return Expression . Lambda < Func < TSource , object > > ( createExpression , parameter ) ;
262
300
}
301
+
302
+ private static Type CreateProjectionType ( Type input , string [ ] fields )
303
+ {
304
+ var fieldsList = fields . Select ( field =>
305
+ {
306
+ var property = input . GetProperty ( field , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance )
307
+ ?? throw new InvalidOperationException ( $ "Property '{ field } ' not found on type '{ input } '") ;
308
+ return new Field
309
+ {
310
+ Name = property . Name ,
311
+ Type = property . GetMemberType ( ) ,
312
+ } ;
313
+ } ) . ToList ( ) ;
314
+
315
+ var name = input . Name + "Projection" ;
316
+ return CreateAnonymousType ( name , fieldsList ) ;
317
+ }
318
+
319
+ private static Type CreateAnonymousType ( string name , ICollection < Field > fields )
320
+ {
321
+ AssemblyName dynamicAssemblyName = new AssemblyName ( "TempAssembly" ) ;
322
+ AssemblyBuilder dynamicAssembly = AssemblyBuilder . DefineDynamicAssembly ( dynamicAssemblyName , AssemblyBuilderAccess . Run ) ;
323
+ ModuleBuilder dynamicModule = dynamicAssembly . DefineDynamicModule ( "TempAssembly" ) ;
324
+
325
+ TypeBuilder dynamicAnonymousType = dynamicModule . DefineType ( name , TypeAttributes . Public ) ;
326
+
327
+ foreach ( var field in fields )
328
+ {
329
+ dynamicAnonymousType . DefineField ( field . Name , field . Type , FieldAttributes . Public ) ;
330
+ }
331
+
332
+ return dynamicAnonymousType . CreateType ( ) ! ;
333
+ }
334
+ }
335
+
336
+ internal class Field
337
+ {
338
+ public string Name { get ; set ; }
339
+
340
+ public Type Type { get ; set ; }
263
341
}
0 commit comments