@@ -2931,20 +2931,29 @@ public IEnumerable<T> ExecuteDeferredQuery<T> (TableMapping map)
2931
2931
var stmt = Prepare ( ) ;
2932
2932
try {
2933
2933
var cols = new TableMapping . Column [ SQLite3 . ColumnCount ( stmt ) ] ;
2934
+ var fastColumnSetters = new Action < T , Sqlite3Statement , int > [ SQLite3 . ColumnCount ( stmt ) ] ;
2934
2935
2935
2936
for ( int i = 0 ; i < cols . Length ; i ++ ) {
2936
2937
var name = SQLite3 . ColumnName16 ( stmt , i ) ;
2937
2938
cols [ i ] = map . FindColumn ( name ) ;
2939
+ if ( cols [ i ] != null )
2940
+ fastColumnSetters [ i ] = FastColumnSetter . GetFastSetter < T > ( _conn , cols [ i ] ) ;
2938
2941
}
2939
2942
2940
2943
while ( SQLite3 . Step ( stmt ) == SQLite3 . Result . Row ) {
2941
2944
var obj = Activator . CreateInstance ( map . MappedType ) ;
2942
2945
for ( int i = 0 ; i < cols . Length ; i ++ ) {
2943
2946
if ( cols [ i ] == null )
2944
2947
continue ;
2945
- var colType = SQLite3 . ColumnType ( stmt , i ) ;
2946
- var val = ReadCol ( stmt , i , colType , cols [ i ] . ColumnType ) ;
2947
- cols [ i ] . SetValue ( obj , val ) ;
2948
+
2949
+ if ( fastColumnSetters [ i ] != null ) {
2950
+ fastColumnSetters [ i ] . Invoke ( ( T ) obj , stmt , i ) ;
2951
+ }
2952
+ else {
2953
+ var colType = SQLite3 . ColumnType ( stmt , i ) ;
2954
+ var val = ReadCol ( stmt , i , colType , cols [ i ] . ColumnType ) ;
2955
+ cols [ i ] . SetValue ( obj , val ) ;
2956
+ }
2948
2957
}
2949
2958
OnInstanceCreated ( obj ) ;
2950
2959
yield return ( T ) obj ;
@@ -3263,6 +3272,227 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr
3263
3272
}
3264
3273
}
3265
3274
3275
+ internal class FastColumnSetter
3276
+ {
3277
+ /// <summary>
3278
+ /// Creates a delegate that can be used to quickly set object members from query columns.
3279
+ ///
3280
+ /// Note that this frontloads the slow reflection-based type checking for columns to only happen once at the beginning of a query,
3281
+ /// and then afterwards each row of the query can invoke the delegate returned by this function to get much better performance (up to 10x speed boost, depending on query size and platform).
3282
+ /// </summary>
3283
+ /// <typeparam name="T">The type of the destination object that the query will read into</typeparam>
3284
+ /// <param name="conn">The active connection. Note that this is primarily needed in order to read preferences regarding how certain data types (such as TimeSpan / DateTime) should be encoded in the database.</param>
3285
+ /// <param name="column">The table mapping used to map the statement column to a member of the destination object type</param>
3286
+ /// <returns>
3287
+ /// A delegate for fast-setting of object members from statement columns.
3288
+ ///
3289
+ /// If no fast setter is available for the requested column (enums in particular cause headache), then this function returns null.
3290
+ /// </returns>
3291
+ internal static Action < T , Sqlite3Statement , int > GetFastSetter < T > ( SQLiteConnection conn , TableMapping . Column column )
3292
+ {
3293
+ Action < T , Sqlite3Statement , int > fastSetter = null ;
3294
+
3295
+ Type clrType = column . PropertyInfo . PropertyType ;
3296
+
3297
+ var clrTypeInfo = clrType . GetTypeInfo ( ) ;
3298
+ if ( clrTypeInfo . IsGenericType && clrTypeInfo . GetGenericTypeDefinition ( ) == typeof ( Nullable < > ) ) {
3299
+ clrType = clrTypeInfo . GenericTypeArguments [ 0 ] ;
3300
+ clrTypeInfo = clrType . GetTypeInfo ( ) ;
3301
+ }
3302
+
3303
+ if ( clrType == typeof ( String ) ) {
3304
+ fastSetter = CreateTypedSetterDelegate < T , string > ( column , ( stmt , index ) => {
3305
+ return SQLite3 . ColumnString ( stmt , index ) ;
3306
+ } ) ;
3307
+ }
3308
+ else if ( clrType == typeof ( Int32 ) ) {
3309
+ fastSetter = CreateNullableTypedSetterDelegate < T , int > ( column , ( stmt , index ) => {
3310
+ return SQLite3 . ColumnInt ( stmt , index ) ;
3311
+ } ) ;
3312
+ }
3313
+ else if ( clrType == typeof ( Boolean ) ) {
3314
+ fastSetter = CreateNullableTypedSetterDelegate < T , bool > ( column , ( stmt , index ) => {
3315
+ return SQLite3 . ColumnInt ( stmt , index ) == 1 ;
3316
+ } ) ;
3317
+ }
3318
+ else if ( clrType == typeof ( double ) ) {
3319
+ fastSetter = CreateNullableTypedSetterDelegate < T , double > ( column , ( stmt , index ) => {
3320
+ return SQLite3 . ColumnDouble ( stmt , index ) ;
3321
+ } ) ;
3322
+ }
3323
+ else if ( clrType == typeof ( float ) ) {
3324
+ fastSetter = CreateNullableTypedSetterDelegate < T , float > ( column , ( stmt , index ) => {
3325
+ return ( float ) SQLite3 . ColumnDouble ( stmt , index ) ;
3326
+ } ) ;
3327
+ }
3328
+ else if ( clrType == typeof ( TimeSpan ) ) {
3329
+ if ( conn . StoreTimeSpanAsTicks ) {
3330
+ fastSetter = CreateNullableTypedSetterDelegate < T , TimeSpan > ( column , ( stmt , index ) => {
3331
+ return new TimeSpan ( SQLite3 . ColumnInt64 ( stmt , index ) ) ;
3332
+ } ) ;
3333
+ }
3334
+ else {
3335
+ fastSetter = CreateNullableTypedSetterDelegate < T , TimeSpan > ( column , ( stmt , index ) => {
3336
+ var text = SQLite3 . ColumnString ( stmt , index ) ;
3337
+ TimeSpan resultTime ;
3338
+ if ( ! TimeSpan . TryParseExact ( text , "c" , System . Globalization . CultureInfo . InvariantCulture , System . Globalization . TimeSpanStyles . None , out resultTime ) ) {
3339
+ resultTime = TimeSpan . Parse ( text ) ;
3340
+ }
3341
+ return resultTime ;
3342
+ } ) ;
3343
+ }
3344
+ }
3345
+ else if ( clrType == typeof ( DateTime ) ) {
3346
+ if ( conn . StoreDateTimeAsTicks ) {
3347
+ fastSetter = CreateNullableTypedSetterDelegate < T , DateTime > ( column , ( stmt , index ) => {
3348
+ return new DateTime ( SQLite3 . ColumnInt64 ( stmt , index ) ) ;
3349
+ } ) ;
3350
+ }
3351
+ else {
3352
+ fastSetter = CreateNullableTypedSetterDelegate < T , DateTime > ( column , ( stmt , index ) => {
3353
+ var text = SQLite3 . ColumnString ( stmt , index ) ;
3354
+ DateTime resultDate ;
3355
+ if ( ! DateTime . TryParseExact ( text , conn . DateTimeStringFormat , System . Globalization . CultureInfo . InvariantCulture , conn . DateTimeStyle , out resultDate ) ) {
3356
+ resultDate = DateTime . Parse ( text ) ;
3357
+ }
3358
+ return resultDate ;
3359
+ } ) ;
3360
+ }
3361
+ }
3362
+ else if ( clrType == typeof ( DateTimeOffset ) ) {
3363
+ fastSetter = CreateNullableTypedSetterDelegate < T , DateTimeOffset > ( column , ( stmt , index ) => {
3364
+ return new DateTimeOffset ( SQLite3 . ColumnInt64 ( stmt , index ) , TimeSpan . Zero ) ;
3365
+ } ) ;
3366
+ }
3367
+ else if ( clrTypeInfo . IsEnum ) {
3368
+ // NOTE: Not sure of a good way (if any?) to do a strongly-typed fast setter like this for enumerated types -- for now, return null and column sets will revert back to the safe (but slow) Reflection-based method of column prop.Set()
3369
+ }
3370
+ else if ( clrType == typeof ( Int64 ) ) {
3371
+ fastSetter = CreateNullableTypedSetterDelegate < T , Int64 > ( column , ( stmt , index ) => {
3372
+ return SQLite3 . ColumnInt64 ( stmt , index ) ;
3373
+ } ) ;
3374
+ }
3375
+ else if ( clrType == typeof ( UInt32 ) ) {
3376
+ fastSetter = CreateNullableTypedSetterDelegate < T , UInt32 > ( column , ( stmt , index ) => {
3377
+ return ( uint ) SQLite3 . ColumnInt64 ( stmt , index ) ;
3378
+ } ) ;
3379
+ }
3380
+ else if ( clrType == typeof ( decimal ) ) {
3381
+ fastSetter = CreateNullableTypedSetterDelegate < T , decimal > ( column , ( stmt , index ) => {
3382
+ return ( decimal ) SQLite3 . ColumnDouble ( stmt , index ) ;
3383
+ } ) ;
3384
+ }
3385
+ else if ( clrType == typeof ( Byte ) ) {
3386
+ fastSetter = CreateNullableTypedSetterDelegate < T , Byte > ( column , ( stmt , index ) => {
3387
+ return ( byte ) SQLite3 . ColumnInt ( stmt , index ) ;
3388
+ } ) ;
3389
+ }
3390
+ else if ( clrType == typeof ( UInt16 ) ) {
3391
+ fastSetter = CreateNullableTypedSetterDelegate < T , UInt16 > ( column , ( stmt , index ) => {
3392
+ return ( ushort ) SQLite3 . ColumnInt ( stmt , index ) ;
3393
+ } ) ;
3394
+ }
3395
+ else if ( clrType == typeof ( Int16 ) ) {
3396
+ fastSetter = CreateNullableTypedSetterDelegate < T , Int16 > ( column , ( stmt , index ) => {
3397
+ return ( short ) SQLite3 . ColumnInt ( stmt , index ) ;
3398
+ } ) ;
3399
+ }
3400
+ else if ( clrType == typeof ( sbyte ) ) {
3401
+ fastSetter = CreateNullableTypedSetterDelegate < T , sbyte > ( column , ( stmt , index ) => {
3402
+ return ( sbyte ) SQLite3 . ColumnInt ( stmt , index ) ;
3403
+ } ) ;
3404
+ }
3405
+ else if ( clrType == typeof ( byte [ ] ) ) {
3406
+ fastSetter = CreateTypedSetterDelegate < T , byte [ ] > ( column , ( stmt , index ) => {
3407
+ return SQLite3 . ColumnByteArray ( stmt , index ) ;
3408
+ } ) ;
3409
+ }
3410
+ else if ( clrType == typeof ( Guid ) ) {
3411
+ fastSetter = CreateNullableTypedSetterDelegate < T , Guid > ( column , ( stmt , index ) => {
3412
+ var text = SQLite3 . ColumnString ( stmt , index ) ;
3413
+ return new Guid ( text ) ;
3414
+ } ) ;
3415
+ }
3416
+ else if ( clrType == typeof ( Uri ) ) {
3417
+ fastSetter = CreateTypedSetterDelegate < T , Uri > ( column , ( stmt , index ) => {
3418
+ var text = SQLite3 . ColumnString ( stmt , index ) ;
3419
+ return new Uri ( text ) ;
3420
+ } ) ;
3421
+ }
3422
+ else if ( clrType == typeof ( StringBuilder ) ) {
3423
+ fastSetter = CreateTypedSetterDelegate < T , StringBuilder > ( column , ( stmt , index ) => {
3424
+ var text = SQLite3 . ColumnString ( stmt , index ) ;
3425
+ return new StringBuilder ( text ) ;
3426
+ } ) ;
3427
+ }
3428
+ else if ( clrType == typeof ( UriBuilder ) ) {
3429
+ fastSetter = CreateTypedSetterDelegate < T , UriBuilder > ( column , ( stmt , index ) => {
3430
+ var text = SQLite3 . ColumnString ( stmt , index ) ;
3431
+ return new UriBuilder ( text ) ;
3432
+ } ) ;
3433
+ }
3434
+ else {
3435
+ // NOTE: Will fall back to the slow setter method in the event that we are unable to create a fast setter delegate for a particular column type
3436
+ }
3437
+ return fastSetter ;
3438
+ }
3439
+
3440
+ /// <summary>
3441
+ /// This creates a strongly typed delegate that will permit fast setting of column values given a Sqlite3Statement and a column index.
3442
+ ///
3443
+ /// Note that this is identical to CreateTypedSetterDelegate(), but has an extra check to see if it should create a nullable version of the delegate.
3444
+ /// </summary>
3445
+ /// <typeparam name="ObjectType">The type of the object whose member column is being set</typeparam>
3446
+ /// <typeparam name="ColumnMemberType">The CLR type of the member in the object which corresponds to the given SQLite columnn</typeparam>
3447
+ /// <param name="column">The column mapping that identifies the target member of the destination object</param>
3448
+ /// <param name="getColumnValue">A lambda that can be used to retrieve the column value at query-time</param>
3449
+ /// <returns>A strongly-typed delegate</returns>
3450
+ private static Action < ObjectType , Sqlite3Statement , int > CreateNullableTypedSetterDelegate < ObjectType , ColumnMemberType > ( TableMapping . Column column , Func < Sqlite3Statement , int , ColumnMemberType > getColumnValue ) where ColumnMemberType : struct
3451
+ {
3452
+ var clrTypeInfo = column . PropertyInfo . PropertyType . GetTypeInfo ( ) ;
3453
+ bool isNullable = false ;
3454
+
3455
+ if ( clrTypeInfo . IsGenericType && clrTypeInfo . GetGenericTypeDefinition ( ) == typeof ( Nullable < > ) ) {
3456
+ isNullable = true ;
3457
+ }
3458
+
3459
+ if ( isNullable ) {
3460
+ var setProperty = ( Action < ObjectType , ColumnMemberType ? > ) Delegate . CreateDelegate (
3461
+ typeof ( Action < ObjectType , ColumnMemberType ? > ) , null ,
3462
+ column . PropertyInfo . GetSetMethod ( ) ) ;
3463
+
3464
+ return ( o , stmt , i ) => {
3465
+ var colType = SQLite3 . ColumnType ( stmt , i ) ;
3466
+ if ( colType != SQLite3 . ColType . Null )
3467
+ setProperty . Invoke ( o , getColumnValue . Invoke ( stmt , i ) ) ;
3468
+ } ;
3469
+ }
3470
+
3471
+ return CreateTypedSetterDelegate < ObjectType , ColumnMemberType > ( column , getColumnValue ) ;
3472
+ }
3473
+
3474
+ /// <summary>
3475
+ /// This creates a strongly typed delegate that will permit fast setting of column values given a Sqlite3Statement and a column index.
3476
+ /// </summary>
3477
+ /// <typeparam name="ObjectType">The type of the object whose member column is being set</typeparam>
3478
+ /// <typeparam name="ColumnMemberType">The CLR type of the member in the object which corresponds to the given SQLite columnn</typeparam>
3479
+ /// <param name="column">The column mapping that identifies the target member of the destination object</param>
3480
+ /// <param name="getColumnValue">A lambda that can be used to retrieve the column value at query-time</param>
3481
+ /// <returns>A strongly-typed delegate</returns>
3482
+ private static Action < ObjectType , Sqlite3Statement , int > CreateTypedSetterDelegate < ObjectType , ColumnMemberType > ( TableMapping . Column column , Func < Sqlite3Statement , int , ColumnMemberType > getColumnValue )
3483
+ {
3484
+ var setProperty = ( Action < ObjectType , ColumnMemberType > ) Delegate . CreateDelegate (
3485
+ typeof ( Action < ObjectType , ColumnMemberType > ) , null ,
3486
+ column . PropertyInfo . GetSetMethod ( ) ) ;
3487
+
3488
+ return ( o , stmt , i ) => {
3489
+ var colType = SQLite3 . ColumnType ( stmt , i ) ;
3490
+ if ( colType != SQLite3 . ColType . Null )
3491
+ setProperty . Invoke ( o , getColumnValue . Invoke ( stmt , i ) ) ;
3492
+ } ;
3493
+ }
3494
+ }
3495
+
3266
3496
/// <summary>
3267
3497
/// Since the insert never changed, we only need to prepare once.
3268
3498
/// </summary>
0 commit comments