Browse Source

Update `AbpRedisCache` to compatible net 9.

pull/20803/head
maliming 2 years ago
parent
commit
ea7383ad35
  1. 3
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpCachingStackExchangeRedisModule.cs
  2. 183
      framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs
  3. 4
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  4. 2
      framework/test/Volo.Abp.Caching.StackExchangeRedis.Tests/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache_Tests.cs
  5. 2
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

3
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<IDistributedCache, AbpRedisCache>());
//context.Services.Replace(ServiceDescriptor.Singleton<IDistributedCache, AbpRedisCache>());
} }
} }
} }

183
framework/src/Volo.Abp.Caching.StackExchangeRedis/Volo/Abp/Caching/StackExchangeRedis/AbpRedisCache.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -16,21 +17,21 @@ namespace Volo.Abp.Caching.StackExchangeRedis;
[DisableConventionalRegistration] [DisableConventionalRegistration]
public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
{ {
protected static readonly string AbsoluteExpirationKey; protected readonly static string AbsoluteExpirationKey;
protected static readonly string SlidingExpirationKey; protected readonly static string SlidingExpirationKey;
protected static readonly string DataKey; protected readonly static string DataKey;
protected static readonly long NotPresent; protected readonly static long NotPresent;
protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData; protected readonly static RedisValue[] HashMembersAbsoluteExpirationSlidingExpirationData;
protected static readonly RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration; protected readonly static RedisValue[] HashMembersAbsoluteExpirationSlidingExpiration;
private readonly static FieldInfo SetScriptField; protected readonly static FieldInfo RedisDatabaseField;
private readonly static FieldInfo RedisDatabaseField; protected readonly static MethodInfo ConnectMethod;
private readonly static MethodInfo ConnectMethod; protected readonly static MethodInfo ConnectAsyncMethod;
private readonly static MethodInfo ConnectAsyncMethod; protected readonly static MethodInfo MapMetadataMethod;
private readonly static MethodInfo MapMetadataMethod; protected readonly static MethodInfo GetAbsoluteExpirationMethod;
private readonly static MethodInfo GetAbsoluteExpirationMethod; protected readonly static MethodInfo GetExpirationInSecondsMethod;
private readonly static MethodInfo GetExpirationInSecondsMethod; protected readonly static MethodInfo OnRedisErrorMethod;
private readonly static MethodInfo OnRedisErrorMethod; protected readonly static MethodInfo RecycleMethodInfo;
protected RedisKey InstancePrefix { get; } 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)); 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)); ConnectMethod = Check.NotNull(type.GetMethod("Connect", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectMethod));
ConnectAsyncMethod = Check.NotNull(type.GetMethod("ConnectAsync", BindingFlags.Instance | BindingFlags.NonPublic), nameof(ConnectAsyncMethod)); 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)); GetAbsoluteExpirationMethod = Check.NotNull(type.GetMethod("GetAbsoluteExpiration", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetAbsoluteExpirationMethod));
GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod)); GetExpirationInSecondsMethod = Check.NotNull(type.GetMethod("GetExpirationInSeconds", BindingFlags.Static | BindingFlags.NonPublic), nameof(GetExpirationInSecondsMethod));
OnRedisErrorMethod = Check.NotNull(type.GetMethod("OnRedisError", BindingFlags.Instance | BindingFlags.NonPublic), nameof(OnRedisErrorMethod)); 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()!; AbsoluteExpirationKey = type.GetField("AbsoluteExpirationKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
SlidingExpirationKey = type.GetField("SlidingExpirationKey", 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()!; DataKey = type.GetField("DataKey", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.ToString()!;
NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To<int>(); NotPresent = type.GetField("NotPresent", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!.To<int>();
HashMembersAbsoluteExpirationSlidingExpirationData = [AbsoluteExpirationKey, SlidingExpirationKey, DataKey]; HashMembersAbsoluteExpirationSlidingExpirationData = [AbsoluteExpirationKey, SlidingExpirationKey, DataKey];
HashMembersAbsoluteExpirationSlidingExpiration = [AbsoluteExpirationKey, SlidingExpirationKey]; HashMembersAbsoluteExpirationSlidingExpiration = [AbsoluteExpirationKey, SlidingExpirationKey];
} }
@ -78,7 +79,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
} }
protected virtual IDatabase Connect() protected virtual IDatabase Connect()
{ {
return (IDatabase)ConnectMethod.Invoke(this, Array.Empty<object>())!; return (IDatabase)ConnectMethod.Invoke(this, Array.Empty<object>())!;
} }
@ -87,32 +88,36 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return await (ValueTask<IDatabase>)ConnectAsyncMethod.Invoke(this, new object[] { token })!; return await (ValueTask<IDatabase>)ConnectAsyncMethod.Invoke(this, new object[] { token })!;
} }
public byte[]?[] GetMany( protected virtual void Recycle(byte[]? lease)
IEnumerable<string> keys) {
RecycleMethodInfo.Invoke(this, new object[] { lease! });
}
public byte[]?[] GetMany(IEnumerable<string> keys)
{ {
keys = Check.NotNull(keys, nameof(keys)); keys = Check.NotNull(keys, nameof(keys));
return GetAndRefreshMany(keys, true); return GetAndRefreshMany(keys, true);
} }
public async Task<byte[]?[]> GetManyAsync( public async Task<byte[]?[]> GetManyAsync(IEnumerable<string> keys, CancellationToken token = default)
IEnumerable<string> keys,
CancellationToken token = default)
{ {
keys = Check.NotNull(keys, nameof(keys)); keys = Check.NotNull(keys, nameof(keys));
return await GetAndRefreshManyAsync(keys, true, token); return await GetAndRefreshManyAsync(keys, true, token);
} }
public void SetMany( public void SetMany(IEnumerable<KeyValuePair<string, byte[]>> items, DistributedCacheEntryOptions options)
IEnumerable<KeyValuePair<string, byte[]>> items,
DistributedCacheEntryOptions options)
{ {
var cache = Connect(); var cache = Connect();
try 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) catch (Exception ex)
{ {
@ -121,10 +126,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
} }
} }
public async Task SetManyAsync( public async Task SetManyAsync( IEnumerable<KeyValuePair<string, byte[]>> items, DistributedCacheEntryOptions options, CancellationToken token = default)
IEnumerable<KeyValuePair<string, byte[]>> items,
DistributedCacheEntryOptions options,
CancellationToken token = default)
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -132,7 +134,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
try 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) catch (Exception ex)
{ {
@ -141,17 +147,14 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
} }
} }
public void RefreshMany( public void RefreshMany(IEnumerable<string> keys)
IEnumerable<string> keys)
{ {
keys = Check.NotNull(keys, nameof(keys)); keys = Check.NotNull(keys, nameof(keys));
GetAndRefreshMany(keys, false); GetAndRefreshMany(keys, false);
} }
public async Task RefreshManyAsync( public async Task RefreshManyAsync(IEnumerable<string> keys, CancellationToken token = default)
IEnumerable<string> keys,
CancellationToken token = default)
{ {
keys = Check.NotNull(keys, nameof(keys)); keys = Check.NotNull(keys, nameof(keys));
@ -193,9 +196,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
} }
} }
protected virtual byte[]?[] GetAndRefreshMany( protected virtual byte[]?[] GetAndRefreshMany(IEnumerable<string> keys, bool getData)
IEnumerable<string> keys,
bool getData)
{ {
var cache = Connect(); var cache = Connect();
@ -217,10 +218,7 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return bytes; return bytes;
} }
protected virtual async Task<byte[]?[]> GetAndRefreshManyAsync( protected virtual async Task<byte[]?[]> GetAndRefreshManyAsync(IEnumerable<string> keys, bool getData, CancellationToken token = default)
IEnumerable<string> keys,
bool getData,
CancellationToken token = default)
{ {
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
@ -239,15 +237,11 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
OnRedisError(ex, cache); OnRedisError(ex, cache);
throw; throw;
} }
return bytes; return bytes;
} }
protected virtual Task[] PipelineRefreshManyAndOutData( protected virtual Task[] PipelineRefreshManyAndOutData(IDatabase cache, RedisKey[] keys, RedisValue[][] results, out byte[]?[] bytes)
IDatabase cache,
RedisKey[] keys,
RedisValue[][] results,
out byte[]?[] bytes)
{ {
bytes = new byte[keys.Length][]; bytes = new byte[keys.Length][];
var tasks = new Task[keys.Length]; var tasks = new Task[keys.Length];
@ -293,37 +287,36 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
return tasks; return tasks;
} }
protected virtual Task[] PipelineSetMany( protected virtual Task[] PipelineSetMany(IDatabase cache, IEnumerable<KeyValuePair<string, byte[]>> items, DistributedCacheEntryOptions options, out List<byte[]?> leases)
IDatabase cache,
IEnumerable<KeyValuePair<string, byte[]>> items,
DistributedCacheEntryOptions options)
{ {
items = Check.NotNull(items, nameof(items)); var tasks = new List<Task>();
options = Check.NotNull(options, nameof(options)); leases = new List<byte[]?>();
var itemArray = items.ToArray();
var tasks = new Task[itemArray.Length];
var creationTime = DateTimeOffset.UtcNow; var creationTime = DateTimeOffset.UtcNow;
var absoluteExpiration = GetAbsoluteExpiration(creationTime, options); 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) }, var prefixedKey = InstancePrefix.Append(item.Key);
[ var ttl = GetExpirationInSeconds(creationTime, absoluteExpiration, options);
absoluteExpiration?.Ticks ?? NotPresent, var fields = GetHashFields(Linearize(new ReadOnlySequence<byte>(item.Value), out var lease), absoluteExpiration, options.SlidingExpiration);
options.SlidingExpiration?.Ticks ?? NotPresent, leases.Add(lease);
GetExpirationInSeconds(creationTime, absoluteExpiration, options) ?? NotPresent, if (ttl is null)
itemArray[i].Value {
]); 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( protected virtual void MapMetadata(RedisValue[] results, out DateTimeOffset? absoluteExpiration, out TimeSpan? slidingExpiration)
RedisValue[] results,
out DateTimeOffset? absoluteExpiration,
out TimeSpan? slidingExpiration)
{ {
var parameters = new object?[] { results, null, null }; var parameters = new object?[] { results, null, null };
MapMetadataMethod.Invoke(this, parameters); MapMetadataMethod.Invoke(this, parameters);
@ -332,36 +325,50 @@ public class AbpRedisCache : RedisCache, ICacheSupportsMultipleItems
slidingExpiration = (TimeSpan?)parameters[2]; slidingExpiration = (TimeSpan?)parameters[2];
} }
protected virtual long? GetExpirationInSeconds( protected virtual long? GetExpirationInSeconds(DateTimeOffset creationTime, DateTimeOffset? absoluteExpiration, DistributedCacheEntryOptions options)
DateTimeOffset creationTime,
DateTimeOffset? absoluteExpiration,
DistributedCacheEntryOptions options)
{ {
return (long?)GetExpirationInSecondsMethod.Invoke(null, return (long?)GetExpirationInSecondsMethod.Invoke(null, new object?[] { creationTime, absoluteExpiration, options });
new object?[] { creationTime, absoluteExpiration, options });
} }
protected virtual DateTimeOffset? GetAbsoluteExpiration( protected virtual DateTimeOffset? GetAbsoluteExpiration(DateTimeOffset creationTime, DistributedCacheEntryOptions options)
DateTimeOffset creationTime,
DistributedCacheEntryOptions options)
{ {
return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options }); return (DateTimeOffset?)GetAbsoluteExpirationMethod.Invoke(null, new object[] { creationTime, options });
} }
protected virtual void OnRedisError(Exception ex, IDatabase cache) protected virtual void OnRedisError(Exception ex, IDatabase cache)
{ {
OnRedisErrorMethod.Invoke(this, [ex, cache]); OnRedisErrorMethod.Invoke(this, [ex, cache]);
} }
private string GetSetScript() private static ReadOnlyMemory<byte> Linearize(in ReadOnlySequence<byte> 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<byte>.Shared.Rent(length);
value.CopyTo(lease);
return new(lease, 0, length);
} }
private static RedisValue[] GetHashFields(bool getData) private static RedisValue[] GetHashFields(bool getData)
{ {
return getData return getData
? HashMembersAbsoluteExpirationSlidingExpirationData ? HashMembersAbsoluteExpirationSlidingExpirationData
: HashMembersAbsoluteExpirationSlidingExpiration; : 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)
];
}
} }

