diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs index 5b1036653d..817c42af18 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs @@ -27,8 +27,7 @@ public class AbpCachingStackExchangeRedisModule : AbpModule } }); - //liangshiwei will update the AbpRedisCache - //context.Services.Replace(ServiceDescriptor.Singleton()); + context.Services.Replace(ServiceDescriptor.Singleton()); } } } diff --git a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs index c5ae0b5393..80815eaa0b 100644 --- a/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs +++ b/framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -16,21 +17,21 @@ namespace Volo.Abp.Caching.StackExchangeRedis; [DisableConventionalRegistration] public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems { - protected static readonly string AbsoluteExpirationKey; - protected static readonly string SlidingExpirationKey; - protected static readonly string DataKey; - protected static readonly long NotPresent; - protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData; - protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration; - - private readonly static FieldInfo SetScriptField; - private readonly static FieldInfo RedisDatabaseField; - private readonly static MethodInfo ConnectMethod; - private readonly static MethodInfo ConnectAsyncMethod; - private readonly static MethodInfo MapMetadataMethod; - private readonly static MethodInfo GetAbsoluteExpirationMethod; - private readonly static MethodInfo GetExpirationInSecondsMethod; - private readonly static MethodInfo OnRedisErrorMethod; + protected readonly static string AbsoluteExpirationKey; + protected readonly static string SlidingExpirationKey; + protected readonly static string DataKey; + protected readonly static long NotPresent; + protected readonly static RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData; + protected readonly static RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration; + + protected readonly static FieldInfo RedisDatabaseField; + protected readonly static MethodInfo ConnectMethod; + protected readonly static MethodInfo ConnectAsyncMethod; + protected readonly static MethodInfo MapMetadataMethod; + protected readonly static MethodInfo GetAbsoluteExpirationMethod; + protected readonly static MethodInfo GetExpirationInSecondsMethod; + protected readonly static MethodInfo OnRedisErrorMethod; + protected readonly static MethodInfo RecycleMethodInfo; protected RedisKey InstancePrefix { get; } @@ -40,8 +41,6 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems RedisDatabaseField = Check.NotNull(type.GetField("_cache", BindingFlags.Instance | BindingFlags.NonPublic), nameof(RedisDatabaseField)); - SetScriptField = Check.NotNull(type.GetField("_setScript", BindingFlags.Instance | BindingFlags.NonPublic), nameof(SetScriptField)); - ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod)); ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod)); @@ -51,9 +50,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod)); GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod)); - + OnRedisErrorMethod = Check.NotNull(type.GetMethod("OnRedisError", BindingFlags.Instance | BindingFlags.NonPublic), nameof(OnRedisErrorMethod)); + RecycleMethodInfo = Check.NotNull(type.GetMethod("Recycle", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static), nameof(RecycleMethodInfo)); + AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; SlidingExpirationKey = type.GetField("SlidingExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; @@ -61,9 +62,9 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!; NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To(); - + HashMembersAbsoluteExpirationSlidingExpirationData = [AbsoluteExpirationKey, SlidingExpirationKey, DataKey]; - + HashMembersAbsoluteExpirationSlidingExpiration = [AbsoluteExpirationKey, SlidingExpirationKey]; } @@ -78,7 +79,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } protected virtual IDatabase Connect() - { + { return (IDatabase)ConnectMethod.Invoke(this, Array.Empty())!; } @@ -87,32 +88,36 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems return await (ValueTask)ConnectAsyncMethod.Invoke(this, new object[] { token })!; } - public byte[]?[] GetMany( - IEnumerable keys) + protected virtual void Recycle(byte[]? lease) + { + RecycleMethodInfo.Invoke(this, new object[] { lease! }); + } + + public byte[]?[] GetMany(IEnumerable keys) { keys = Check.NotNull(keys, nameof(keys)); return GetAndRefreshMany(keys, true); } - public async Task GetManyAsync( - IEnumerable keys, - CancellationToken token = default) + public async Task GetManyAsync(IEnumerable keys, CancellationToken token = default) { keys = Check.NotNull(keys, nameof(keys)); return await GetAndRefreshManyAsync(keys, true, token); } - public void SetMany( - IEnumerable> items, - DistributedCacheEntryOptions options) + public void SetMany(IEnumerable> items, DistributedCacheEntryOptions options) { var cache = Connect(); try { - Task.WaitAll(PipelineSetMany(cache, items, options)); + Task.WaitAll(PipelineSetMany(cache, items, options, out var leases)); + foreach (var lease in leases) + { + Recycle(lease); + } } catch (Exception ex) { @@ -121,10 +126,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } } - public async Task SetManyAsync( - IEnumerable> items, - DistributedCacheEntryOptions options, - CancellationToken token = default) + public async Task SetManyAsync( IEnumerable> items, DistributedCacheEntryOptions options, CancellationToken token = default) { token.ThrowIfCancellationRequested(); @@ -132,7 +134,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems try { - await Task.WhenAll(PipelineSetMany(cache, items, options)); + await Task.WhenAll(PipelineSetMany(cache, items, options, out var leases)); + foreach (var lease in leases) + { + Recycle(lease); + } } catch (Exception ex) { @@ -141,17 +147,14 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } } - public void RefreshMany( - IEnumerable keys) + public void RefreshMany(IEnumerable keys) { keys = Check.NotNull(keys, nameof(keys)); GetAndRefreshMany(keys, false); } - public async Task RefreshManyAsync( - IEnumerable keys, - CancellationToken token = default) + public async Task RefreshManyAsync(IEnumerable keys, CancellationToken token = default) { keys = Check.NotNull(keys, nameof(keys)); @@ -193,9 +196,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems } } - protected virtual byte[]?[] GetAndRefreshMany( - IEnumerable keys, - bool getData) + protected virtual byte[]?[] GetAndRefreshMany(IEnumerable keys, bool getData) { var cache = Connect(); @@ -217,10 +218,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems return bytes; } - protected virtual async Task GetAndRefreshManyAsync( - IEnumerable keys, - bool getData, - CancellationToken token = default) + protected virtual async Task GetAndRefreshManyAsync(IEnumerable keys, bool getData, CancellationToken token = default) { token.ThrowIfCancellationRequested(); @@ -239,15 +237,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems OnRedisError(ex, cache); throw; } - + return bytes; } - protected virtual Task[] PipelineRefreshManyAndOutData( - IDatabase cache, - RedisKey[] keys, - RedisValue[][] results, - out byte[]?[] bytes) + protected virtual Task[] PipelineRefreshManyAndOutData(IDatabase cache, RedisKey[] keys, RedisValue[][] results, out byte[]?[] bytes) { bytes = new byte[keys.Length][]; var tasks = new Task[keys.Length]; @@ -293,37 +287,36 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems return tasks; } - protected virtual Task[] PipelineSetMany( - IDatabase cache, - IEnumerable> items, - DistributedCacheEntryOptions options) + protected virtual Task[] PipelineSetMany(IDatabase cache, IEnumerable> items, DistributedCacheEntryOptions options, out List leases) { - items = Check.NotNull(items, nameof(items)); - options = Check.NotNull(options, nameof(options)); + var tasks = new List(); + leases = new List(); - var itemArray = items.ToArray(); - var tasks = new Task[itemArray.Length]; var creationTime = DateTimeOffset.UtcNow; + var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); - for (var i = 0; i < itemArray.Length; i++) + foreach (var item in items) { - tasks[i] = cache.ScriptEvaluateAsync(GetSetScript(), new RedisKey[] { InstancePrefix.Append(itemArray[i].Key) }, - [ - absoluteExpiration?.Ticks ?? NotPresent, - options.SlidingExpiration?.Ticks ?? NotPresent, - GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, - itemArray[i].Value - ]); + var prefixedKey = InstancePrefix.Append(item.Key); + var ttl = GetExpirationInSeconds(creationTime, absoluteExpiration, options); + var fields = GetHashFields(Linearize(new ReadOnlySequence(item.Value), out var lease), absoluteExpiration, options.SlidingExpiration); + leases.Add(lease); + if (ttl is null) + { + tasks.Add(cache.HashSetAsync(prefixedKey, fields)); + } + else + { + tasks.Add(cache.HashSetAsync(prefixedKey, fields)); + tasks.Add( cache.KeyExpireAsync(prefixedKey, TimeSpan.FromSeconds(ttl.GetValueOrDefault()))); + } } - return tasks; + return tasks.ToArray(); } - protected virtual void MapMetadata( - RedisValue[] results, - out DateTimeOffset? absoluteExpiration, - out TimeSpan? slidingExpiration) + protected virtual void MapMetadata(RedisValue[] results, out DateTimeOffset? absoluteExpiration, out TimeSpan? slidingExpiration) { var parameters = new object?[] { results, null, null }; MapMetadataMethod.Invoke(this, parameters); @@ -332,36 +325,50 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems slidingExpiration = (TimeSpan?)parameters[2]; } - protected virtual long? GetExpirationInSeconds( - DateTimeOffset creationTime, - DateTimeOffset? absoluteExpiration, - DistributedCacheEntryOptions options) + protected virtual long? GetExpirationInSeconds(DateTimeOffset creationTime, DateTimeOffset? absoluteExpiration, DistributedCacheEntryOptions options) { - return (long?)GetExpirationInSecondsMethod.Invoke(null, - new object?[] { creationTime, absoluteExpiration, options }); + return (long?)GetExpirationInSecondsMethod.Invoke(null, new object?[] { creationTime, absoluteExpiration, options }); } - protected virtual DateTimeOffset? GetAbsoluteExpiration( - DateTimeOffset creationTime, - DistributedCacheEntryOptions options) + protected virtual DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options) { return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options }); } - + protected virtual void OnRedisError(Exception ex, IDatabase cache) { OnRedisErrorMethod.Invoke(this, [ex, cache]); } - - private string GetSetScript() + + private static ReadOnlyMemory Linearize(in ReadOnlySequence value, out byte[]? lease) { - return SetScriptField.GetValue(this)!.ToString()!; + // RedisValue only supports single-segment chunks; this will almost never be an issue, but + // on those rare occasions: use a leased array to harmonize things + if (value.IsSingleSegment) + { + lease = null; + return value.First; + } + var length = checked((int)value.Length); + lease = ArrayPool.Shared.Rent(length); + value.CopyTo(lease); + return new(lease, 0, length); } - + private static RedisValue[] GetHashFields(bool getData) { return getData ? HashMembersAbsoluteExpirationSlidingExpirationData : HashMembersAbsoluteExpirationSlidingExpiration; } + + private static HashEntry[] GetHashFields(RedisValue value, DateTimeOffset? absoluteExpiration, TimeSpan? slidingExpiration) + { + return + [ + new HashEntry(AbsoluteExpirationKey, absoluteExpiration?.Ticks ?? NotPresent), + new HashEntry(SlidingExpirationKey, slidingExpiration?.Ticks ?? NotPresent), + new HashEntry(DataKey, value) + ]; + } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index da8aebbeec..4cd33e2bc0 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -114,7 +114,6 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - //TODO: Re-check. optionsBuilder.ConfigureWarnings(c => c.Ignore(RelationalEventId.PendingModelChangesWarning)); base.OnConfiguring(optionsBuilder); } @@ -628,7 +627,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, originalExtraProperties = entry.OriginalValues.GetValue(nameof(IHasExtraProperties.ExtraProperties)); } - //TODO: Reload will throw an exception. Check it later. + //TODO: Reload will throw an exception. Check it when new EF Core versions released. //entry.Reload(); var storeValues = entry.OriginalValues; @@ -636,7 +635,6 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, entry.OriginalValues.SetValues(storeValues); entry.State = EntityState.Unchanged; - if (entry.Entity is IHasExtraProperties) { ObjectHelper.TrySetProperty(entry.Entity.As(), x => x.ExtraProperties, () => originalExtraProperties); diff --git a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs index 12210428c3..58b5deafdf 100644 --- a/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs +++ b/framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs @@ -13,7 +13,7 @@ public class AbpRedisCache_Tests : AbpCachingStackExchangeRedisTestBase _distributedCache = GetRequiredService(); } - [Fact(Skip = "liangshiwei will update the AbpRedisCache")] + [Fact] public void Should_Replace_RedisCache() { (_distributedCache is AbpRedisCache).ShouldBeTrue(); diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index a6fccfd6d8..e75af9a556 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -79,7 +79,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule public override void OnPreApplicationInitialization(ApplicationInitializationContext context) { - //context.ServiceProvider.GetRequiredService().Database.Migrate(); + context.ServiceProvider.GetRequiredService().Database.Migrate(); using (var scope = context.ServiceProvider.CreateScope()) { var categoryRepository = scope.ServiceProvider.GetRequiredService>();