diff --git a/EasyCaching.sln b/EasyCaching.sln index bfd9e15d..a595298f 100644 --- a/EasyCaching.sln +++ b/EasyCaching.sln @@ -68,6 +68,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bus", "bus", "{B337509B-75F EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.LiteDB", "src\EasyCaching.LiteDB\EasyCaching.LiteDB.csproj", "{BA850294-3103-4540-8A27-FC768E1DC8FC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Serialization.SystemTextJson", "serialization\EasyCaching.Serialization.SystemTextJson\EasyCaching.Serialization.SystemTextJson.csproj", "{4FCF16BF-5E21-4B74-AB45-3C121ADF1485}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -178,6 +180,10 @@ Global {BA850294-3103-4540-8A27-FC768E1DC8FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA850294-3103-4540-8A27-FC768E1DC8FC}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA850294-3103-4540-8A27-FC768E1DC8FC}.Release|Any CPU.Build.0 = Release|Any CPU + {4FCF16BF-5E21-4B74-AB45-3C121ADF1485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FCF16BF-5E21-4B74-AB45-3C121ADF1485}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FCF16BF-5E21-4B74-AB45-3C121ADF1485}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FCF16BF-5E21-4B74-AB45-3C121ADF1485}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -209,6 +215,7 @@ Global {43AD80E9-696B-4042-9D50-B26F48BE1928} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} {711603E1-8363-4F8D-9AA9-8C03EC8BD35F} = {B4241D34-A973-4A13-BD89-9BAE3F2BDDF6} {BA850294-3103-4540-8A27-FC768E1DC8FC} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} + {4FCF16BF-5E21-4B74-AB45-3C121ADF1485} = {15070C49-A507-4844-BCFE-D319CFBC9A63} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63A57886-054B-476C-AAE1-8D7C8917682E} diff --git a/build/releasenotes.props b/build/releasenotes.props index de977ee8..eaa7fb1d 100644 --- a/build/releasenotes.props +++ b/build/releasenotes.props @@ -1,14 +1,14 @@ - 1. IRedisProvider support search keys. - 2. DefaultEasyCachingKeyGenerator support GenerateCacheKeyForModel. + 1. Allow to cache null value. + 2. Ready for 1.x version. 1. Upgrading dependencies. - 1. IRedisProvider support search keys. + 1. Upgrading dependencies. 1. Upgrading dependencies. @@ -38,7 +38,7 @@ 1. Upgrading dependencies. - 1. IRedisProvider support search keys. + 1. Upgrading dependencies. 1. Upgrading dependencies. @@ -53,7 +53,10 @@ 1. Upgrading dependencies. - 1. LiteDB joined for the first time. + 1. Upgrading dependencies. + + 1. Init. + diff --git a/build/version.props b/build/version.props index 73a17b9c..63cef48f 100644 --- a/build/version.props +++ b/build/version.props @@ -1,23 +1,24 @@ - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 - 0.9.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 + 1.1.0 diff --git a/sample/EasyCaching.Demo.Interceptors/Controllers/ValuesController.cs b/sample/EasyCaching.Demo.Interceptors/Controllers/ValuesController.cs index a7591e19..a973e71b 100644 --- a/sample/EasyCaching.Demo.Interceptors/Controllers/ValuesController.cs +++ b/sample/EasyCaching.Demo.Interceptors/Controllers/ValuesController.cs @@ -38,6 +38,11 @@ public string Aspectcore(int type = 1) var res = _aService.GetDemo(111); return $"{res.Id}-{res.Name}-{res.CreateTime}"; } + else if (type == 5) + { + var res = _aService.GetData(); + return Newtonsoft.Json.JsonConvert.SerializeObject(res); + } else { return "wait"; diff --git a/sample/EasyCaching.Demo.Interceptors/Services/IAspectCoreService.cs b/sample/EasyCaching.Demo.Interceptors/Services/IAspectCoreService.cs index 2411b7e8..409056f6 100644 --- a/sample/EasyCaching.Demo.Interceptors/Services/IAspectCoreService.cs +++ b/sample/EasyCaching.Demo.Interceptors/Services/IAspectCoreService.cs @@ -27,6 +27,9 @@ public interface IAspectCoreService //: EasyCaching.Core.Internal.IEasyCaching [EasyCachingAble(Expiration = 10)] Demo GetDemo(int id); + + [EasyCachingAble(Expiration = 10)] + object GetData(); } public class AspectCoreService : IAspectCoreService @@ -71,6 +74,10 @@ public string PutSomething(string str) return str; } + public object GetData() + { + return new { x = System.DateTimeOffset.Now.ToUnixTimeSeconds() }; + } } [ProtoContract] diff --git a/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/EasyCachingJsonSerializerOptions.cs b/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/EasyCachingJsonSerializerOptions.cs new file mode 100644 index 00000000..63e80eb3 --- /dev/null +++ b/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/EasyCachingJsonSerializerOptions.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace EasyCaching.Serialization.SystemTextJson.Configurations +{ + public class EasyCachingJsonSerializerOptions + { + /// + /// Gets or sets the encoder to use when escaping strings, or null to use the default + /// encoder. + /// + public JavaScriptEncoder Encoder => JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + /// + /// Gets or sets a value that defines how comments are handled during deserialization. + /// + public JsonCommentHandling ReadCommentHandling => JsonCommentHandling.Disallow; + /// + /// Gets or sets a value that specifies the policy used to convert a property's name + /// on an object to another format, such as camel-casing, or null to leave property + /// names unchanged. + /// + public JsonNamingPolicy PropertyNamingPolicy => null; + /// + /// Gets or sets a value that determines whether a property's name uses a case-insensitive + /// comparison during deserialization. The default value is false. + /// + public bool PropertyNameCaseInsensitive => false; + /// + /// Specifies how number types should be handled when serializing or deserializing. + /// + public JsonNumberHandling NumberHandling => JsonNumberHandling.Strict; + /// + /// Gets or sets the maximum depth allowed when serializing or deserializing JSON, + /// with the default value of 0 indicating a maximum depth of 64. + /// + public int MaxDepth => 0; + /// + /// Determines whether fields are handled serialization and deserialization. The + /// default value is false. + /// + public bool IncludeFields => false; + /// + /// Gets a value that determines whether read-only properties are ignored during + /// serialization. The default value is false. + /// + public bool IgnoreReadOnlyProperties => false; + /// + /// Determines whether read-only fields are ignored during serialization. A property + /// is read-only if it isn't marked with the readonly keyword. The default value is false. + /// + public bool IgnoreReadOnlyFields => false; + /// + /// Gets or sets a value that determines whether null values are ignored during serialization + /// and deserialization. The default value is false. + /// + public bool IgnoreNullValues = false; + /// + /// Gets or sets the policy used to convert a System.Collections.IDictionary key's + /// name to another format, such as camel-casing. + /// + public JsonNamingPolicy DictionaryKeyPolicy => null; + /// + /// Specifies a condition to determine when properties with default values are ignored + /// during serialization or deserialization. The default value is System.Text.Json.Serialization.JsonIgnoreCondition.Never. + /// + public JsonIgnoreCondition DefaultIgnoreCondition => JsonIgnoreCondition.Never; + /// + /// Gets or sets the default buffer size, in bytes, to use when creating temporary buffers. + /// + public int DefaultBufferSize => 16 * 1024; + /// + /// Gets the list of user-defined converters that were registered. + /// + public IList Converters => new List(); + /// + /// Get or sets a value that indicates whether an extra comma at the end of a list + /// of JSON values in an object or array is allowed (and ignored) within the JSON + /// payload being deserialized. + /// + public bool AllowTrailingCommas => false; + /// + /// Configures how object references are handled when reading and writing JSON. + /// + public ReferenceHandler ReferenceHandler => null; + /// + /// Gets or sets a value that defines whether JSON should use pretty printing. By + /// default, JSON is serialized without any extra white space. + /// + public bool WriteIndented => false; + } +} diff --git a/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/EasyCachingOptionsExtensions.cs b/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/EasyCachingOptionsExtensions.cs new file mode 100644 index 00000000..885572a2 --- /dev/null +++ b/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/EasyCachingOptionsExtensions.cs @@ -0,0 +1,71 @@ +using EasyCaching.Core.Configurations; +using System; +using System.Linq; +using System.Text.Json; + +namespace EasyCaching.Serialization.SystemTextJson.Configurations +{ + + /// + /// EasyCaching options extensions. + /// + public static class EasyCachingOptionsExtensions + { + /// + /// Withs the json serializer. + /// + /// Options. + /// The name of this serializer instance. + public static EasyCachingOptions WithSystemTextJson(this EasyCachingOptions options, string name = "json") => options.WithSystemTextJson(configure: x => { }, name); + + /// + /// Withs the json serializer. + /// + /// Options. + /// Configure serializer settings. + /// The name of this serializer instance. + public static EasyCachingOptions WithSystemTextJson(this EasyCachingOptions options, Action configure, string name) + { + var easyCachingJsonSerializerOptions = new EasyCachingJsonSerializerOptions(); + + configure(easyCachingJsonSerializerOptions); + + void jsonSerializerSettings(JsonSerializerOptions x) + { + x.MaxDepth = easyCachingJsonSerializerOptions.MaxDepth; + x.AllowTrailingCommas = easyCachingJsonSerializerOptions.AllowTrailingCommas; + x.Converters.Union(easyCachingJsonSerializerOptions.Converters); + x.DefaultBufferSize = easyCachingJsonSerializerOptions.DefaultBufferSize; + x.DefaultIgnoreCondition = easyCachingJsonSerializerOptions.DefaultIgnoreCondition; + x.DictionaryKeyPolicy = easyCachingJsonSerializerOptions.DictionaryKeyPolicy; + x.Encoder = easyCachingJsonSerializerOptions.Encoder; + x.IgnoreReadOnlyFields = easyCachingJsonSerializerOptions.IgnoreReadOnlyFields; + x.IgnoreReadOnlyProperties = easyCachingJsonSerializerOptions.IgnoreReadOnlyProperties; + x.IncludeFields = easyCachingJsonSerializerOptions.IncludeFields; + x.NumberHandling = easyCachingJsonSerializerOptions.NumberHandling; + x.PropertyNameCaseInsensitive = easyCachingJsonSerializerOptions.PropertyNameCaseInsensitive; + x.PropertyNamingPolicy = easyCachingJsonSerializerOptions.PropertyNamingPolicy; + x.ReadCommentHandling = easyCachingJsonSerializerOptions.ReadCommentHandling; + x.ReferenceHandler = easyCachingJsonSerializerOptions.ReferenceHandler; + x.WriteIndented = easyCachingJsonSerializerOptions.WriteIndented; + } + + options.RegisterExtension(new JsonOptionsExtension(name, jsonSerializerSettings)); + + return options; + } + + /// + /// Withs the json serializer. + /// + /// Options. + /// Configure serializer settings. + /// The name of this serializer instance. + public static EasyCachingOptions WithSystemTextJson(this EasyCachingOptions options, Action jsonSerializerSettingsConfigure, string name) + { + options.RegisterExtension(new JsonOptionsExtension(name, jsonSerializerSettingsConfigure)); + + return options; + } + } +} \ No newline at end of file diff --git a/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/JsonOptionsExtension.cs b/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/JsonOptionsExtension.cs new file mode 100644 index 00000000..cf61886f --- /dev/null +++ b/serialization/EasyCaching.Serialization.SystemTextJson/Configurations/JsonOptionsExtension.cs @@ -0,0 +1,51 @@ +using EasyCaching.Core.Configurations; +using EasyCaching.Core.Serialization; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Text.Json; + +namespace EasyCaching.Serialization.SystemTextJson +{ + /// + /// Json options extension. + /// + internal sealed class JsonOptionsExtension : IEasyCachingOptionsExtension + { + /// + /// The name. + /// + private readonly string _name; + + /// + /// The configure. + /// + private readonly Action _configure; + + /// + /// Initializes a new instance of the class. + /// + /// Name. + /// Configure. + public JsonOptionsExtension(string name, Action configure) + { + _name = name; + _configure = configure; + } + + public void AddServices(IServiceCollection services) + { + Action configure = x => { }; + + if (_configure != null) configure = _configure; + + services.AddOptions(); + services.Configure(_name, configure); + services.AddSingleton(x => + { + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(_name); + return new DefaultJsonSerializer(_name, options); + }); + } + } +} diff --git a/serialization/EasyCaching.Serialization.SystemTextJson/DefaultJsonSerializer.cs b/serialization/EasyCaching.Serialization.SystemTextJson/DefaultJsonSerializer.cs new file mode 100644 index 00000000..dc2a6b44 --- /dev/null +++ b/serialization/EasyCaching.Serialization.SystemTextJson/DefaultJsonSerializer.cs @@ -0,0 +1,118 @@ +using EasyCaching.Core.Internal; +using EasyCaching.Core.Serialization; +using System; +using System.IO; +using System.Text; +using System.Text.Json; + +namespace EasyCaching.Serialization.SystemTextJson +{ + /// + /// Default json serializer. + /// + public class DefaultJsonSerializer : IEasyCachingSerializer + { + /// + /// The json serializer. + /// + private readonly JsonSerializerOptions jsonSerializerOption; + /// + /// The name. + /// + private readonly string _name; + /// + /// Gets the name. + /// + /// The name. + public string Name => _name; + + /// + /// Initializes a new instance of the class. + /// + /// Name. + /// serializerSettings. + public DefaultJsonSerializer(string name, JsonSerializerOptions serializerSettings) + { + _name = name; + jsonSerializerOption = serializerSettings; + } + + /// + /// Deserialize the specified bytes. + /// + /// The deserialize. + /// Bytes. + /// The 1st type parameter. + public T Deserialize(byte[] bytes) + { + return JsonSerializer.Deserialize(bytes, jsonSerializerOption); + } + /// + /// Deserialize the specified bytes. + /// + /// The deserialize. + /// Bytes. + /// Type. + public object Deserialize(byte[] bytes, Type type) + { + return JsonSerializer.Deserialize(bytes, type, jsonSerializerOption); + } + /// + /// Deserializes the object. + /// + /// The object. + /// Value. + public object DeserializeObject(ArraySegment value) + { + var jr = new Utf8JsonReader(value); + jr.Read(); + if (jr.TokenType == JsonTokenType.StartArray) + { + jr.Read(); + var typeName = Encoding.UTF8.GetString(jr.ValueSpan.ToArray()); + var type = Type.GetType(typeName, throwOnError: true); + + jr.Read(); + return JsonSerializer.Deserialize(ref jr, type, jsonSerializerOption); + } + else + { + throw new InvalidDataException("JsonTranscoder only supports [\"TypeName\", object]"); + } + } + /// + //; Serialize the specified value. + /// + /// The serialize. + /// Value. + /// The 1st type parameter. + public byte[] Serialize(T value) + { + return JsonSerializer.SerializeToUtf8Bytes(value, jsonSerializerOption); + } + /// + /// Serializes the object. + /// + /// The object. + /// Object. + public ArraySegment SerializeObject(object obj) + { + var typeName = TypeHelper.BuildTypeName(obj.GetType()); + + using (var ms = new MemoryStream()) + using (var jw = new Utf8JsonWriter(ms)) + { + jw.WriteStartArray(); + jw.WriteStringValue(typeName); + + JsonSerializer.Serialize(jw, obj, jsonSerializerOption); + + jw.WriteEndArray(); + + jw.Flush(); + + return new ArraySegment(ms.ToArray(), 0, (int)ms.Length); + } + } + } +} diff --git a/serialization/EasyCaching.Serialization.SystemTextJson/EasyCaching.Serialization.SystemTextJson.csproj b/serialization/EasyCaching.Serialization.SystemTextJson/EasyCaching.Serialization.SystemTextJson.csproj new file mode 100644 index 00000000..9589b461 --- /dev/null +++ b/serialization/EasyCaching.Serialization.SystemTextJson/EasyCaching.Serialization.SystemTextJson.csproj @@ -0,0 +1,40 @@ + + + + + netstandard2.0 + EasyCaching Contributors + $(EasyCachingSTJsonPackageVersion) + + + A serialize library based on System.Text.Json + + Caching,Serialization,Json + https://github.com/dotnetcore/EasyCaching + LICENSE + https://github.com/dotnetcore/EasyCaching + https://github.com/dotnetcore/EasyCaching + nuget-icon.png + + $(EasyCachingSTJsonPackageNotes) + + + + + true + $(NoWarn);1591 + + + + + + + + + + + + + + + diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs index ed14392c..d833bbd3 100644 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Async.cs @@ -74,7 +74,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); var result = await _cache.GetAsync(cacheKey); - if (result != null) + if (result != null || _options.CacheNulls) { CacheStats.OnHit(); @@ -99,7 +99,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< } var item = await dataRetriever(); - if (item != null) + if (item != null || _options.CacheNulls) { await SetAsync(cacheKey, item, expiration); //remove mutex key @@ -176,8 +176,8 @@ public override async Task> BaseGetAsync(string cacheKey) return CacheValue.NoValue; } - } - + } + /// /// Gets the count. /// @@ -324,7 +324,7 @@ public override async Task BaseSetAllAsync(IDictionary value, Time public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -353,7 +353,7 @@ await _cache.SetAsync( public override async Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Geo.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Geo.cs index d7fc9c0e..45f1934d 100644 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Geo.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.Geo.cs @@ -13,11 +13,11 @@ public long GeoAdd(string cacheKey, List<(double longitude, double latitude, str ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); - var list = new List<(double longitude, double latitude, object member)>(); + var list = new List<(decimal longitude, decimal latitude, object member)>(); foreach (var item in values) { - list.Add((item.longitude, item.latitude, item.member)); + list.Add(((decimal longitude, decimal latitude, object member))(item.longitude, item.latitude, item.member)); } var res = _cache.GeoAdd(cacheKey, list.ToArray()); @@ -29,11 +29,11 @@ public async Task GeoAddAsync(string cacheKey, List<(double longitude, dou ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); - var list = new List<(double longitude, double latitude, object member)>(); + var list = new List<(decimal longitude, decimal latitude, object member)>(); foreach (var item in values) { - list.Add((item.longitude, item.latitude, item.member)); + list.Add(((decimal longitude, decimal latitude, object member))(item.longitude, item.latitude, item.member)); } var res = await _cache.GeoAddAsync(cacheKey, list.ToArray()); @@ -48,7 +48,7 @@ public async Task GeoAddAsync(string cacheKey, List<(double longitude, dou ArgumentCheck.NotNullOrWhiteSpace(unit, nameof(unit)); var res = _cache.GeoDist(cacheKey, member1, member2, GetGeoUnit(unit)); - return res; + return (double?)res; } public async Task GeoDistAsync(string cacheKey, string member1, string member2, string unit = "m") @@ -59,7 +59,7 @@ public async Task GeoAddAsync(string cacheKey, List<(double longitude, dou ArgumentCheck.NotNullOrWhiteSpace(unit, nameof(unit)); var res = await _cache.GeoDistAsync(cacheKey, member1, member2, GetGeoUnit(unit)); - return res; + return (double?)res; } public List GeoHash(string cacheKey, List members) @@ -92,7 +92,7 @@ public async Task> GeoHashAsync(string cacheKey, List membe return res.ToList(); } - public List<(double longitude, double latitude)?> GeoPos(string cacheKey, List members) + public List<(decimal longitude, decimal latitude)?> GeoPos(string cacheKey, List members) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullAndCountGTZero(members, nameof(members)); @@ -107,7 +107,7 @@ public async Task> GeoHashAsync(string cacheKey, List membe return res.ToList(); } - public async Task> GeoPosAsync(string cacheKey, List members) + public async Task> GeoPosAsync(string cacheKey, List members) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullAndCountGTZero(members, nameof(members)); diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs index 90d569f8..1c603ce3 100755 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.SortedSet.cs @@ -11,11 +11,11 @@ public long ZAdd(string cacheKey, Dictionary cacheValues) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var param = new List<(double, object)>(); + var param = new List<(decimal, object)>(); foreach (var item in cacheValues) { - param.Add((item.Value, _serializer.Serialize(item.Key))); + param.Add(((decimal, object))(item.Value, _serializer.Serialize(item.Key))); } var len = _cache.ZAdd(cacheKey, param.ToArray()); @@ -35,7 +35,7 @@ public long ZCount(string cacheKey, double min, double max) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var len = _cache.ZCount(cacheKey, min, max); + var len = _cache.ZCount(cacheKey, (decimal)min, (decimal)max); return len; } public double ZIncrBy(string cacheKey, string field, double val = 1) @@ -43,8 +43,8 @@ public double ZIncrBy(string cacheKey, string field, double val = 1) ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullOrWhiteSpace(field, nameof(field)); - var value = _cache.ZIncrBy(cacheKey, field, val); - return value; + var value = _cache.ZIncrBy(cacheKey, field, (decimal)val); + return (double)value; } public long ZLexCount(string cacheKey, string min, string max) { @@ -106,18 +106,18 @@ public long ZRem(string cacheKey, IList cacheValues) var score = _cache.ZScore(cacheKey, bytes); - return score; + return (double?)score; } public async Task ZAddAsync(string cacheKey, Dictionary cacheValues) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var param = new List<(double, object)>(); + var param = new List<(decimal, object)>(); foreach (var item in cacheValues) { - param.Add((item.Value, _serializer.Serialize(item.Key))); + param.Add(((decimal, object))(item.Value, _serializer.Serialize(item.Key))); } var len = await _cache.ZAddAsync(cacheKey, param.ToArray()); @@ -138,7 +138,7 @@ public async Task ZCountAsync(string cacheKey, double min, double max) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var len = await _cache.ZCountAsync(cacheKey, min, max); + var len = await _cache.ZCountAsync(cacheKey, (decimal)min, (decimal)max); return len; } @@ -147,8 +147,8 @@ public async Task ZIncrByAsync(string cacheKey, string field, double val ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullOrWhiteSpace(field, nameof(field)); - var value= await _cache.ZIncrByAsync(cacheKey, field, val); - return value; + var value= await _cache.ZIncrByAsync(cacheKey, field, (decimal)val); + return (double)value; } public async Task ZLexCountAsync(string cacheKey, string min, string max) @@ -210,7 +210,7 @@ public async Task ZRemAsync(string cacheKey, IList cacheValues) var score = await _cache.ZScoreAsync(cacheKey, bytes); - return score; + return (double?)score; } } diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.String.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.String.cs index ae298fdb..f87fbff4 100755 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.String.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.String.cs @@ -26,16 +26,16 @@ public double IncrByFloat(string cacheKey, double value = 1) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var res = _cache.IncrByFloat(cacheKey, value); - return res; + var res = _cache.IncrByFloat(cacheKey, (decimal)value); + return (double)res; } public async Task IncrByFloatAsync(string cacheKey, double value = 1) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var res = await _cache.IncrByFloatAsync(cacheKey, value); - return res; + var res = await _cache.IncrByFloatAsync(cacheKey, (decimal)value); + return (double)res; } public bool StringSet(string cacheKey, string cacheValue, System.TimeSpan? expiration, string when) diff --git a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs index 2c68fd59..f366f51b 100644 --- a/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs +++ b/src/EasyCaching.CSRedis/DefaultCSRedisCachingProvider.cs @@ -60,7 +60,7 @@ public DefaultCSRedisCachingProvider( RedisOptions options, ILoggerFactory loggerFactory = null) { - this._name = name; + this._name = name; this._options = options; this._logger = loggerFactory?.CreateLogger(); this._cache = clients.Single(x => x.Name.Equals(_name)); @@ -68,8 +68,8 @@ public DefaultCSRedisCachingProvider( this._serializer = !string.IsNullOrWhiteSpace(options.SerializerName) ? serializers.Single(x => x.Name.Equals(options.SerializerName)) - : serializers.FirstOrDefault(x => x.Name.Equals(_name)) ?? serializers.Single(x => x.Name.Equals(EasyCachingConstValue.DefaultSerializerName)); - + : serializers.FirstOrDefault(x => x.Name.Equals(_name)) ?? serializers.Single(x => x.Name.Equals(EasyCachingConstValue.DefaultSerializerName)); + this.ProviderName = this._name; this.ProviderType = CachingProviderType.Redis; this.ProviderStats = this._cacheStats; @@ -87,7 +87,8 @@ public DefaultCSRedisCachingProvider( ProviderType = ProviderType, SerializerName = options.SerializerName, SleepMs = options.SleepMs, - Serializer = _serializer + Serializer = _serializer, + CacheNulls = options.CacheNulls, }; } @@ -101,8 +102,8 @@ public override bool BaseExists(string cacheKey) ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); return _cache.Exists(cacheKey); - } - + } + /// /// Flush this instance. /// @@ -151,7 +152,7 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, } var item = dataRetriever(); - if (item != null) + if (item != null || _options.CacheNulls) { Set(cacheKey, item, expiration); //remove mutex key @@ -223,8 +224,8 @@ public override IDictionary> BaseGetAll(IEnumerable /// Handles the prefix of CacheKey. /// @@ -318,8 +319,8 @@ public override int BaseGetCount(string prefix = "") } return this.SearchRedisKeys(this.HandlePrefix(prefix)).Length; - } - + } + /// /// Remove the specified cacheKey. /// @@ -376,7 +377,7 @@ public override void BaseRemoveByPrefix(string prefix) public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -424,7 +425,7 @@ public override void BaseSetAll(IDictionary values, TimeSpan expir public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) diff --git a/src/EasyCaching.CSRedis/EasyCaching.CSRedis.csproj b/src/EasyCaching.CSRedis/EasyCaching.CSRedis.csproj index 4d0230c0..bb0eedb6 100644 --- a/src/EasyCaching.CSRedis/EasyCaching.CSRedis.csproj +++ b/src/EasyCaching.CSRedis/EasyCaching.CSRedis.csproj @@ -32,7 +32,7 @@ - + diff --git a/src/EasyCaching.Core/Configurations/BaseProviderOptions.cs b/src/EasyCaching.Core/Configurations/BaseProviderOptions.cs index 6937fda3..3ec88f96 100644 --- a/src/EasyCaching.Core/Configurations/BaseProviderOptions.cs +++ b/src/EasyCaching.Core/Configurations/BaseProviderOptions.cs @@ -40,5 +40,10 @@ public class BaseProviderOptions /// Mainly for distributed cache /// public string SerializerName { get; set; } + + /// + /// Get or sets whether null values should be cached, default is false. + /// + public bool CacheNulls { get; set; } = false; } } diff --git a/src/EasyCaching.Core/IRedisCachingProvider.cs b/src/EasyCaching.Core/IRedisCachingProvider.cs index 5bdb9e18..c8183813 100644 --- a/src/EasyCaching.Core/IRedisCachingProvider.cs +++ b/src/EasyCaching.Core/IRedisCachingProvider.cs @@ -916,14 +916,14 @@ public interface IRedisCachingProvider /// /// /// - List<(double longitude, double latitude)?> GeoPos(string cacheKey, List members); + List<(decimal longitude, decimal latitude)?> GeoPos(string cacheKey, List members); /// /// https://redis.io/commands/geopos /// /// /// /// - Task> GeoPosAsync(string cacheKey, List members); + Task> GeoPosAsync(string cacheKey, List members); #endregion object Eval(string script, string cacheKey, List args); diff --git a/src/EasyCaching.Core/Internal/ArgumentCheck.cs b/src/EasyCaching.Core/Internal/ArgumentCheck.cs index 769319f1..f06d25fd 100644 --- a/src/EasyCaching.Core/Internal/ArgumentCheck.cs +++ b/src/EasyCaching.Core/Internal/ArgumentCheck.cs @@ -23,6 +23,21 @@ public static void NotNull(object argument, string argumentName) throw new ArgumentNullException(argumentName); } } + + /// + /// Validates that is not null , otherwise throws an exception. + /// + /// Argument. + /// Argument name. + /// Allow nulls. + /// + public static void NotNull(object argument, string argumentName, bool allowNulls) + { + if (argument == null && !allowNulls) + { + throw new ArgumentNullException(argumentName); + } + } /// /// Validates that is not null or white space , otherwise throws an exception. diff --git a/src/EasyCaching.Core/Internal/ProviderInfo.cs b/src/EasyCaching.Core/Internal/ProviderInfo.cs index 080b834f..b259fd1e 100644 --- a/src/EasyCaching.Core/Internal/ProviderInfo.cs +++ b/src/EasyCaching.Core/Internal/ProviderInfo.cs @@ -41,5 +41,10 @@ public class ProviderInfo /// Mainly for distributed cache /// public string SerializerName { get; set; } + + /// + /// Get or sets whether null values should be cached, default is false. + /// + public bool CacheNulls { get; set; } = false; } } diff --git a/src/EasyCaching.Core/Serialization/DefaultBinaryFormatterSerializer.cs b/src/EasyCaching.Core/Serialization/DefaultBinaryFormatterSerializer.cs index fb6a72a2..937b01ba 100644 --- a/src/EasyCaching.Core/Serialization/DefaultBinaryFormatterSerializer.cs +++ b/src/EasyCaching.Core/Serialization/DefaultBinaryFormatterSerializer.cs @@ -23,6 +23,11 @@ public class DefaultBinaryFormatterSerializer : IEasyCachingSerializer /// The 1st type parameter. public T Deserialize(byte[] bytes) { + if (bytes.Length == 0) + { + return (T)(object) null; + } + using (var ms = new MemoryStream(bytes)) { return (T)(new BinaryFormatter().Deserialize(ms)); @@ -37,6 +42,11 @@ public T Deserialize(byte[] bytes) /// Type. public object Deserialize(byte[] bytes, Type type) { + if (bytes.Length == 0) + { + return null; + } + using (var ms = new MemoryStream(bytes)) { return (new BinaryFormatter().Deserialize(ms)); @@ -64,6 +74,11 @@ public object DeserializeObject(ArraySegment value) /// The 1st type parameter. public byte[] Serialize(T value) { + if (value == null) + { + return Array.Empty(); + } + using (var ms = new MemoryStream()) { new BinaryFormatter().Serialize(ms, value); @@ -78,6 +93,11 @@ public byte[] Serialize(T value) /// Object. public ArraySegment SerializeObject(object obj) { + if (obj == null) + { + return new ArraySegment(Array.Empty()); + } + using (var ms = new MemoryStream()) { new BinaryFormatter().Serialize(ms, obj); diff --git a/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs b/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs index 554c0e40..9f26dbfb 100644 --- a/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs +++ b/src/EasyCaching.Disk/DefaultDiskCachingProvider.Async.cs @@ -6,8 +6,8 @@ using System.Linq; using System.Threading.Tasks; using EasyCaching.Core; - using MessagePack; - using MessagePack.Resolvers; + using MessagePack; + using MessagePack.Resolvers; using Microsoft.Extensions.Logging; public partial class DefaultDiskCachingProvider : EasyCachingAbstractProvider @@ -96,7 +96,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< /* GetAsync_Parallel_Should_Succeed always failed in CI due to this reason, but succeed in local PC - MessagePack.MessagePackSerializationException : Failed to deserialize EasyCaching.Disk.DiskCacheValue value. + MessagePack.MessagePackSerializationException : Failed to deserialize EasyCaching.Disk.DiskCacheValue value. ---- System.IO.EndOfStreamException : Attempted to read past the end of the stream. */ var cached = await GetDiskCacheValueAsync(path); @@ -130,7 +130,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< var res = await dataRetriever(); - if (res != null) + if (res != null || _options.CacheNulls) { await SetAsync(cacheKey, res, expiration); //remove mutex key @@ -143,8 +143,8 @@ public override async Task> BaseGetAsync(string cacheKey, Func< _cacheKeysMap.TryRemove($"{cacheKey}_Lock", out _); return CacheValue.NoValue; } - } - + } + public override Task BaseGetCountAsync(string prefix = "") { if (string.IsNullOrWhiteSpace(prefix)) @@ -392,7 +392,7 @@ public override async Task BaseSetAllAsync(IDictionary values, Tim public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); var (path, fileName) = GetFilePath(cacheKey); @@ -411,7 +411,7 @@ public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSp public override async Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); var (path, fileName) = GetFilePath(cacheKey); diff --git a/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs b/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs index 85ede11a..1d60cf4c 100644 --- a/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs +++ b/src/EasyCaching.Disk/DefaultDiskCachingProvider.cs @@ -9,8 +9,8 @@ using System.Text; using System.Threading; using EasyCaching.Core; - using MessagePack; - using MessagePack.Resolvers; + using MessagePack; + using MessagePack.Resolvers; using Microsoft.Extensions.Logging; public partial class DefaultDiskCachingProvider : EasyCachingAbstractProvider @@ -69,7 +69,8 @@ public DefaultDiskCachingProvider(string name, ProviderName = ProviderName, ProviderType = ProviderType, SerializerName = options.SerializerName, - SleepMs = options.SleepMs + SleepMs = options.SleepMs, + CacheNulls = options.CacheNulls, }; Init(); @@ -173,8 +174,8 @@ public override bool BaseExists(string cacheKey) var val = GetDiskCacheValue(path); return val.Expiration > DateTimeOffset.UtcNow; - } - + } + public override void BaseFlush() { if (_options.EnableLogging) @@ -227,7 +228,7 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, var res = dataRetriever(); - if (res != null) + if (res != null || _options.CacheNulls) { Set(cacheKey, res, expiration); // remove mutex key @@ -323,8 +324,8 @@ public override IDictionary> BaseGetAll(IEnumerable> BaseGetByPrefix(string prefix) { ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); @@ -398,8 +399,8 @@ public override TimeSpan BaseGetExpiration(string cacheKey) var cached = GetDiskCacheValue(path); return cached.Expiration.Subtract(DateTimeOffset.UtcNow); - } - + } + public override void BaseRemove(string cacheKey) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); @@ -456,12 +457,12 @@ public override void BaseRemoveByPrefix(string prefix) _cacheKeysMap.TryRemove(item, out _); } } - } - + } + public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); var (path, fileName) = GetFilePath(cacheKey); @@ -502,12 +503,12 @@ public override void BaseSetAll(IDictionary values, TimeSpan expir } } - } - + } + public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); var (path, fileName) = GetFilePath(cacheKey); @@ -530,8 +531,8 @@ public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expir AppendKey(cacheKey, fileName); return true; } - } - + } + private (string path, string md5Name) GetFilePath(string key) { var md5FolderName = GetMd5Str(_name); @@ -597,9 +598,9 @@ private bool DeleteFileWithRetry(string path) private DiskCacheValue GetDiskCacheValue(string path) { using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - var cached = MessagePackSerializer.Deserialize(stream, MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolver.Instance)); - + { + var cached = MessagePackSerializer.Deserialize(stream, MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolver.Instance)); + return cached; } } @@ -607,8 +608,8 @@ private DiskCacheValue GetDiskCacheValue(string path) private void AppendKey(string key, string md5Key) { _cacheKeysMap.TryAdd(key, md5Key); - } - + } + private byte[] BuildDiskCacheValue(T t, TimeSpan ts) { var value = MessagePackSerializer.Serialize(t, MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolver.Instance)); diff --git a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs index 4a1b86f6..6d30b07c 100644 --- a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs +++ b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.Async.cs @@ -50,7 +50,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< var res = await dataRetriever(); - if (res != null) + if (res != null || _options.CacheNulls) { await SetAsync(cacheKey, res, expiration); //remove mutex key @@ -96,8 +96,8 @@ public override async Task> BaseGetAsync(string cacheKey) return CacheValue.NoValue; } - } - + } + /// /// Gets the count. /// @@ -163,7 +163,7 @@ public override async Task BaseRemoveAsync(string cacheKey) public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -294,7 +294,7 @@ public override async Task BaseFlushAsync() public override Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); //var val = new CacheValue(cacheValue, true, expiration); diff --git a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs index 4fd0e2b3..04eb1ddb 100644 --- a/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs +++ b/src/EasyCaching.InMemory/DefaultInMemoryCachingProvider.cs @@ -74,7 +74,8 @@ public DefaultInMemoryCachingProvider( ProviderName = ProviderName, ProviderType = ProviderType, SerializerName = options.SerializerName, - SleepMs = options.SleepMs + SleepMs = options.SleepMs, + CacheNulls = options.CacheNulls, }; } @@ -118,7 +119,7 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, var res = dataRetriever(); - if (res != null) + if (res != null || _options.CacheNulls) { Set(cacheKey, res, expiration); //remove mutex key @@ -188,7 +189,7 @@ public override void BaseRemove(string cacheKey) public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -322,7 +323,7 @@ public override void BaseFlush() public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); //var val = new CacheValue(cacheValue, true, expiration); diff --git a/src/EasyCaching.LiteDB/Configurations/EasyCachingOptionsExtensions.cs b/src/EasyCaching.LiteDB/Configurations/EasyCachingOptionsExtensions.cs index 7e3e36f2..3af88846 100644 --- a/src/EasyCaching.LiteDB/Configurations/EasyCachingOptionsExtensions.cs +++ b/src/EasyCaching.LiteDB/Configurations/EasyCachingOptionsExtensions.cs @@ -2,15 +2,15 @@ { using System; using EasyCaching.Core; - using EasyCaching.Core.Configurations; - using EasyCaching.LiteDB; + using EasyCaching.Core.Configurations; + using EasyCaching.LiteDB; using Microsoft.Extensions.Configuration; /// /// EasyCaching options extensions. /// public static class EasyCachingOptionsExtensions - { + { /// /// Uses the LiteDB provider (specify the config via hard code). /// diff --git a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs index 094c9fe3..a14a0997 100644 --- a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs +++ b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.Async.cs @@ -34,13 +34,13 @@ public override async Task BaseExistsAsync(string cacheKey) public override async Task> BaseGetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); - return await Task.Run(() => - { - return BaseGet(cacheKey, () => - { - return dataRetriever.Invoke().GetAwaiter().GetResult(); - }, expiration); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + return await Task.Run(() => + { + return BaseGet(cacheKey, () => + { + return dataRetriever.Invoke().GetAwaiter().GetResult(); + }, expiration); }); } @@ -52,7 +52,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< /// The 1st type parameter. public override async Task> BaseGetAsync(string cacheKey) { - ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); return await Task.Run(() => BaseGet(cacheKey)); } @@ -74,7 +74,7 @@ public override async Task BaseGetCountAsync(string prefix = "") /// Object Type. public override async Task BaseGetAsync(string cacheKey, Type type) { - ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); return await Task.Run(() => System.Convert.ChangeType(BaseGet(cacheKey).Value, type)); } @@ -101,11 +101,11 @@ public override async Task BaseRemoveAsync(string cacheKey) public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); await Task.Run(() => BaseSet(cacheKey, cacheValue, expiration)); - } - + } + /// /// Removes cached item by cachekey's prefix async. /// @@ -118,8 +118,8 @@ public override async Task BaseRemoveByPrefixAsync(string prefix) _logger?.LogInformation($"RemoveByPrefixAsync : prefix = {prefix}"); await Task.Run(() => BaseRemoveByPrefix(prefix)); - } - + } + /// /// Sets all async. /// @@ -128,10 +128,10 @@ public override async Task BaseRemoveByPrefixAsync(string prefix) /// Expiration. /// The 1st type parameter. public override async Task BaseSetAllAsync(IDictionary values, TimeSpan expiration) - { + { await Task.Run(() => BaseSetAll(values, expiration)); - } - + } + /// /// Gets all async. /// @@ -140,7 +140,7 @@ public override async Task BaseSetAllAsync(IDictionary values, Tim /// The 1st type parameter. public override async Task>> BaseGetAllAsync(IEnumerable cacheKeys) { - ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); return await Task.Run(() => BaseGetAll(cacheKeys)); } @@ -155,8 +155,8 @@ public override async Task>> BaseGetByPrefixAs ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); return await Task.Run(() => BaseGetByPrefix(prefix)); - } - + } + /// /// Removes all async. /// @@ -164,7 +164,7 @@ public override async Task>> BaseGetByPrefixAs /// Cache keys. public override async Task BaseRemoveAllAsync(IEnumerable cacheKeys) { - ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); await Task.Run(() => BaseRemoveAll(cacheKeys)); } @@ -185,8 +185,8 @@ public override async Task BaseRemoveAllAsync(IEnumerable cacheKeys) public override async Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); - ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); return await Task.Run(() => BaseTrySet(cacheKey, cacheValue, expiration)); } @@ -197,8 +197,8 @@ public override async Task BaseTrySetAsync(string cacheKey, T cacheValu /// expiration public override async Task BaseGetExpirationAsync(string cacheKey) { - ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); return await Task.Run(() => BaseGetExpiration(cacheKey)); - } + } } } \ No newline at end of file diff --git a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs index 500ef93c..a919ae72 100644 --- a/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs +++ b/src/EasyCaching.LiteDB/DefaultLiteDBCachingProvider.cs @@ -1,12 +1,12 @@ namespace EasyCaching.LiteDB { - using EasyCaching.Core; - using global::LiteDB; + using EasyCaching.Core; + using global::LiteDB; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; - + /// /// LiteDBCaching provider. /// @@ -25,8 +25,8 @@ public partial class DefaultLiteDBCachingProvider : EasyCachingAbstractProvider /// /// The logger. /// - private readonly ILogger _logger; - + private readonly ILogger _logger; + private readonly LiteDatabase _litedb; /// @@ -76,29 +76,30 @@ public DefaultLiteDBCachingProvider( ProviderName = ProviderName, ProviderType = ProviderType, SerializerName = options.SerializerName, - SleepMs = options.SleepMs + SleepMs = options.SleepMs, + CacheNulls = options.CacheNulls }; InitDb(); - } - - /// - /// init database - /// - private void InitDb() - { - lock (_litedb) - { - _litedb.Checkpoint(); - _litedb.Rebuild(); - lock (_cache) - { - _cache.EnsureIndex(c => c.cachekey); - } - } - - } - + } + + /// + /// init database + /// + private void InitDb() + { + lock (_litedb) + { + _litedb.Checkpoint(); + _litedb.Rebuild(); + lock (_cache) + { + _cache.EnsureIndex(c => c.cachekey); + } + } + + } + /// /// Exists the specified cacheKey. /// @@ -108,7 +109,7 @@ public override bool BaseExists(string cacheKey) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var dbResult = _cache.Count(fc => fc.cachekey == cacheKey); + var dbResult = _cache.Count(fc => fc.cachekey == cacheKey && fc.expiration > DateTimeOffset.Now.ToUnixTimeSeconds()); return dbResult > 0; } @@ -126,34 +127,22 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); - var dbResult = _cache.FindOne(c => c.cachekey == cacheKey)?.cachevalue; + var result = BaseGet(cacheKey); - if (!string.IsNullOrWhiteSpace(dbResult)) + if (result.HasValue) { - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); - - CacheStats.OnHit(); - - return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); + return result; } - - CacheStats.OnMiss(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); - + var item = dataRetriever(); - if (item != null) + if (item != null || _options.CacheNulls) { Set(cacheKey, item, expiration); - return new CacheValue(item, true); - } - else - { - return CacheValue.NoValue; + result = new CacheValue(item, true); } + + return result; } /// @@ -164,18 +153,20 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, /// The 1st type parameter. public override CacheValue BaseGet(string cacheKey) { - ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - - var dbResult = _cache.FindOne(c => c.cachekey == cacheKey)?.cachevalue; + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - if (!string.IsNullOrWhiteSpace(dbResult)) - { - CacheStats.OnHit(); + var cacheItem = _cache.FindOne(c => c.cachekey == cacheKey && c.expiration > DateTimeOffset.Now.ToUnixTimeSeconds()); + if (cacheItem != null || _options.CacheNulls) + { if (_options.EnableLogging) _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); - return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); + CacheStats.OnHit(); + + return string.IsNullOrWhiteSpace(cacheItem?.cachevalue) + ? CacheValue.Null + : new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(cacheItem.cachevalue), true); } else { @@ -210,24 +201,23 @@ public override void BaseRemove(string cacheKey) public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) { var addSec = new Random().Next(1, MaxRdSecond); expiration.Add(new TimeSpan(0, 0, addSec)); - } - var exp = expiration.Ticks / 10000000; + } _cache.Upsert(new CacheItem { cachekey = cacheKey, name = _name, cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(cacheValue), - expiration = expiration.Ticks / 10000000 + expiration = DateTimeOffset.UtcNow.Add(expiration).ToUnixTimeSeconds() }); - } - + } + /// /// Removes cached item by cachekey's prefix. /// @@ -251,28 +241,28 @@ public override void BaseRemoveByPrefix(string prefix) public override void BaseSetAll(IDictionary values, TimeSpan expiration) { ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); - ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); - _litedb.BeginTrans(); - try - { - foreach (var item in values) - { - _cache.Upsert(new CacheItem - { - cachekey = item.Key, - name = _name, - cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(item.Value), - expiration = expiration.Ticks / 10000000 - }); - } - _litedb.Commit(); - } - catch (Exception) - { - _litedb.Rollback(); + ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); + _litedb.BeginTrans(); + try + { + foreach (var item in values) + { + _cache.Upsert(new CacheItem + { + cachekey = item.Key, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(item.Value), + expiration = DateTimeOffset.UtcNow.Add(expiration).ToUnixTimeSeconds() + }); + } + _litedb.Commit(); } - } - + catch (Exception) + { + _litedb.Rollback(); + } + } + /// /// Gets all. /// @@ -281,9 +271,9 @@ public override void BaseSetAll(IDictionary values, TimeSpan expir /// The 1st type parameter. public override IDictionary> BaseGetAll(IEnumerable cacheKeys) { - ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); - var lst = cacheKeys.ToList(); - var list = _cache.Find(c => lst.Contains(c.cachekey)).ToList(); + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + var lst = cacheKeys.ToList(); + var list = _cache.Find(c => lst.Contains(c.cachekey) && c.expiration > DateTimeOffset.Now.ToUnixTimeSeconds()).ToList(); return GetDict(list); } @@ -315,21 +305,21 @@ private IDictionary> GetDict(List list) public override IDictionary> BaseGetByPrefix(string prefix) { ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); - var list = _cache.Find(c => c.cachekey.StartsWith(prefix)).ToList(); + var list = _cache.Find(c => c.cachekey.StartsWith(prefix) && c.expiration > DateTimeOffset.Now.ToUnixTimeSeconds()).ToList(); return GetDict(list); - } - + } + /// /// Removes all. /// /// Cache keys. public override void BaseRemoveAll(IEnumerable cacheKeys) { - ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); - var lst = cacheKeys.ToList(); - // _litedb.BeginTrans(); - _cache.DeleteMany(c => lst.Contains(c.cachekey)); - // _litedb.Commit(); + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + var lst = cacheKeys.ToList(); + // _litedb.BeginTrans(); + _cache.DeleteMany(c => lst.Contains(c.cachekey)); + // _litedb.Commit(); } /// @@ -341,11 +331,11 @@ public override int BaseGetCount(string prefix = "") { if (string.IsNullOrWhiteSpace(prefix)) { - return _cache.Count(); + return _cache.Count(c => c.expiration > DateTimeOffset.Now.ToUnixTimeSeconds()); } else { - return _cache.Count(c => c.cachekey.StartsWith(prefix)); + return _cache.Count(c => c.cachekey.StartsWith(prefix) && c.expiration > DateTimeOffset.Now.ToUnixTimeSeconds()); } } @@ -365,7 +355,7 @@ public override int BaseGetCount(string prefix = "") public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -373,20 +363,20 @@ public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expir var addSec = new Random().Next(1, MaxRdSecond); expiration.Add(new TimeSpan(0, 0, addSec)); } - var exp = expiration.Ticks / 10000000; - var r = _cache.FindOne(c => c.cachekey == cacheKey && c.expiration == exp); + + var r = _cache.FindOne(c => c.cachekey == cacheKey && c.expiration > DateTimeOffset.Now.ToUnixTimeSeconds()); bool result = false; - if (r == null) - { - var rows = _cache.Insert(new CacheItem - { - cachekey = cacheKey, - name = _name, - cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(cacheValue), - expiration = expiration.Ticks / 10000000 - }); - result = rows != null; - } + if (r == null) + { + var rows = _cache.Insert(new CacheItem + { + cachekey = cacheKey, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(cacheValue), + expiration = DateTimeOffset.UtcNow.Add(expiration).ToUnixTimeSeconds() + }); + result = rows != null; + } return result; } @@ -399,9 +389,8 @@ public override TimeSpan BaseGetExpiration(string cacheKey) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var time = _cache.FindOne(c => c.cachekey == cacheKey)?.expiration; - if (time == null) return TimeSpan.Zero; - else return TimeSpan.FromSeconds((double)time); + var time = _cache.FindOne(c => c.cachekey == cacheKey )?.expiration; + return time == null ? TimeSpan.Zero : DateTimeOffset.FromUnixTimeSeconds((long)time).Subtract(DateTimeOffset.UtcNow); } /// diff --git a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs index 683082c3..9de69f45 100644 --- a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs +++ b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.Async.cs @@ -25,23 +25,15 @@ public override async Task> BaseGetAsync(string cacheKey, Func< ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); - var result = await _memcachedClient.GetAsync(this.HandleCacheKey(cacheKey)); - if (result.Success) - { - CacheStats.OnHit(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + var result = await BaseGetAsync(cacheKey); - return new CacheValue(result.Value, true); + if (result.HasValue) + { + return result; } - - CacheStats.OnMiss(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); - - var flag = await _memcachedClient.StoreAsync(Enyim.Caching.Memcached.StoreMode.Add, this.HandleCacheKey($"{cacheKey}_Lock"), 1, TimeSpan.FromMilliseconds(_options.LockMs)); + + var flag = await _memcachedClient.StoreAsync(Enyim.Caching.Memcached.StoreMode.Add, + this.HandleCacheKey($"{cacheKey}_Lock"), 1, TimeSpan.FromMilliseconds(_options.LockMs)); if (!flag) { @@ -50,7 +42,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< } var item = await dataRetriever(); - if (item != null) + if (item != null || _options.CacheNulls) { await this.SetAsync(cacheKey, item, expiration); await _memcachedClient.RemoveAsync(this.HandleCacheKey($"{cacheKey}_Lock")); @@ -73,23 +65,19 @@ public override async Task> BaseGetAsync(string cacheKey) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - var result = await _memcachedClient.GetAsync(this.HandleCacheKey(cacheKey)); + var result = await _memcachedClient.GetAsync(this.HandleCacheKey(cacheKey)); + if (result.Success) { - CacheStats.OnHit(); + OnCacheHit(cacheKey); - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); - - return new CacheValue(result.Value, true); + return NullValue.Equals(result.Value) + ? CacheValue.Null + : new CacheValue((T)result.Value, true); } else { - CacheStats.OnMiss(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); - + OnCacheMiss(cacheKey); return CacheValue.NoValue; } } @@ -125,20 +113,12 @@ public override async Task BaseGetAsync(string cacheKey, Type type) var result = await Task.FromResult(_memcachedClient.Get(this.HandleCacheKey(cacheKey))); if (result != null) { - CacheStats.OnHit(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); - + OnCacheHit(cacheKey); return result; } else { - CacheStats.OnMiss(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); - + OnCacheMiss(cacheKey); return null; } } @@ -166,7 +146,7 @@ public override async Task BaseRemoveAsync(string cacheKey) public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -175,8 +155,12 @@ public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSp expiration = expiration.Add(TimeSpan.FromSeconds(addSec)); } - await _memcachedClient.StoreAsync(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(cacheKey), cacheValue, expiration); - } + await _memcachedClient.StoreAsync( + Enyim.Caching.Memcached.StoreMode.Set, + this.HandleCacheKey(cacheKey), + this.ConvertToStoredValue(cacheValue), + expiration); + } /// /// Existses the specified cacheKey async. @@ -215,7 +199,11 @@ public override async Task BaseRemoveByPrefixAsync(string prefix) { newValue = string.Concat(newValue, new Random().Next(9).ToString()); } - await _memcachedClient.StoreAsync(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(prefix), newValue, new TimeSpan(0, 0, 0)); + await _memcachedClient.StoreAsync( + Enyim.Caching.Memcached.StoreMode.Set, + this.HandleCacheKey(prefix), + newValue, + new TimeSpan(0, 0, 0)); } /// @@ -248,18 +236,12 @@ public override async Task>> BaseGetAllAsync(cacheKeys); - var result = new Dictionary>(); - - foreach (var item in values) - { - if (item.Value != null) - result.Add(item.Key, new CacheValue(item.Value, true)); - else - result.Add(item.Key, CacheValue.NoValue); - } + var values = await _memcachedClient.GetAsync(cacheKeys); - return result; + return values + .ToDictionary( + pair => pair.Key, + pair => ConvertFromStoredValue(pair.Value)); } /// @@ -312,7 +294,7 @@ public override async Task BaseFlushAsync() public override Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -321,7 +303,11 @@ public override Task BaseTrySetAsync(string cacheKey, T cacheValue, Tim expiration = expiration.Add(TimeSpan.FromSeconds(addSec)); } - return _memcachedClient.StoreAsync(Enyim.Caching.Memcached.StoreMode.Add, this.HandleCacheKey(cacheKey), cacheValue, expiration); + return _memcachedClient.StoreAsync( + Enyim.Caching.Memcached.StoreMode.Add, + this.HandleCacheKey(cacheKey), + ConvertToStoredValue(cacheValue), + expiration); } public override Task BaseGetExpirationAsync(string cacheKey) diff --git a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs index 9f36ef80..75388c5d 100644 --- a/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs +++ b/src/EasyCaching.Memcached/DefaultMemcachedCachingProvider.cs @@ -13,6 +13,8 @@ /// public partial class DefaultMemcachedCachingProvider : EasyCachingAbstractProvider { + public const string NullValue = "{NULL}"; + /// /// The memcached client. /// @@ -75,7 +77,8 @@ public DefaultMemcachedCachingProvider( ProviderName = ProviderName, ProviderType = ProviderType, SerializerName = options.SerializerName, - SleepMs = options.SleepMs, + SleepMs = options.SleepMs, + CacheNulls = options.CacheNulls, }; } @@ -92,21 +95,13 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); - if (_memcachedClient.Get(this.HandleCacheKey(cacheKey)) is T result) - { - CacheStats.OnHit(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + var result = BaseGet(cacheKey); - return new CacheValue(result, true); + if (result.HasValue) + { + return result; } - - CacheStats.OnMiss(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); - + var flag = _memcachedClient.Store(Enyim.Caching.Memcached.StoreMode.Add, this.HandleCacheKey($"{cacheKey}_Lock"), 1, TimeSpan.FromMilliseconds(_options.LockMs)); if (!flag) @@ -116,7 +111,7 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, } var item = dataRetriever(); - if (item != null) + if (item != null || _options.CacheNulls) { this.Set(cacheKey, item, expiration); _memcachedClient.Remove(this.HandleCacheKey($"{cacheKey}_Lock")); @@ -139,24 +134,18 @@ public override CacheValue BaseGet(string cacheKey) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - if (_memcachedClient.Get(this.HandleCacheKey(cacheKey)) is T result) - { - CacheStats.OnHit(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + var result = ConvertFromStoredValue(_memcachedClient.Get(this.HandleCacheKey(cacheKey))); - return new CacheValue(result, true); + if (result.HasValue) + { + OnCacheHit(cacheKey); } else { - CacheStats.OnMiss(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); - - return CacheValue.NoValue; + OnCacheMiss(cacheKey); } + + return result; } /// @@ -182,7 +171,7 @@ public override void BaseRemove(string cacheKey) public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -190,8 +179,12 @@ public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expirati var addSec = new Random().Next(1, MaxRdSecond); expiration = expiration.Add(TimeSpan.FromSeconds(addSec)); } - - _memcachedClient.Store(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(cacheKey), cacheValue, expiration); + + _memcachedClient.Store( + Enyim.Caching.Memcached.StoreMode.Set, + this.HandleCacheKey(cacheKey), + this.ConvertToStoredValue(cacheValue), + expiration); } /// @@ -230,7 +223,11 @@ public override void BaseRemoveByPrefix(string prefix) { newValue = string.Concat(newValue, new Random().Next(9).ToString()); } - _memcachedClient.Store(Enyim.Caching.Memcached.StoreMode.Set, this.HandleCacheKey(prefix), newValue, new TimeSpan(0, 0, 0)); + _memcachedClient.Store( + Enyim.Caching.Memcached.StoreMode.Set, + this.HandleCacheKey(prefix), + newValue, + new TimeSpan(0, 0, 0)); } /// @@ -253,6 +250,18 @@ private string HandleCacheKey(string cacheKey) return cacheKey; } + + private object ConvertToStoredValue(object cacheValue) => cacheValue ?? NullValue; + + private CacheValue ConvertFromStoredValue(object cacheValue) + { + switch (cacheValue) + { + case NullValue: return CacheValue.Null; + case T typedResult: return new CacheValue(typedResult, true); + default: return CacheValue.NoValue; + } + } /// /// Sets all. @@ -281,18 +290,11 @@ public override IDictionary> BaseGetAll(IEnumerable(cacheKeys); - var result = new Dictionary>(); - - foreach (var item in values) - { - if (item.Value != null) - result.Add(item.Key, new CacheValue(item.Value, true)); - else - result.Add(item.Key, CacheValue.NoValue); - } - - return result; + return _memcachedClient + .Get(cacheKeys) + .ToDictionary( + pair => pair.Key, + pair => ConvertFromStoredValue(pair.Value)); } /// @@ -359,7 +361,7 @@ public override void BaseFlush() public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -368,7 +370,11 @@ public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expir expiration = expiration.Add(TimeSpan.FromSeconds(addSec)); } - return _memcachedClient.Store(Enyim.Caching.Memcached.StoreMode.Add, this.HandleCacheKey(cacheKey), cacheValue, expiration); + return _memcachedClient.Store( + Enyim.Caching.Memcached.StoreMode.Add, + this.HandleCacheKey(cacheKey), + ConvertToStoredValue(cacheValue), + expiration); } public override TimeSpan BaseGetExpiration(string cacheKey) @@ -384,5 +390,21 @@ public override ProviderInfo BaseGetProviderInfo() { return _info; } + + private void OnCacheHit(string cacheKey) + { + CacheStats.OnHit(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + } + + private void OnCacheMiss(string cacheKey) + { + CacheStats.OnMiss(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); + } } } \ No newline at end of file diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs index 4787dc50..a09e7a59 100644 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Async.cs @@ -84,7 +84,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< } var item = await dataRetriever(); - if (item != null) + if (item != null || _options.CacheNulls) { await SetAsync(cacheKey, item, expiration); @@ -98,8 +98,8 @@ public override async Task> BaseGetAsync(string cacheKey, Func< await _cache.KeyDeleteAsync($"{cacheKey}_Lock"); return CacheValue.NoValue; } - } - + } + /// /// Gets the specified cacheKey async. /// @@ -130,8 +130,8 @@ public override async Task> BaseGetAsync(string cacheKey) return CacheValue.NoValue; } - } - + } + /// /// Gets the count. /// @@ -150,8 +150,8 @@ public override Task BaseGetCountAsync(string prefix = "") } return Task.FromResult(this.SearchRedisKeys(this.HandlePrefix(prefix)).Length); - } - + } + /// /// Removes the specified cacheKey async. /// @@ -162,8 +162,8 @@ public override async Task BaseRemoveAsync(string cacheKey) ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); await _cache.KeyDeleteAsync(cacheKey); - } - + } + /// /// Sets the specified cacheKey, cacheValue and expiration async. /// @@ -175,7 +175,7 @@ public override async Task BaseRemoveAsync(string cacheKey) public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -188,8 +188,8 @@ await _cache.StringSetAsync( cacheKey, _serializer.Serialize(cacheValue), expiration); - } - + } + /// /// Existses the specified cacheKey async. /// @@ -238,8 +238,8 @@ public override async Task BaseSetAllAsync(IDictionary values, Tim tasks.Add(SetAsync(item.Key, item.Value, expiration)); await Task.WhenAll(tasks); - } - + } + /// /// Gets all async. /// @@ -264,8 +264,8 @@ public override async Task>> BaseGetAllAsync /// Gets the by prefix async. /// @@ -293,8 +293,8 @@ public override async Task>> BaseGetByPrefixAs } return result; - } - + } + /// /// Removes all async. /// @@ -307,8 +307,8 @@ public override async Task BaseRemoveAllAsync(IEnumerable cacheKeys) var redisKeys = cacheKeys.Where(k => !string.IsNullOrEmpty(k)).Select(k => (RedisKey)k).ToArray(); if (redisKeys.Length > 0) await _cache.KeyDeleteAsync(redisKeys); - } - + } + /// /// Flush All Cached Item async. /// @@ -326,8 +326,8 @@ public override async Task BaseFlushAsync() } await Task.WhenAll(tasks); - } - + } + /// /// Tries the set async. /// @@ -339,7 +339,7 @@ public override async Task BaseFlushAsync() public override Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Geo.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Geo.cs index f5ae0c91..8142dcae 100644 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.Geo.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.Geo.cs @@ -1,7 +1,8 @@ namespace EasyCaching.Redis { using EasyCaching.Core; - using StackExchange.Redis; + using StackExchange.Redis; + using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -95,7 +96,7 @@ public async Task> GeoHashAsync(string cacheKey, List membe return res.ToList(); } - public List<(double longitude, double latitude)?> GeoPos(string cacheKey, List members) + public List<(decimal longitude, decimal latitude)?> GeoPos(string cacheKey, List members) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullAndCountGTZero(members, nameof(members)); @@ -108,25 +109,24 @@ public async Task> GeoHashAsync(string cacheKey, List membe var res = _cache.GeoPosition(cacheKey, list.ToArray()); - var tuple = new List<(double longitude, double latitude)?>(); + var tuple = new List<(decimal longitude, decimal latitude)?>(); foreach (var item in res) { if (item.HasValue) { - tuple.Add((item.Value.Longitude, item.Value.Latitude)); + tuple.Add((Convert.ToDecimal(item.Value.Longitude.ToString()), Convert.ToDecimal(item.Value.Latitude.ToString()))); } else { tuple.Add(null); } - } return tuple; } - public async Task> GeoPosAsync(string cacheKey, List members) + public async Task> GeoPosAsync(string cacheKey, List members) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNullAndCountGTZero(members, nameof(members)); @@ -139,13 +139,13 @@ public async Task> GeoHashAsync(string cacheKey, List membe var res = await _cache.GeoPositionAsync(cacheKey, list.ToArray()); - var tuple = new List<(double longitude, double latitude)?>(); + var tuple = new List<(decimal longitude, decimal latitude)?>(); foreach (var item in res) { if (item.HasValue) { - tuple.Add((item.Value.Longitude, item.Value.Latitude)); + tuple.Add((Convert.ToDecimal(item.Value.Longitude.ToString()), Convert.ToDecimal(item.Value.Latitude.ToString()))); } else { diff --git a/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs b/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs index cf615365..ec1c9833 100644 --- a/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs +++ b/src/EasyCaching.Redis/DefaultRedisCachingProvider.cs @@ -102,7 +102,8 @@ public DefaultRedisCachingProvider( ProviderType = ProviderType, SerializerName = options.SerializerName, SleepMs = options.SleepMs, - Serializer = _serializer + Serializer = _serializer, + CacheNulls = options.CacheNulls }; } @@ -143,7 +144,7 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, } var item = dataRetriever(); - if (item != null) + if (item != null || _options.CacheNulls) { Set(cacheKey, item, expiration); //remove mutex key @@ -213,7 +214,7 @@ public override void BaseRemove(string cacheKey) public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -453,7 +454,7 @@ public override void BaseFlush() public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) diff --git a/src/EasyCaching.SQLite/Configurations/SQLiteDatabaseProvider.cs b/src/EasyCaching.SQLite/Configurations/SQLiteDatabaseProvider.cs index b5237fe0..b3d19e24 100644 --- a/src/EasyCaching.SQLite/Configurations/SQLiteDatabaseProvider.cs +++ b/src/EasyCaching.SQLite/Configurations/SQLiteDatabaseProvider.cs @@ -2,7 +2,10 @@ { using EasyCaching.Core; using Microsoft.Data.Sqlite; - using Microsoft.Extensions.Options; + using System.Collections.Concurrent; + using System.Data; + using System.Threading; + using System.Threading.Tasks; /// /// SQLite database provider. @@ -16,16 +19,27 @@ public class SQLiteDatabaseProvider : ISQLiteDatabaseProvider public SQLiteDatabaseProvider(string name , SQLiteOptions options) { - this._name = name; - this._options = options.DBConfig; + _name = name; + _options = options.DBConfig; + _builder = new SqliteConnectionStringBuilder + { + DataSource = _options.DataSource, + Mode = _options.OpenMode, + Cache = _options.CacheMode + }; + + _conns = new ConcurrentDictionary(); } + private static ConcurrentDictionary _conns; + /// - /// The conn. + /// The builder /// - private static SqliteConnection _conn; + private static SqliteConnectionStringBuilder _builder; private readonly string _name = EasyCachingConstValue.DefaultSQLiteName; + public string DBProviderName => _name; /// @@ -34,19 +48,25 @@ public SQLiteDatabaseProvider(string name , SQLiteOptions options) /// The connection. public SqliteConnection GetConnection() { - if(_conn == null) + var threadId = Thread.CurrentThread.ManagedThreadId; + var con = _conns.GetOrAdd(threadId, CreateNewConnection()); + + Task.Run(async () => { - SqliteConnectionStringBuilder builder = new SqliteConnectionStringBuilder() + await Task.Delay(5000).ConfigureAwait(false); + _conns.TryRemove(threadId, out var removingConn); + if (removingConn?.State == ConnectionState.Closed) { - DataSource = _options.DataSource, - Mode = _options.OpenMode, - Cache = _options.CacheMode - }; + removingConn.Dispose(); + } + }); - _conn = new SqliteConnection(builder.ToString()); + return con; + } - } - return _conn; + private SqliteConnection CreateNewConnection() + { + return new SqliteConnection(_builder.ToString()); } } } diff --git a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs index 2c5b6d86..71cced65 100644 --- a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs +++ b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.Async.cs @@ -69,7 +69,7 @@ public override async Task> BaseGetAsync(string cacheKey, Func< var item = await dataRetriever?.Invoke(); - if (item != null) + if (item != null || _options.CacheNulls) { await SetAsync(cacheKey, item, expiration); return new CacheValue(item, true); @@ -196,7 +196,7 @@ public override async Task BaseRemoveAsync(string cacheKey) public override async Task BaseSetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -332,7 +332,7 @@ public override async Task BaseRemoveAllAsync(IEnumerable cacheKeys) public override async Task BaseTrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) diff --git a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs index 9600ade6..f5e3b427 100644 --- a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs +++ b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs @@ -74,28 +74,29 @@ public DefaultSQLiteCachingProvider( ProviderName = ProviderName, ProviderType = ProviderType, SerializerName = options.SerializerName, - SleepMs = options.SleepMs + SleepMs = options.SleepMs, + CacheNulls = options.CacheNulls }; InitDb(_dbProvider); - } - - /// - /// init database - /// - /// - private void InitDb(ISQLiteDatabaseProvider dbProvider) - { - var conn = dbProvider.GetConnection(); - - if (conn.State == System.Data.ConnectionState.Closed) - { - conn.Open(); - } - - conn.Execute(ConstSQL.CREATESQL); - } - + } + + /// + /// init database + /// + /// + private void InitDb(ISQLiteDatabaseProvider dbProvider) + { + var conn = dbProvider.GetConnection(); + + if (conn.State == System.Data.ConnectionState.Closed) + { + conn.Open(); + } + + conn.Execute(ConstSQL.CREATESQL); + } + /// /// Exists the specified cacheKey. /// @@ -127,38 +128,20 @@ public override CacheValue BaseGet(string cacheKey, Func dataRetriever, ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); - var dbResult = _cache.Query(ConstSQL.GETSQL, new - { - cachekey = cacheKey, - name = _name - }).FirstOrDefault(); + var result = BaseGet(cacheKey); - if (!string.IsNullOrWhiteSpace(dbResult)) + if (!result.HasValue) { - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); - - CacheStats.OnHit(); + var item = dataRetriever(); - return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); + if (item != null || _options.CacheNulls) + { + Set(cacheKey, item, expiration); + result = new CacheValue(item, true); + } } - CacheStats.OnMiss(); - - if (_options.EnableLogging) - _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); - - var item = dataRetriever(); - - if (item != null) - { - Set(cacheKey, item, expiration); - return new CacheValue(item, true); - } - else - { - return CacheValue.NoValue; - } + return result; } /// @@ -177,14 +160,16 @@ public override CacheValue BaseGet(string cacheKey) name = _name }).FirstOrDefault(); - if (!string.IsNullOrWhiteSpace(dbResult)) + if (!string.IsNullOrWhiteSpace(dbResult) || _options.CacheNulls) { CacheStats.OnHit(); if (_options.EnableLogging) _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); - return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); + return string.IsNullOrWhiteSpace(dbResult) + ? CacheValue.Null + : new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); } else { @@ -220,7 +205,7 @@ public override void BaseRemove(string cacheKey) public override void BaseSet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) @@ -386,7 +371,7 @@ public override int BaseGetCount(string prefix = "") public override bool BaseTrySet(string cacheKey, T cacheValue, TimeSpan expiration) { ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); - ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue), _options.CacheNulls); ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); if (MaxRdSecond > 0) diff --git a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs index e8de309d..5c4762f0 100644 --- a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs @@ -1,6 +1,7 @@ namespace EasyCaching.UnitTests { using EasyCaching.Core; + using EasyCaching.Core.Configurations; using FakeItEasy; using System; using System.Collections.Generic; @@ -11,9 +12,19 @@ public abstract class BaseCachingProviderTest { protected IEasyCachingProvider _provider; + protected IEasyCachingProvider _providerWithNullsCached; + protected TimeSpan _defaultTs; protected string _nameSpace = string.Empty; + protected BaseCachingProviderTest() + { + _provider = CreateCachingProvider(options => { }); + _providerWithNullsCached = CreateCachingProvider(options => options.CacheNulls = true); + } + + protected abstract IEasyCachingProvider CreateCachingProvider(Action additionalSetup); + #region Parameter Check Test [Theory] [InlineData("")] @@ -283,6 +294,21 @@ public void Set_Value_And_Get_Cached_Value_Should_Succeed() _provider.Remove(cacheKey); } + + [Fact] + public void Set_Value_And_Get_Cached_Value_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-sg-{Guid.NewGuid().ToString()}"; + string cacheValue = null; + + _providerWithNullsCached.Set(cacheKey, cacheValue, _defaultTs); + + var val = _providerWithNullsCached.Get(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + _providerWithNullsCached.Remove(cacheKey); + } [Fact] public async Task Set_Value_And_Get_Cached_Value_Async_Should_Succeed() @@ -298,6 +324,21 @@ public async Task Set_Value_And_Get_Cached_Value_Async_Should_Succeed() await _provider.RemoveAsync(cacheKey); } + + [Fact] + public async Task Set_Value_And_Get_Cached_Value_Async_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-sg-{Guid.NewGuid().ToString()}"; + string cacheValue = null; + + await _providerWithNullsCached.SetAsync(cacheKey, cacheValue, _defaultTs); + + var val = await _providerWithNullsCached.GetAsync(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + await _providerWithNullsCached.RemoveAsync(cacheKey); + } [Fact] protected virtual void Set_Object_Value_And_Get_Cached_Value_Should_Succeed() @@ -315,6 +356,21 @@ protected virtual void Set_Object_Value_And_Get_Cached_Value_Should_Succeed() _provider.Remove(cacheKey); } + [Fact] + protected virtual void Set_Object_Value_And_Get_Cached_Value_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-sog-{Guid.NewGuid().ToString()}"; + Product cacheValue = null; + + _providerWithNullsCached.Set(cacheKey, cacheValue, _defaultTs); + + var val = _providerWithNullsCached.Get(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + _providerWithNullsCached.Remove(cacheKey); + } + [Fact] protected virtual async Task Set_Object_Value_And_Get_Cached_Value_Async_Should_Succeed() { @@ -331,6 +387,21 @@ protected virtual async Task Set_Object_Value_And_Get_Cached_Value_Async_Should_ await _provider.RemoveAsync(cacheKey); } + [Fact] + protected virtual async Task Set_Object_Value_And_Get_Cached_Value_Async_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-sogasync-{Guid.NewGuid().ToString()}"; + Product cacheValue = null; + + await _providerWithNullsCached.SetAsync(cacheKey, cacheValue, _defaultTs); + + var val = await _providerWithNullsCached.GetAsync(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + await _providerWithNullsCached.RemoveAsync(cacheKey); + } + [Fact] protected virtual void Set_And_Get_Value_Type_Should_Succeed() { @@ -345,6 +416,21 @@ protected virtual void Set_And_Get_Value_Type_Should_Succeed() _provider.Remove(cacheKey); } + + [Fact] + protected virtual void Set_And_Get_Value_Type_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-svg-{Guid.NewGuid().ToString()}"; + int? cacheValue = null; + + _providerWithNullsCached.Set(cacheKey, cacheValue, _defaultTs); + + var val = _providerWithNullsCached.Get(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + _providerWithNullsCached.Remove(cacheKey); + } [Fact] protected virtual async Task Set_And_Get_Value_Type_Async_Should_Succeed() @@ -360,6 +446,21 @@ protected virtual async Task Set_And_Get_Value_Type_Async_Should_Succeed() await _provider.RemoveAsync(cacheKey); } + + [Fact] + protected virtual async Task Set_And_Get_Value_Type_Async_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-svgasync-{Guid.NewGuid().ToString()}"; + int? cacheValue = null; + + await _providerWithNullsCached.SetAsync(cacheKey, cacheValue, _defaultTs); + + var val = await _providerWithNullsCached.GetAsync(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + await _providerWithNullsCached.RemoveAsync(cacheKey); + } #endregion #region Get/GetAsync @@ -388,7 +489,7 @@ public async Task Get_Not_Cached_Value_Async_Should_Call_Retriever() } [Fact] - public void Get_Not_Cached_Value_Should_Call_Retriever_And_Return_Null() + public void Get_Not_Cached_Value_Should_Call_Retriever_And_Return_Null_Without_Caching() { var cacheKey = $"{_nameSpace}{Guid.NewGuid().ToString()}"; var func = Create_Fake_Retriever_Return_NULL(); @@ -396,6 +497,9 @@ public void Get_Not_Cached_Value_Should_Call_Retriever_And_Return_Null() var res = _provider.Get(cacheKey, func, _defaultTs); Assert.Equal(default(string),res.Value); + var cachedValue = _provider.Get(cacheKey); + Assert.False(cachedValue.HasValue); + Assert.Null(cachedValue.Value); } [Fact] @@ -409,6 +513,20 @@ public async Task Get_Not_Cached_Value_Async_Should_Call_Retriever_And_Return_Nu Assert.Equal(default(string), res.Value); } + [Fact] + public void Get_Not_Cached_Value_Should_Call_Retriever_And_Return_Null_With_Caching_When_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}{Guid.NewGuid().ToString()}"; + var func = Create_Fake_Retriever_Return_NULL(); + + var res = _providerWithNullsCached.Get(cacheKey, func, _defaultTs); + + Assert.Equal(default(string),res.Value); + var cachedValue = _providerWithNullsCached.Get(cacheKey); + Assert.True(cachedValue.HasValue); + Assert.Null(cachedValue.Value); + } + [Fact] public void Get_Cached_Value_Should_Not_Call_Retriever() { @@ -749,6 +867,25 @@ protected virtual void SetAll_Should_Succeed() Assert.Equal("value1", res1.Value); Assert.Equal("value2", res2.Value); } + + [Fact] + protected virtual void SetAll_Should_Succeed_When_One_Of_Values_Is_Null_And_Nulls_Are_Cached() + { + var dict = new Dictionary() + { + [$"{_nameSpace}setallwithnull:key:1"] = "value1", + [$"{_nameSpace}setallwithnull:key:2"] = null, + };; + + _providerWithNullsCached.SetAll(dict, _defaultTs); + + var res1 = _providerWithNullsCached.Get($"{_nameSpace}setallwithnull:key:1"); + var res2 = _providerWithNullsCached.Get($"{_nameSpace}setallwithnull:key:2"); + + Assert.Equal("value1", res1.Value); + Assert.True(res2.HasValue); + Assert.Null(res2.Value); + } [Fact] protected virtual async Task SetAllAsync_Should_Succeed() @@ -763,6 +900,25 @@ protected virtual async Task SetAllAsync_Should_Succeed() Assert.Equal("value1", res1.Value); Assert.Equal("value2", res2.Value); } + + [Fact] + protected virtual async Task SetAllAsync_Should_Succeed_When_One_Of_Values_Is_Null_And_Nulls_Are_Cached() + { + var dict = new Dictionary() + { + [$"{_nameSpace}setallasyncwithnull:key:1"] = "value1", + [$"{_nameSpace}setallasyncwithnull:key:2"] = null, + };; + + await _providerWithNullsCached.SetAllAsync(dict, _defaultTs); + + var res1 = _providerWithNullsCached.Get($"{_nameSpace}setallasyncwithnull:key:1"); + var res2 = _providerWithNullsCached.Get($"{_nameSpace}setallasyncwithnull:key:2"); + + Assert.Equal("value1", res1.Value); + Assert.True(res2.HasValue); + Assert.Null(res2.Value); + } #endregion #region GetAll/GetAllAsync @@ -783,6 +939,28 @@ protected virtual void GetAll_Should_Succeed() Assert.Equal("value1", res.Where(x => x.Key == $"{_nameSpace}getall:key:1").Select(x => x.Value).FirstOrDefault().Value); Assert.Equal("value2", res.Where(x => x.Key == $"{_nameSpace}getall:key:2").Select(x => x.Value).FirstOrDefault().Value); } + + [Fact] + protected virtual void GetAll_Should_Succeed_When_One_Of_Values_Is_Null_And_Nulls_Are_Cached() + { + _providerWithNullsCached.RemoveAll(new List { $"{_nameSpace}getall:key:1", $"{_nameSpace}getall:key:2" }); + var dict = new Dictionary() + { + [$"{_nameSpace}getallwithnull:key:1"] = "value1", + [$"{_nameSpace}getallwithnull:key:2"] = null, + };; + + _providerWithNullsCached.SetAll(dict, _defaultTs); + + var res = _providerWithNullsCached.GetAll(new List { $"{_nameSpace}getallwithnull:key:1", $"{_nameSpace}getallwithnull:key:2" }); + + Assert.Equal(2, res.Count); + + Assert.Contains($"{_nameSpace}getallwithnull:key:1",res.Select(x => x.Key)); + Assert.Contains($"{_nameSpace}getallwithnull:key:2", res.Select(x => x.Key)); + Assert.Equal("value1", res.Where(x => x.Key == $"{_nameSpace}getallwithnull:key:1").Select(x => x.Value).FirstOrDefault().Value); + Assert.Equal(null, res.Where(x => x.Key == $"{_nameSpace}getallwithnull:key:2").Select(x => x.Value).FirstOrDefault().Value); + } [Fact] protected virtual async Task GetAllAsync_Should_Succeed() @@ -801,6 +979,28 @@ protected virtual async Task GetAllAsync_Should_Succeed() Assert.Equal("value1",res.Where(x => x.Key == $"{_nameSpace}getallasync:key:1").Select(x => x.Value).FirstOrDefault().Value) ; Assert.Equal("value2",res.Where(x => x.Key == $"{_nameSpace}getallasync:key:2").Select(x => x.Value).FirstOrDefault().Value); } + + [Fact] + protected virtual async Task GetAllAsync_Should_Succeed_When_One_Of_Values_Is_Null_And_Nulls_Are_Cached() + { + _providerWithNullsCached.RemoveAll(new List { $"{_nameSpace}getall:key:1", $"{_nameSpace}getall:key:2" }); + var dict = new Dictionary() + { + [$"{_nameSpace}getallasyncwithnull:key:1"] = "value1", + [$"{_nameSpace}getallasyncwithnull:key:2"] = null, + };; + + _providerWithNullsCached.SetAll(dict, _defaultTs); + + var res = await _providerWithNullsCached.GetAllAsync(new List { $"{_nameSpace}getallasyncwithnull:key:1", $"{_nameSpace}getallasyncwithnull:key:2" }); + + Assert.Equal(2, res.Count); + + Assert.Contains($"{_nameSpace}getallasyncwithnull:key:1", res.Select(x => x.Key)); + Assert.Contains($"{_nameSpace}getallasyncwithnull:key:2", res.Select(x => x.Key)); + Assert.Equal("value1", res.Where(x => x.Key == $"{_nameSpace}getallasyncwithnull:key:1").Select(x => x.Value).FirstOrDefault().Value); + Assert.Equal(null, res.Where(x => x.Key == $"{_nameSpace}getallasyncwithnull:key:2").Select(x => x.Value).FirstOrDefault().Value); + } [Fact] protected virtual void GetAll_With_Value_Type_Should_Succeed() @@ -997,8 +1197,8 @@ protected virtual void Get_Count_With_Prefix_Should_Succeed() _provider.Remove($"{rd}:getcount:withprefix:1"); Assert.Equal(4, _provider.GetCount($"{rd}:getcount:withprefix:")); - } - + } + [Fact] protected virtual async Task Get_Count_Async_Without_Prefix_Should_Succeed() { @@ -1050,6 +1250,21 @@ protected virtual void TrySet_Value_And_Get_Cached_Value_Should_Succeed() Assert.True(val.HasValue); Assert.Equal(cacheValue1, val.Value); } + + [Fact] + public void TrySet_Value_And_Get_Cached_Value_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-sg-{Guid.NewGuid().ToString()}"; + string cacheValue = null; + + _providerWithNullsCached.TrySet(cacheKey, cacheValue, _defaultTs); + + var val = _providerWithNullsCached.Get(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + _providerWithNullsCached.Remove(cacheKey); + } [Fact] protected virtual async Task TrySet_Value_And_Get_Cached_Value_Async_Should_Succeed() @@ -1068,6 +1283,21 @@ protected virtual async Task TrySet_Value_And_Get_Cached_Value_Async_Should_Succ Assert.True(val.HasValue); Assert.Equal(cacheValue1, val.Value); } + + [Fact] + public async Task TrySet_Value_And_Get_Cached_Value_Async_Should_Succeed_When_CacheValue_IsNull_And_Nulls_Are_Cached() + { + var cacheKey = $"{_nameSpace}-sg-{Guid.NewGuid().ToString()}"; + string cacheValue = null; + + await _providerWithNullsCached.TrySetAsync(cacheKey, cacheValue, _defaultTs); + + var val = await _providerWithNullsCached.GetAsync(cacheKey); + Assert.True(val.HasValue); + Assert.Null(val.Value); + + await _providerWithNullsCached.RemoveAsync(cacheKey); + } #endregion #region GetExpiration diff --git a/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs index e49ce2a5..90b7d039 100644 --- a/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/BaseRedisFeatureCachingProviderTest.cs @@ -1783,8 +1783,8 @@ protected virtual void GeoAdd_And_GeoPos_Should_Succeed() var pos = _provider.GeoPos(cacheKey, new List { "Palermo", "Catania", "NonExisting" }); Assert.Equal(3, pos.Count); - Assert.Contains(13.361389338970184, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); - Assert.Contains(15.087267458438873, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(13.36138933897018433m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(15.08726745843887329m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); Assert.Contains(null, pos); _provider.KeyDel(cacheKey); @@ -1802,8 +1802,8 @@ protected virtual async Task GeoAddAsync_And_GeoPosAsync_Should_Succeed() var pos = await _provider.GeoPosAsync(cacheKey, new List { "Palermo", "Catania", "NonExisting" }); Assert.Equal(3, pos.Count); - Assert.Contains(13.361389338970184, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); - Assert.Contains(15.087267458438873, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(13.36138933897018433m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(15.08726745843887329m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); Assert.Contains(null, pos); await _provider.KeyDelAsync(cacheKey); diff --git a/test/EasyCaching.UnitTests/CachingTests/CSRedisCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/CSRedisCachingProviderTest.cs index cba10f79..d82c3197 100644 --- a/test/EasyCaching.UnitTests/CachingTests/CSRedisCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/CSRedisCachingProviderTest.cs @@ -2,6 +2,7 @@ namespace EasyCaching.UnitTests { using System; using EasyCaching.Core; + using EasyCaching.Core.Configurations; using EasyCaching.CSRedis; using EasyCaching.Serialization.Json; using EasyCaching.Serialization.MessagePack; @@ -12,25 +13,30 @@ public class CSRedisCachingProviderTest : BaseCachingProviderTest { public CSRedisCachingProviderTest() { - IServiceCollection services = new ServiceCollection(); - services.AddEasyCaching(option => - { - option.UseCSRedis(config => + + _defaultTs = TimeSpan.FromSeconds(30); + _nameSpace = "CSRedisBase"; + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) + { + var services = new ServiceCollection(); + services.AddEasyCaching(x => + x.UseCSRedis(options => { - config.DBConfig = new CSRedisDBOptions + options.DBConfig = new CSRedisDBOptions { ConnectionStrings = new System.Collections.Generic.List { "127.0.0.1:6388,defaultDatabase=13,poolsize=10" } }; - }); - }); + additionalSetup(options); + }) + ); IServiceProvider serviceProvider = services.BuildServiceProvider(); - _provider = serviceProvider.GetService(); - _defaultTs = TimeSpan.FromSeconds(30); - _nameSpace = "CSRedisBase"; + return serviceProvider.GetService(); } } diff --git a/test/EasyCaching.UnitTests/CachingTests/DiskCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/DiskCachingProviderTest.cs index ae048694..09c299d4 100644 --- a/test/EasyCaching.UnitTests/CachingTests/DiskCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/DiskCachingProviderTest.cs @@ -1,3 +1,5 @@ +using EasyCaching.Core.Configurations; + namespace EasyCaching.UnitTests { using System; @@ -11,26 +13,32 @@ namespace EasyCaching.UnitTests public class DiskCachingProviderTest : BaseCachingProviderTest { public DiskCachingProviderTest() + { + _defaultTs = TimeSpan.FromSeconds(30); + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) { IServiceCollection services = new ServiceCollection(); - services.AddEasyCaching(x => x.UseDisk(options => - { - options.MaxRdSecond = 0; - options.DBConfig = new DiskDbOptions + services.AddEasyCaching(x => + x.UseDisk(options => { - BasePath = Path.GetTempPath() - }; - - })); + options.MaxRdSecond = 0; + options.DBConfig = new DiskDbOptions + { + BasePath = Path.GetTempPath() + }; + additionalSetup(options); + }) + ); IServiceProvider serviceProvider = services.BuildServiceProvider(); - _provider = serviceProvider.GetService(); - _defaultTs = TimeSpan.FromSeconds(30); + return serviceProvider.GetService(); } [Fact(Skip = "fail in windows ci")] - protected override Task GetAsync_Parallel_Should_Succeed() - { - return Task.CompletedTask; + protected override Task GetAsync_Parallel_Should_Succeed() + { + return Task.CompletedTask; } } } \ No newline at end of file diff --git a/test/EasyCaching.UnitTests/CachingTests/LiteDBCachingTest.cs b/test/EasyCaching.UnitTests/CachingTests/LiteDBCachingTest.cs index 7e484b64..5623df1a 100644 --- a/test/EasyCaching.UnitTests/CachingTests/LiteDBCachingTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/LiteDBCachingTest.cs @@ -1,21 +1,23 @@ namespace EasyCaching.UnitTests { - using Dapper; - using EasyCaching.Core; - using EasyCaching.LiteDB; + using EasyCaching.Core; + using EasyCaching.LiteDB; + using EasyCaching.Core.Configurations; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; using System.IO; - using System.Linq; using System.Threading.Tasks; using Xunit; public class LiteDBCachingTest : BaseCachingProviderTest { - private readonly ILiteDBDatabaseProvider _dbProvider; - public LiteDBCachingTest() + { + _defaultTs = TimeSpan.FromSeconds(30); + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) { IServiceCollection services = new ServiceCollection(); services.AddEasyCaching(x => @@ -25,17 +27,12 @@ public LiteDBCachingTest() { FileName = "s1.ldb" }; + additionalSetup(options); }) ); IServiceProvider serviceProvider = services.BuildServiceProvider(); - var _dbProviders = serviceProvider.GetServices(); - _dbProvider = _dbProviders.FirstOrDefault(); - - - _provider = new DefaultLiteDBCachingProvider(EasyCachingConstValue.DefaultLiteDBName, _dbProviders, - new LiteDBOptions()); - _defaultTs = TimeSpan.FromSeconds(30); + return serviceProvider.GetService();; } [Fact] @@ -43,7 +40,7 @@ protected override Task GetAsync_Parallel_Should_Succeed() { return Task.FromResult(1); } - + [Fact] protected override void Get_Parallel_Should_Succeed() { @@ -82,7 +79,7 @@ public LiteDBCachingProviderWithFactoryTest() var _dbProviders = serviceProvider.GetServices(); foreach (var _dbProvider in _dbProviders) { - var conn = _dbProvider.GetConnection(); + var conn = _dbProvider.GetConnection(); } _defaultTs = TimeSpan.FromSeconds(30); diff --git a/test/EasyCaching.UnitTests/CachingTests/MemcachedProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/MemcachedProviderTest.cs index 1419749b..c676a7b2 100644 --- a/test/EasyCaching.UnitTests/CachingTests/MemcachedProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/MemcachedProviderTest.cs @@ -1,6 +1,7 @@ namespace EasyCaching.UnitTests { using EasyCaching.Core; + using EasyCaching.Core.Configurations; using EasyCaching.Memcached; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -12,16 +13,23 @@ public class MemcachedProviderTest : BaseCachingProviderTest { public MemcachedProviderTest() + { + _defaultTs = TimeSpan.FromSeconds(50); + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) { IServiceCollection services = new ServiceCollection(); services.AddEasyCaching(x => - x.UseMemcached( - options => { options.DBConfig.AddServer("127.0.0.1", 11211); }) + x.UseMemcached(options => + { + options.DBConfig.AddServer("127.0.0.1", 11211); + additionalSetup(options); + }) ); services.AddLogging(); IServiceProvider serviceProvider = services.BuildServiceProvider(); - _provider = serviceProvider.GetService(); - _defaultTs = TimeSpan.FromSeconds(50); + return serviceProvider.GetService(); } [Fact] @@ -135,14 +143,14 @@ protected override void Get_Count_With_Prefix_Should_Succeed() { } - protected override async Task Get_Count_Async_Without_Prefix_Should_Succeed() - { - await Task.FromResult(1); + protected override async Task Get_Count_Async_Without_Prefix_Should_Succeed() + { + await Task.FromResult(1); } - protected override async Task Get_Count_Async_With_Prefix_Should_Succeed() - { - await Task.FromResult(1); + protected override async Task Get_Count_Async_With_Prefix_Should_Succeed() + { + await Task.FromResult(1); } [Fact] diff --git a/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs index 1be95d9a..5738e81d 100644 --- a/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/MemoryCachingProviderTest.cs @@ -1,3 +1,5 @@ +using EasyCaching.Core.Configurations; + namespace EasyCaching.UnitTests { using EasyCaching.Core; @@ -15,16 +17,26 @@ namespace EasyCaching.UnitTests public class MemoryCachingProviderTest : BaseCachingProviderTest { public MemoryCachingProviderTest() + { + _defaultTs = TimeSpan.FromSeconds(30); + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) { IServiceCollection services = new ServiceCollection(); - services.AddEasyCaching(x => x.UseInMemory(options => options.MaxRdSecond = 0)); + services.AddEasyCaching(x => x + .UseInMemory(options => + { + options.MaxRdSecond = 0; + additionalSetup(options); + }) + ); IServiceProvider serviceProvider = services.BuildServiceProvider(); - _provider = serviceProvider.GetService(); - _defaultTs = TimeSpan.FromSeconds(30); + return serviceProvider.GetService();; } [Fact] - public void Deault_MaxRdSecond_Should_Be_0() + public void Default_MaxRdSecond_Should_Be_0() { Assert.Equal(0, _provider.MaxRdSecond); } @@ -41,7 +53,6 @@ public void TrySet_Parallel_Should_Succeed() }); Assert.Equal(1, list.Count(x => x)); - } [Fact] @@ -79,8 +90,8 @@ public void Issues105_StackOverflowException_Test() Assert.Equal("ss", first.Key); Assert.Equal(1, first.Value.Count); - } - + } + [Fact] public void Issues150_DeepClone_Object_Test() { @@ -92,8 +103,8 @@ public void Issues150_DeepClone_Object_Test() var res = _provider.Get(cacheKey); - res.Value.Name = "kobe"; - + res.Value.Name = "kobe"; + var res2 = _provider.Get(cacheKey); Assert.Equal("catcherwong", res2.Value.Name); @@ -197,8 +208,8 @@ public void Provider_Information_Should_Be_Correct() //Assert.Equal(99, _provider.Order); Assert.Equal("mName", _provider.Name); } - } - + } + public class MemoryCachingProviderDeepCloneTest { private readonly TimeSpan _defaultTs; @@ -211,34 +222,34 @@ public MemoryCachingProviderDeepCloneTest() { IServiceCollection services = new ServiceCollection(); services.AddEasyCaching(x => - { + { x.UseInMemory(options => - { + { options.MaxRdSecond = 0; - //options.DBConfig = new InMemoryCachingOptions - //{ + //options.DBConfig = new InMemoryCachingOptions + //{ // EnableWriteDeepClone = false, // EnableReadDeepClone = true, //}; }, "m1"); - x.UseInMemory(options => - { + x.UseInMemory(options => + { options.MaxRdSecond = 0; - options.DBConfig = new InMemoryCachingOptions - { + options.DBConfig = new InMemoryCachingOptions + { EnableWriteDeepClone = true, - EnableReadDeepClone = true, + EnableReadDeepClone = true, }; - }, "m2"); - - x.UseInMemory(options => - { + }, "m2"); + + x.UseInMemory(options => + { options.MaxRdSecond = 0; - options.DBConfig = new InMemoryCachingOptions - { + options.DBConfig = new InMemoryCachingOptions + { EnableWriteDeepClone = false, - EnableReadDeepClone = false, + EnableReadDeepClone = false, }; }, "m3"); }); @@ -254,7 +265,7 @@ public MemoryCachingProviderDeepCloneTest() [Fact] public void Enable_Read_Write_DeepClone_Should_Succeed() - { + { var cacheKey = Guid.NewGuid().ToString(); var cacheValue = new MySettingForCaching { Name = "catcherwong" }; @@ -265,8 +276,8 @@ public void Enable_Read_Write_DeepClone_Should_Succeed() var res = _m2.Get(cacheKey); - res.Value.Name = "kobe"; - + res.Value.Name = "kobe"; + var res2 = _m2.Get(cacheKey); Assert.Equal("catcherwong", res2.Value.Name); @@ -275,7 +286,7 @@ public void Enable_Read_Write_DeepClone_Should_Succeed() [Fact] public void Enable_Read_And_Disable_Write_DeepClone_Should_Succeed() - { + { var cacheKey = Guid.NewGuid().ToString(); var cacheValue = new MySettingForCaching { Name = "catcherwong" }; @@ -286,17 +297,17 @@ public void Enable_Read_And_Disable_Write_DeepClone_Should_Succeed() var res = _m1.Get(cacheKey); - res.Value.Name = "kobe"; - + res.Value.Name = "kobe"; + var res2 = _m1.Get(cacheKey); Assert.Equal("afterset", res2.Value.Name); - } - - + } + + [Fact] public void Disable_Read_And_Disable_Write_DeepClone_Should_Succeed() - { + { var cacheKey = Guid.NewGuid().ToString(); var cacheValue = new MySettingForCaching { Name = "catcherwong" }; @@ -307,17 +318,17 @@ public void Disable_Read_And_Disable_Write_DeepClone_Should_Succeed() var res = _m3.Get(cacheKey); - res.Value.Name = "kobe"; - + res.Value.Name = "kobe"; + var res2 = _m3.Get(cacheKey); Assert.Equal("kobe", res2.Value.Name); } } - [Serializable] - public class MySettingForCaching - { - public string Name { get; set; } + [Serializable] + public class MySettingForCaching + { + public string Name { get; set; } } } \ No newline at end of file diff --git a/test/EasyCaching.UnitTests/CachingTests/RedisCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/RedisCachingProviderTest.cs index 892c4670..79be621d 100644 --- a/test/EasyCaching.UnitTests/CachingTests/RedisCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/RedisCachingProviderTest.cs @@ -14,6 +14,12 @@ public class RedisCachingProviderTest : BaseCachingProviderTest private readonly string ProviderName = "Test"; public RedisCachingProviderTest() + { + _defaultTs = TimeSpan.FromSeconds(30); + _nameSpace = "RedisBasic"; + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) { IServiceCollection services = new ServiceCollection(); services.AddEasyCaching(x => @@ -25,12 +31,12 @@ public RedisCachingProviderTest() }; options.DBConfig.Endpoints.Add(new ServerEndPoint("127.0.0.1", 6380)); options.DBConfig.Database = 5; - }, ProviderName) + additionalSetup(options); + }, + ProviderName) ); IServiceProvider serviceProvider = services.BuildServiceProvider(); - _provider = serviceProvider.GetService(); - _defaultTs = TimeSpan.FromSeconds(30); - _nameSpace = "RedisBasic"; + return serviceProvider.GetService(); } [Fact] diff --git a/test/EasyCaching.UnitTests/CachingTests/SERedisFeatureCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/SERedisFeatureCachingProviderTest.cs index 31df4c3d..e817a1bc 100644 --- a/test/EasyCaching.UnitTests/CachingTests/SERedisFeatureCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/SERedisFeatureCachingProviderTest.cs @@ -5,6 +5,8 @@ using EasyCaching.Redis; using Microsoft.Extensions.DependencyInjection; using System; + using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; using Xunit; @@ -67,5 +69,41 @@ protected override void RPushX_Should_Succeed() { } + + protected override async Task GeoAddAsync_And_GeoPosAsync_Should_Succeed() + { + var cacheKey = $"{_nameSpace}-geohashasync-{Guid.NewGuid().ToString()}"; + + var res = await _provider.GeoAddAsync(cacheKey, new List<(double longitude, double latitude, string member)> { (13.361389, 38.115556, "Palermo"), (15.087269, 37.502669, "Catania") }); + + Assert.Equal(2, res); + + var pos = await _provider.GeoPosAsync(cacheKey, new List { "Palermo", "Catania", "NonExisting" }); + + Assert.Equal(3, pos.Count); + Assert.Contains(13.361389338970184m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(15.087267458438873m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(null, pos); + + await _provider.KeyDelAsync(cacheKey); + } + + protected override void GeoAdd_And_GeoPos_Should_Succeed() + { + var cacheKey = $"{_nameSpace}-geohash-{Guid.NewGuid().ToString()}"; + + var res = _provider.GeoAdd(cacheKey, new List<(double longitude, double latitude, string member)> { (13.361389, 38.115556, "Palermo"), (15.087269, 37.502669, "Catania") }); + + Assert.Equal(2, res); + + var pos = _provider.GeoPos(cacheKey, new List { "Palermo", "Catania", "NonExisting" }); + + Assert.Equal(3, pos.Count); + Assert.Contains(13.361389338970184m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(15.087267458438873m, pos.Where(x => x.HasValue).Select(x => x.Value.longitude)); + Assert.Contains(null, pos); + + _provider.KeyDel(cacheKey); + } } } diff --git a/test/EasyCaching.UnitTests/CachingTests/SQLiteCachingTest.cs b/test/EasyCaching.UnitTests/CachingTests/SQLiteCachingTest.cs index 49e1f144..878a583c 100644 --- a/test/EasyCaching.UnitTests/CachingTests/SQLiteCachingTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/SQLiteCachingTest.cs @@ -2,6 +2,7 @@ { using Dapper; using EasyCaching.Core; + using EasyCaching.Core.Configurations; using EasyCaching.SQLite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -13,9 +14,12 @@ public class SQLiteCachingTest : BaseCachingProviderTest { - private readonly ISQLiteDatabaseProvider _dbProvider; - public SQLiteCachingTest() + { + _defaultTs = TimeSpan.FromSeconds(30); + } + + protected override IEasyCachingProvider CreateCachingProvider(Action additionalSetup) { IServiceCollection services = new ServiceCollection(); services.AddEasyCaching(x => @@ -27,14 +31,14 @@ public SQLiteCachingTest() CacheMode = Microsoft.Data.Sqlite.SqliteCacheMode.Default, OpenMode = Microsoft.Data.Sqlite.SqliteOpenMode.Memory, }; + additionalSetup(options); }) ); IServiceProvider serviceProvider = services.BuildServiceProvider(); - var _dbProviders = serviceProvider.GetServices(); - _dbProvider = _dbProviders.FirstOrDefault(); + var dbProvider = serviceProvider.GetServices().First(); - var conn = _dbProvider.GetConnection(); + var conn = dbProvider.GetConnection(); if (conn.State == System.Data.ConnectionState.Closed) { conn.Open(); @@ -42,9 +46,7 @@ public SQLiteCachingTest() conn.Execute(ConstSQL.CREATESQL); - _provider = new DefaultSQLiteCachingProvider(EasyCachingConstValue.DefaultSQLiteName, _dbProviders, - new SQLiteOptions()); - _defaultTs = TimeSpan.FromSeconds(30); + return serviceProvider.GetService(); } [Fact] diff --git a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj index c02fee37..24533c83 100644 --- a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj +++ b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj @@ -38,6 +38,7 @@ + diff --git a/test/EasyCaching.UnitTests/SerializerTests/SystemTestJsonSerializerTest.cs b/test/EasyCaching.UnitTests/SerializerTests/SystemTestJsonSerializerTest.cs new file mode 100644 index 00000000..38db6210 --- /dev/null +++ b/test/EasyCaching.UnitTests/SerializerTests/SystemTestJsonSerializerTest.cs @@ -0,0 +1,72 @@ +using EasyCaching.Serialization.SystemTextJson; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Xunit; + +namespace EasyCaching.UnitTests.SerializerTests +{ + public class SystemTestJsonSerializerTest : BaseSerializerTest + { + public SystemTestJsonSerializerTest() + { + _serializer = new DefaultJsonSerializer("json", new System.Text.Json.JsonSerializerOptions()); + } + + [Fact] + public void Isuue_50_Test() + { + Employee joe = new Employee { Name = "Joe User" }; + Employee mike = new Employee { Name = "Mike Manager" }; + joe.Manager = mike; + mike.Manager = mike; + + Assert.Throws(() => _serializer.Serialize(joe)); + } + + [Fact] + public void ReferenceLoopHandling_Test_Should_Succeed() + { + var serializer = new DefaultJsonSerializer("json", new JsonSerializerOptions() + { + ReferenceHandler = ReferenceHandler.Preserve + }); + + Employee joe = new Employee { Name = "Joe User" }; + Employee mike = new Employee { Name = "Mike Manager" }; + joe.Manager = mike; + mike.Manager = mike; + + var joe_byte = serializer.Serialize(joe); + var joe_obj = serializer.Deserialize(joe_byte); + + + Assert.Equal(joe.Name, joe_obj.Name); + Assert.Equal(joe.Manager, mike); + } + + public class Employee + { + public string Name { get; set; } + public Employee Manager { get; set; } + } + + [Fact] + public void NullValueHandling_Test_Should_Succeed() + { + var serializer = new DefaultJsonSerializer("json", new JsonSerializerOptions + { + IgnoreNullValues = true + }); + + Employee joe = new Employee { Name = "Joe User" }; + + var joe_byte = serializer.Serialize(joe); + var joe_obj = serializer.Deserialize(joe_byte); + + Assert.Null(joe.Manager); + } + } +} \ No newline at end of file