4
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -114,7 +114,6 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
//TODO: Re-check.
optionsBuilder.ConfigureWarnings(c => c.Ignore(RelationalEventId.PendingModelChangesWarning)); optionsBuilder.ConfigureWarnings(c => c.Ignore(RelationalEventId.PendingModelChangesWarning));
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
} }
@ -628,7 +627,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
originalExtraProperties = entry.OriginalValues.GetValue<ExtraPropertyDictionary>(nameof(IHasExtraProperties.ExtraProperties)); originalExtraProperties = entry.OriginalValues.GetValue<ExtraPropertyDictionary>(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(); //entry.Reload();
var storeValues = entry.OriginalValues; var storeValues = entry.OriginalValues;
@ -636,7 +635,6 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
entry.OriginalValues.SetValues(storeValues); entry.OriginalValues.SetValues(storeValues);
entry.State = EntityState.Unchanged; entry.State = EntityState.Unchanged;
if (entry.Entity is IHasExtraProperties) if (entry.Entity is IHasExtraProperties)
{ {
ObjectHelper.TrySetProperty(entry.Entity.As<IHasExtraProperties>(), x => x.ExtraProperties, () => originalExtraProperties); ObjectHelper.TrySetProperty(entry.Entity.As<IHasExtraProperties>(), x => x.ExtraProperties, () => originalExtraProperties);

2
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<IDistributedCache>(); _distributedCache = GetRequiredService<IDistributedCache>();
} }
[Fact(Skip = "liangshiwei will update the AbpRedisCache")] [Fact]
public void Should_Replace_RedisCache() public void Should_Replace_RedisCache()
{ {
(_distributedCache is AbpRedisCache).ShouldBeTrue(); (_distributedCache is AbpRedisCache).ShouldBeTrue();

2
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) public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{ {
//context.ServiceProvider.GetRequiredService<SecondDbContext>().Database.Migrate(); context.ServiceProvider.GetRequiredService<SecondDbContext>().Database.Migrate();
using (var scope = context.ServiceProvider.CreateScope()) using (var scope = context.ServiceProvider.CreateScope())
{ {
var categoryRepository = scope.ServiceProvider.GetRequiredService<IBasicRepository<Category, Guid>>(); var categoryRepository = scope.ServiceProvider.GetRequiredService<IBasicRepository<Category, Guid>>();

Loading…
Cancel
Save