44using System . Globalization ;
55using System . Linq . Expressions ;
66using System . Reflection ;
7+ using System . Reflection . Emit ;
8+ using AutoMapper ;
79using AutoMapper . Internal ;
10+ using AutoMapper . QueryableExtensions ;
811using Microsoft . EntityFrameworkCore ;
912using Microsoft . EntityFrameworkCore . Infrastructure ;
1013using NorthwindCRUD ;
1619[ SuppressMessage ( "StyleCop.CSharp.SpacingRules" , "SA1025:Code should not contain multiple whitespace in a row" , Justification = "..." ) ]
1720public static class QueryExecutor
1821{
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 )
2028 {
2129 var infrastructure = source as IInfrastructure < IServiceProvider > ;
2230 var serviceProvider = infrastructure ! . Instance ;
2331 var currentDbContext = serviceProvider . GetService ( typeof ( ICurrentDbContext ) ) as ICurrentDbContext ;
2432 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 > ( ) ;
2634 }
2735
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 )
2937 {
3038 if ( query is null )
3139 {
@@ -34,14 +42,26 @@ private static IQueryable<TEntity> BuildQuery<TEntity>(DataContext db, IQueryabl
3442
3543 var filterExpression = BuildExpression ( db , source , query . FilteringOperands , query . Operator ) ;
3644 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 )
3859 {
39- var projectionExpression = BuildProjectionExpression < TEntity > ( query . ReturnFields ) ;
40- return filteredQuery . Select ( projectionExpression ) . Cast < TEntity > ( ) ;
60+ return ( IQueryable < object > ) filteredQuery . ProjectTo < TTarget > ( mapper . ConfigurationProvider ) ;
4161 }
4262 else
4363 {
44- return filteredQuery ;
64+ return filteredQuery . Cast < object > ( ) ;
4565 }
4666 }
4767
@@ -71,7 +91,7 @@ private static Expression<Func<TEntity, bool>> BuildExpression<TEntity>(DataCont
7191
7292 private static Expression BuildConditionExpression < TEntity > ( DataContext db , IQueryable < TEntity > source , QueryFilter filter , ParameterExpression parameter )
7393 {
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 )
7595 {
7696 var property = source . ElementType . GetProperty ( filter . FieldName , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance )
7797 ?? throw new InvalidOperationException ( $ "Property '{ filter . FieldName } ' not found on type '{ source . ElementType } '") ;
@@ -117,7 +137,7 @@ private static Expression BuildConditionExpression<TEntity>(DataContext db, IQue
117137 "false" => Expression . Equal ( field , Expression . Constant ( false ) ) ,
118138 _ => throw new NotImplementedException ( "Not implemented" ) ,
119139 } ;
120- if ( filter . IgnoreCase . Value && field . Type == typeof ( string ) )
140+ if ( filter . IgnoreCase == true && field . Type == typeof ( string ) )
121141 {
122142 // TODO: Implement case-insensitive comparison
123143 }
@@ -238,6 +258,12 @@ private static Expression GetSearchValue(dynamic? value, Type targetType)
238258 }
239259
240260 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+
241267 var convertedValue = Convert . ChangeType ( value , nonNullableType , CultureInfo . InvariantCulture ) ;
242268 return Expression . Constant ( convertedValue , targetType ) ;
243269 }
@@ -247,17 +273,69 @@ private static Expression GetEmptyValue(Type targetType)
247273 return Expression . Constant ( targetType == typeof ( string ) ? string . Empty : targetType . GetDefaultValue ( ) ) ;
248274 }
249275
250- private static Expression < Func < TEntity , dynamic > > BuildProjectionExpression < TEntity > ( string [ ] returnFields )
276+ private static Expression < Func < TSource , object > > BuildProjectionExpression < TSource , TTarget > ( string [ ] returnFields )
251277 {
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 =>
254287 {
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 } '") ;
256290 var propertyAccess = Expression . Property ( parameter , property ) ;
257- return Expression . Bind ( property , propertyAccess ) ;
291+ return Expression . Bind ( field , propertyAccess ) ;
258292 } ) . ToArray ( ) ;
259293
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 ) ;
262300 }
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 ; }
263341}
0 commit comments