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 {
@@ -36,12 +44,24 @@ private static IQueryable<TEntity> BuildQuery<TEntity>(DataContext db, IQueryabl
3644 var filteredQuery = source . Where ( filterExpression ) ;
3745 if ( query . ReturnFields != null && query . ReturnFields . Any ( ) )
3846 {
39- var projectionExpression = BuildProjectionExpression < TEntity > ( query . ReturnFields ) ;
40- return filteredQuery . Select ( projectionExpression ) . Cast < TEntity > ( ) ;
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 )
59+ {
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 }
@@ -247,17 +267,69 @@ private static Expression GetEmptyValue(Type targetType)
247267 return Expression . Constant ( targetType == typeof ( string ) ? string . Empty : targetType . GetDefaultValue ( ) ) ;
248268 }
249269
250- private static Expression < Func < TEntity , dynamic > > BuildProjectionExpression < TEntity > ( string [ ] returnFields )
270+ private static Expression < Func < TSource , object > > BuildProjectionExpression < TSource , TTarget > ( string [ ] returnFields )
251271 {
252- var parameter = Expression . Parameter ( typeof ( TEntity ) , "entity" ) ;
253- var bindings = returnFields . Select ( field =>
272+ var tagetEntityType = typeof ( TTarget ) ;
273+ var dbEntityType = typeof ( TSource ) ;
274+
275+ // Create the anonymous projection type
276+ var projectionType = CreateProjectionType ( tagetEntityType , returnFields ) ;
277+
278+ var parameter = Expression . Parameter ( dbEntityType , "entity" ) ;
279+
280+ var bindings = returnFields . Select ( fieldName =>
254281 {
255- var property = typeof ( TEntity ) . GetProperty ( field , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance ) ?? throw new InvalidOperationException ( $ "Property '{ field } ' not found on type '{ typeof ( TEntity ) } '") ;
282+ var property = dbEntityType . GetProperty ( fieldName , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance ) ?? throw new InvalidOperationException ( $ "Property '{ fieldName } ' not found on type '{ dbEntityType } ") ;
283+ var field = projectionType . GetField ( fieldName , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance ) ?? throw new InvalidOperationException ( $ "Property '{ fieldName } ' not found on type '{ projectionType } '") ;
256284 var propertyAccess = Expression . Property ( parameter , property ) ;
257- return Expression . Bind ( property , propertyAccess ) ;
285+ return Expression . Bind ( field , propertyAccess ) ;
258286 } ) . ToArray ( ) ;
259287
260- var body = Expression . MemberInit ( Expression . New ( typeof ( TEntity ) ) , bindings ) ;
261- return Expression . Lambda < Func < TEntity , dynamic > > ( body , parameter ) ;
288+ // Get Microsoft.CSharp assembly where anonymous types are defined
289+ var dynamicAssembly = typeof ( Microsoft . CSharp . RuntimeBinder . Binder ) . Assembly ;
290+
291+ var createExpression = Expression . MemberInit ( Expression . New ( projectionType ) , bindings ) ;
292+
293+ return Expression . Lambda < Func < TSource , object > > ( createExpression , parameter ) ;
262294 }
295+
296+ private static Type CreateProjectionType ( Type input , string [ ] fields )
297+ {
298+ var fieldsList = fields . Select ( field =>
299+ {
300+ var property = input . GetProperty ( field , BindingFlags . IgnoreCase | BindingFlags . Public | BindingFlags . Instance )
301+ ?? throw new InvalidOperationException ( $ "Property '{ field } ' not found on type '{ input } '") ;
302+ return new Field
303+ {
304+ Name = property . Name ,
305+ Type = property . GetMemberType ( ) ,
306+ } ;
307+ } ) . ToList ( ) ;
308+
309+ var name = input . Name + "Projection" ;
310+ return CreateAnonymousType ( name , fieldsList ) ;
311+ }
312+
313+ private static Type CreateAnonymousType ( string name , ICollection < Field > fields )
314+ {
315+ AssemblyName dynamicAssemblyName = new AssemblyName ( "TempAssembly" ) ;
316+ AssemblyBuilder dynamicAssembly = AssemblyBuilder . DefineDynamicAssembly ( dynamicAssemblyName , AssemblyBuilderAccess . Run ) ;
317+ ModuleBuilder dynamicModule = dynamicAssembly . DefineDynamicModule ( "TempAssembly" ) ;
318+
319+ TypeBuilder dynamicAnonymousType = dynamicModule . DefineType ( name , TypeAttributes . Public ) ;
320+
321+ foreach ( var field in fields )
322+ {
323+ dynamicAnonymousType . DefineField ( field . Name , field . Type , FieldAttributes . Public ) ;
324+ }
325+
326+ return dynamicAnonymousType . CreateType ( ) ! ;
327+ }
328+ }
329+
330+ internal class Field
331+ {
332+ public string Name { get ; set ; }
333+
334+ public Type Type { get ; set ; }
263335}
0 commit comments