@@ -33,6 +33,13 @@ public class RedisCacheClient : IDistributedCacheClient
33
33
end
34
34
return count" ;
35
35
36
+ private const string GET_KEYS_SCRIPT = @"return redis.call('keys', @pattern)" ;
37
+
38
+ private const string GET_KEY_AND_VALUE_SCRIPT = @"local ks = redis.call('KEYS', @keypattern)
39
+ local result = {}
40
+ for index,val in pairs(ks) do result[(2 * index - 1)] = val; result[(2 * index)] = redis.call('hgetall', val) end;
41
+ return result" ;
42
+
36
43
private const string ABSOLUTE_EXPIRATION_KEY = "absexp" ;
37
44
private const string SLIDING_EXPIRATION_KEY = "sldexp" ;
38
45
private const string DATA_KEY = "data" ;
@@ -296,6 +303,78 @@ public async Task<bool> ExistsAsync<T>(string key)
296
303
return await _db . KeyExistsAsync ( key ) ;
297
304
}
298
305
306
+ /// <summary>
307
+ /// Support fuzzy filtering to obtain key set
308
+ /// </summary>
309
+ /// <param name="keyPattern"></param>
310
+ /// <returns></returns>
311
+ public List < string > GetKeys ( string keyPattern )
312
+ {
313
+ var prepared = LuaScript . Prepare ( GET_KEYS_SCRIPT ) ;
314
+ var cacheResult = _db . ScriptEvaluate ( prepared , new { pattern = keyPattern } ) ;
315
+ if ( cacheResult . IsNull )
316
+ return new List < string > ( ) ;
317
+
318
+ return ( ( string [ ] ) cacheResult ) . ToList ( ) ;
319
+ }
320
+
321
+ /// <summary>
322
+ /// Support fuzzy filtering to obtain key set
323
+ /// </summary>
324
+ /// <param name="keyPattern"></param>
325
+ /// <returns></returns>
326
+ public async Task < List < string > > GetKeysAsync ( string keyPattern )
327
+ {
328
+ var prepared = LuaScript . Prepare ( GET_KEYS_SCRIPT ) ;
329
+ var cacheResult = await _db . ScriptEvaluateAsync ( prepared , new { pattern = keyPattern } ) ;
330
+ if ( cacheResult . IsNull ) return new List < string > ( ) ;
331
+
332
+ return ( ( string [ ] ) cacheResult ) . ToList ( ) ;
333
+ }
334
+
335
+ public Dictionary < string , T ? > GetListByKeyPattern < T > ( string keyPattern )
336
+ {
337
+ var arrayRedisResult = _db . ScriptEvaluate (
338
+ LuaScript . Prepare ( GET_KEY_AND_VALUE_SCRIPT ) ,
339
+ new { keypattern = keyPattern } ) . ToDictionary ( ) ;
340
+
341
+ return GetListByKeyPatternCore < T ? > (
342
+ arrayRedisResult ,
343
+ ( key , absExpr , sldExpr ) =>
344
+ {
345
+ Refresh ( key , absExpr , sldExpr ) ;
346
+ return Task . CompletedTask ;
347
+ } ) ;
348
+ }
349
+
350
+ public async Task < Dictionary < string , T ? > > GetListByKeyPatternAsync < T > ( string keyPattern )
351
+ {
352
+ var arrayRedisResult = ( await _db . ScriptEvaluateAsync (
353
+ LuaScript . Prepare ( GET_KEY_AND_VALUE_SCRIPT ) ,
354
+ new { keypattern = keyPattern } ) ) . ToDictionary ( ) ;
355
+
356
+ return GetListByKeyPatternCore < T ? > (
357
+ arrayRedisResult ,
358
+ async ( key , absExpr , sldExpr ) => await RefreshAsync ( key , absExpr , sldExpr ) ) ;
359
+ }
360
+
361
+ private Dictionary < string , T ? > GetListByKeyPatternCore < T > (
362
+ Dictionary < string , RedisResult > arrayRedisResult ,
363
+ Func < string , DateTimeOffset ? , TimeSpan ? , Task > func )
364
+ {
365
+ Dictionary < string , T ? > dictionary = new ( ) ;
366
+
367
+ foreach ( var redisResult in arrayRedisResult )
368
+ {
369
+ var byteArray = ( RedisValue [ ] ) redisResult . Value ;
370
+ MapMetadata ( byteArray , out DateTimeOffset ? absExpr , out TimeSpan ? sldExpr , out RedisValue data ) ;
371
+ func . Invoke ( redisResult . Key , absExpr , sldExpr ) ;
372
+ dictionary . Add ( redisResult . Key , ConvertToValue < T > ( data ) ) ;
373
+ }
374
+
375
+ return dictionary ;
376
+ }
377
+
299
378
/// <inheritdoc />
300
379
public void Refresh ( string key )
301
380
{
@@ -331,31 +410,25 @@ await _subscriber.SubscribeAsync(channel, (_, message) =>
331
410
/// <inheritdoc />
332
411
public void Publish < T > ( string channel , Action < SubscribeOptions < T > > setup )
333
412
{
334
- if ( channel == null )
335
- throw new ArgumentNullException ( nameof ( channel ) ) ;
336
-
337
- if ( setup == null )
338
- throw new ArgumentNullException ( nameof ( setup ) ) ;
339
-
340
- var options = new SubscribeOptions < T > ( ) ;
341
- setup . Invoke ( options ) ;
342
-
343
- if ( string . IsNullOrWhiteSpace ( options . Key ) )
344
- throw new ArgumentNullException ( nameof ( options . Key ) ) ;
345
-
346
- var message = JsonSerializer . Serialize ( options ) ;
347
-
348
- _subscriber . Publish ( channel , message ) ;
413
+ PublishCore ( channel , setup , ( c , message ) =>
414
+ {
415
+ _subscriber . Publish ( c , message ) ;
416
+ return Task . CompletedTask ;
417
+ } ) ;
349
418
}
350
419
351
420
/// <inheritdoc />
352
421
public async Task PublishAsync < T > ( string channel , Action < SubscribeOptions < T > > setup )
353
422
{
354
- if ( channel == null )
355
- throw new ArgumentNullException ( nameof ( channel ) ) ;
423
+ PublishCore ( channel , setup , async ( c , message ) => await _subscriber . PublishAsync ( c , message ) ) ;
424
+ await Task . CompletedTask ;
425
+ }
426
+
427
+ private void PublishCore < T > ( string channel , Action < SubscribeOptions < T > > setup , Func < string , string , Task > func )
428
+ {
429
+ ArgumentNullException . ThrowIfNull ( channel , nameof ( channel ) ) ;
356
430
357
- if ( setup == null )
358
- throw new ArgumentNullException ( nameof ( setup ) ) ;
431
+ ArgumentNullException . ThrowIfNull ( setup , nameof ( setup ) ) ;
359
432
360
433
var options = new SubscribeOptions < T > ( ) ;
361
434
setup . Invoke ( options ) ;
@@ -364,8 +437,7 @@ public async Task PublishAsync<T>(string channel, Action<SubscribeOptions<T>> se
364
437
throw new ArgumentNullException ( nameof ( options . Key ) ) ;
365
438
366
439
var message = JsonSerializer . Serialize ( options ) ;
367
-
368
- await _subscriber . PublishAsync ( channel , message ) ;
440
+ func . Invoke ( channel , message ) ;
369
441
}
370
442
371
443
public async Task < long > HashIncrementAsync ( string key , long value = 1L )
@@ -393,92 +465,55 @@ private RedisValue GetAndRefresh(string key, bool getData)
393
465
if ( key == null )
394
466
throw new ArgumentNullException ( nameof ( key ) ) ;
395
467
396
- RedisValue [ ] results ;
397
- if ( getData )
398
- {
399
- results = _db . HashMemberGet ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY , DATA_KEY ) ;
400
- }
401
- else
402
- {
403
- results = _db . HashMemberGet ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY ) ;
404
- }
405
-
406
- if ( results . Length >= 2 )
407
- {
408
- MapMetadata ( results , out DateTimeOffset ? absExpr , out TimeSpan ? sldExpr ) ;
409
- Refresh ( key , absExpr , sldExpr ) ;
410
- }
468
+ var results = getData ? _db . HashMemberGet ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY , DATA_KEY ) :
469
+ _db . HashMemberGet ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY ) ;
411
470
412
- if ( results . Length >= 3 && results [ 2 ] . HasValue )
413
- {
414
- return results [ 2 ] ;
415
- }
471
+ MapMetadata ( results , out DateTimeOffset ? absExpr , out TimeSpan ? sldExpr , out RedisValue data ) ;
472
+ Refresh ( key , absExpr , sldExpr ) ;
416
473
417
- return RedisValue . Null ;
474
+ return data ;
418
475
}
419
476
420
477
private async Task < RedisValue > GetAndRefreshAsync ( string key , bool getData )
421
478
{
422
479
if ( key == null )
423
480
throw new ArgumentNullException ( nameof ( key ) ) ;
424
481
425
- RedisValue [ ] results ;
426
- if ( getData )
427
- {
428
- results = await _db . HashMemberGetAsync ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY , DATA_KEY ) . ConfigureAwait ( false ) ;
429
- }
430
- else
431
- {
432
- results = await _db . HashMemberGetAsync ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY ) . ConfigureAwait ( false ) ;
433
- }
434
-
435
- // TODO: Error handling
436
- if ( results . Length >= 2 )
437
- {
438
- MapMetadata ( results , out DateTimeOffset ? absExpr , out TimeSpan ? sldExpr ) ;
439
- await RefreshAsync ( key , absExpr , sldExpr ) . ConfigureAwait ( false ) ;
440
- }
482
+ var results = getData ?
483
+ await _db . HashMemberGetAsync ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY , DATA_KEY ) . ConfigureAwait ( false ) :
484
+ await _db . HashMemberGetAsync ( key , ABSOLUTE_EXPIRATION_KEY , SLIDING_EXPIRATION_KEY ) . ConfigureAwait ( false ) ;
441
485
442
- if ( results . Length >= 3 && results [ 2 ] . HasValue )
443
- {
444
- return results [ 2 ] ;
445
- }
446
-
447
- return RedisValue . Null ;
486
+ MapMetadata ( results , out DateTimeOffset ? absExpr , out TimeSpan ? sldExpr , out var data ) ;
487
+ await RefreshAsync ( key , absExpr , sldExpr ) . ConfigureAwait ( false ) ;
488
+ return data ;
448
489
}
449
490
450
491
private void Refresh ( string key , DateTimeOffset ? absExpr , TimeSpan ? sldExpr )
451
492
{
452
- if ( key == null )
453
- {
454
- throw new ArgumentNullException ( nameof ( key ) ) ;
455
- }
456
-
457
- // Note Refresh has no effect if there is just an absolute expiration (or neither).
458
- if ( sldExpr . HasValue )
493
+ RefreshCore ( key , absExpr , sldExpr , ( k , expr ) =>
459
494
{
460
- TimeSpan ? expr ;
461
- if ( absExpr . HasValue )
462
- {
463
- var relExpr = absExpr . Value - DateTimeOffset . Now ;
464
- expr = relExpr <= sldExpr . Value ? relExpr : sldExpr ;
465
- }
466
- else
467
- {
468
- expr = sldExpr ;
469
- }
470
-
471
- _db . KeyExpire ( key , expr ) ;
472
- // TODO: Error handling
473
- }
495
+ _db . KeyExpire ( k , expr ) ;
496
+ return Task . CompletedTask ;
497
+ } ) ;
474
498
}
475
499
476
500
private async Task RefreshAsync ( string key , DateTimeOffset ? absExpr , TimeSpan ? sldExpr , CancellationToken token = default )
477
501
{
478
- if ( key == null )
502
+ RefreshCore ( key , absExpr , sldExpr , async ( k , expr ) =>
479
503
{
480
- throw new ArgumentNullException ( nameof ( key ) ) ;
481
- }
504
+ await _db . KeyExpireAsync ( k , expr ) . ConfigureAwait ( false ) ;
505
+ } , token ) ;
506
+ await Task . CompletedTask ;
507
+ }
508
+
509
+ private void RefreshCore (
510
+ string key ,
511
+ DateTimeOffset ? absExpr ,
512
+ TimeSpan ? sldExpr ,
513
+ Func < string , TimeSpan ? , Task > func ,
514
+ CancellationToken token = default )
515
+ {
516
+ ArgumentNullException . ThrowIfNull ( key , nameof ( key ) ) ;
482
517
483
518
token . ThrowIfCancellationRequested ( ) ;
484
519
@@ -496,25 +531,39 @@ private async Task RefreshAsync(string key, DateTimeOffset? absExpr, TimeSpan? s
496
531
expr = sldExpr ;
497
532
}
498
533
499
- await _db . KeyExpireAsync ( key , expr ) . ConfigureAwait ( false ) ;
500
- // TODO: Error handling
534
+ func . Invoke ( key , expr ) ;
501
535
}
502
536
}
503
537
504
- private static void MapMetadata ( RedisValue [ ] results , out DateTimeOffset ? absoluteExpiration , out TimeSpan ? slidingExpiration )
538
+ private static void MapMetadata ( RedisValue [ ] results , out DateTimeOffset ? absoluteExpiration , out TimeSpan ? slidingExpiration ,
539
+ out RedisValue data )
505
540
{
506
541
absoluteExpiration = null ;
507
542
slidingExpiration = null ;
508
- var absoluteExpirationTicks = ( long ? ) results [ 0 ] ;
509
- if ( absoluteExpirationTicks . HasValue && absoluteExpirationTicks . Value != NOT_PRESENT )
510
- {
511
- absoluteExpiration = new DateTimeOffset ( absoluteExpirationTicks . Value , TimeSpan . Zero ) ;
512
- }
543
+ data = RedisValue . Null ;
513
544
514
- var slidingExpirationTicks = ( long ? ) results [ 1 ] ;
515
- if ( slidingExpirationTicks . HasValue && slidingExpirationTicks . Value != NOT_PRESENT )
545
+ for ( int index = 0 ; index < results . Length ; index += 2 )
516
546
{
517
- slidingExpiration = new TimeSpan ( slidingExpirationTicks . Value ) ;
547
+ if ( results [ index ] == ABSOLUTE_EXPIRATION_KEY )
548
+ {
549
+ var absoluteExpirationTicks = ( long ? ) results [ index + 1 ] ;
550
+ if ( absoluteExpirationTicks . HasValue && absoluteExpirationTicks . Value != NOT_PRESENT )
551
+ {
552
+ absoluteExpiration = new DateTimeOffset ( absoluteExpirationTicks . Value , TimeSpan . Zero ) ;
553
+ }
554
+ }
555
+ else if ( results [ index ] == SLIDING_EXPIRATION_KEY )
556
+ {
557
+ var slidingExpirationTicks = ( long ? ) results [ index + 1 ] ;
558
+ if ( slidingExpirationTicks . HasValue && slidingExpirationTicks . Value != NOT_PRESENT )
559
+ {
560
+ slidingExpiration = new TimeSpan ( slidingExpirationTicks . Value ) ;
561
+ }
562
+ }
563
+ else if ( results [ index ] == DATA_KEY )
564
+ {
565
+ data = results [ index + 1 ] ;
566
+ }
518
567
}
519
568
}
520
569
0 commit comments