diff --git a/EasyCaching.sln b/EasyCaching.sln index 3f2504a3..7d07ad0a 100644 --- a/EasyCaching.sln +++ b/EasyCaching.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32616.157 MinimumVisualStudioVersion = 10.0.40219.1 @@ -76,6 +76,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Bus.Zookeeper", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.FasterKv", "src\EasyCaching.FasterKv\EasyCaching.FasterKv.csproj", "{7191E567-38DF-4879-82E1-73EC618AFCAC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Serialization.MemoryPack", "serialization\EasyCaching.Serialization.MemoryPack\EasyCaching.Serialization.MemoryPack.csproj", "{EEF22C21-F380-4980-B72C-F14488369333}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,6 +204,10 @@ Global {7191E567-38DF-4879-82E1-73EC618AFCAC}.Debug|Any CPU.Build.0 = Debug|Any CPU {7191E567-38DF-4879-82E1-73EC618AFCAC}.Release|Any CPU.ActiveCfg = Release|Any CPU {7191E567-38DF-4879-82E1-73EC618AFCAC}.Release|Any CPU.Build.0 = Release|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEF22C21-F380-4980-B72C-F14488369333}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -237,6 +243,7 @@ Global {F7FBADEB-D766-4595-949A-07104B52692C} = {B337509B-75F9-4851-821F-9BBE87C4E4BC} {5E488583-391E-4E15-83C1-7301B4FE79AE} = {B337509B-75F9-4851-821F-9BBE87C4E4BC} {7191E567-38DF-4879-82E1-73EC618AFCAC} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} + {EEF22C21-F380-4980-B72C-F14488369333} = {15070C49-A507-4844-BCFE-D319CFBC9A63} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63A57886-054B-476C-AAE1-8D7C8917682E} diff --git a/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj b/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj index 91b6c494..10bc10bd 100644 --- a/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj +++ b/sample/EasyCaching.Demo.ConsoleApp/EasyCaching.Demo.ConsoleApp.csproj @@ -20,10 +20,13 @@ + + + + - + - diff --git a/sample/EasyCaching.Demo.ConsoleApp/Program.cs b/sample/EasyCaching.Demo.ConsoleApp/Program.cs index 03f7b282..3885d5ee 100644 --- a/sample/EasyCaching.Demo.ConsoleApp/Program.cs +++ b/sample/EasyCaching.Demo.ConsoleApp/Program.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using EasyCaching.Serialization.MemoryPack; namespace EasyCaching.Demo.ConsoleApp { using EasyCaching.Core; using EasyCaching.SQLite; + using MemoryPack; using Microsoft.Extensions.DependencyInjection; using System; @@ -16,15 +17,18 @@ static void Main(string[] args) IServiceCollection services = new ServiceCollection(); services.AddEasyCaching(option => { + option.WithMemoryPack(configure => + { + }, "mempack"); + option.UseInMemory("m1"); - // option.UseRedis(config => - // { - // config.DBConfig = new Redis.RedisDBOptions { Configuration = "localhost" }; - // config.SerializerName = "json"; - // }, "r1"); - // - + option.UseRedis((options) => + { + options.SerializerName = "mempack"; + options.DBConfig.Endpoints.Add(new Core.Configurations.ServerEndPoint("localhost", 6388)); + }, "r1"); + option.UseSQLite(c => { c.DBConfig = new SQLiteDBOptions @@ -43,7 +47,7 @@ static void Main(string[] args) IServiceProvider serviceProvider = services.BuildServiceProvider(); var factory = serviceProvider.GetService(); - + // var redisCache = factory.GetCachingProvider("r1"); // // redisCache.Set("rkey", new Product() { Name = "test" }, TimeSpan.FromSeconds(20)); @@ -55,41 +59,63 @@ static void Main(string[] args) // Console.WriteLine($"redis cache get value, {redisVal.HasValue} {redisVal.IsNull} {redisVal.Value}"); - - var mCache = factory.GetCachingProvider("m1"); - - mCache.Set("mkey1", new Product() { Name = "test" }, TimeSpan.FromSeconds(20)); - var mVal1 = mCache.Get("mkey1"); + var rCache = factory.GetCachingProvider("r1"); + + var prod = new Product() + { + Name = "Name1", + Lastname = "Lastname1", + Inner = new() + { + Name = "Name2", + Lastname = "Lastname2" + } + }; + + prod.Inner.Inner = prod; + rCache.Set("mkey1", prod, TimeSpan.FromSeconds(20)); + + var mVal1 = rCache.Get("mkey1"); + + rCache.Set("mkey", "mvalue", TimeSpan.FromSeconds(20)); + + var mVal = rCache.Get("mkey"); + + var mAllKey = rCache.GetAllKeysByPrefix("mk"); - mCache.Set("mkey", "mvalue", TimeSpan.FromSeconds(20)); - - var mVal = mCache.Get("mkey"); - - var mAllKey = mCache.GetAllKeysByPrefix("mk"); - Console.WriteLine($"in-memory cache get value, {mVal.HasValue} {mVal.IsNull} {mVal.Value} "); - + var sCache = factory.GetCachingProvider("s1"); - + sCache.Set("skey", "svalue", TimeSpan.FromSeconds(20)); - + var sVal = sCache.Get("skey"); - + Console.WriteLine($"sqlite cache get value, {sVal.HasValue} {sVal.IsNull} {sVal.Value} "); Console.ReadKey(); } } - public class Product + [MemoryPackable(GenerateType.CircularReference)] + public partial class Product { + [MemoryPackOrder(0)] public string Name { get; set; } + + [MemoryPackOrder(1)] + + public string Lastname { get; set; } + + [MemoryPackOrder(2)] + + public Product Inner { set; get; } } } diff --git a/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingMemPackSerializerOptions.cs b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingMemPackSerializerOptions.cs new file mode 100644 index 00000000..033aa04d --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingMemPackSerializerOptions.cs @@ -0,0 +1,13 @@ +using MemoryPack; + +namespace EasyCaching.Serialization.MemoryPack; + +/// +/// EasyCaching memory pack serializer options. +/// +public record EasyCachingMemPackSerializerOptions +{ + public StringEncoding StringEncoding { set; get; } +} + + diff --git a/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingOptionsExtensions.cs b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingOptionsExtensions.cs new file mode 100644 index 00000000..6764f372 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/EasyCachingOptionsExtensions.cs @@ -0,0 +1,36 @@ +using MemoryPack; +using EasyCaching.Core.Configurations; +using EasyCaching.Serialization.Json; + +namespace EasyCaching.Serialization.MemoryPack; + +/// +/// Easy caching options extensions. +/// +public static class EasyCachingOptionsExtensions +{ + /// + /// Withs the memory pack serializer. + /// + /// Options. + /// The name of this serializer instance. + public static EasyCachingOptions WithMemoryPack(this EasyCachingOptions options, string name = "mempack") + { + options.RegisterExtension(new MemoryPackOptionsExtension(name, null)); + + return options; + } + + /// + /// Withs the memory pack serializer. + /// + /// Options. + /// Configure serializer settings. + /// The name of this serializer instance. + public static EasyCachingOptions WithMemoryPack(this EasyCachingOptions options, Action serializerOptions, string name) + { + options.RegisterExtension(new MemoryPackOptionsExtension(name, serializerOptions)); + + return options; + } +} diff --git a/serialization/EasyCaching.Serialization.MemoryPack/Configurations/MemoryPackOptionsExtension.cs b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/MemoryPackOptionsExtension.cs new file mode 100644 index 00000000..58126711 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/Configurations/MemoryPackOptionsExtension.cs @@ -0,0 +1,55 @@ +namespace EasyCaching.Serialization.Json; + +using System; +using EasyCaching.Core.Configurations; +using EasyCaching.Core.Serialization; +using EasyCaching.Serialization.MemoryPack; +using global::MemoryPack; +using Microsoft.Extensions.DependencyInjection; + +/// +/// MemoryPack options extension. +/// +internal sealed class MemoryPackOptionsExtension : IEasyCachingOptionsExtension +{ + /// + /// The name. + /// + private readonly string _name; + + /// + /// The configure. + /// + private readonly Action _configure; + + /// + /// Initializes a new instance of the class. + /// + /// Name. + /// Configure. + public MemoryPackOptionsExtension(string name, Action configure) + { + this._name = name; + this._configure = configure; + } + + /// + /// Adds the services. + /// + /// Services. + public void AddServices(IServiceCollection services) + { + Action configure = _configure ?? (_ => { }); + + services.AddOptions(); + services.Configure(_name, configure); + + services.AddSingleton(x => + { + var optionsMon = x.GetRequiredService>(); + var easyCachingOptions = optionsMon.Get(_name); + var options = new MemoryPackSerializerOptions { StringEncoding = easyCachingOptions.StringEncoding }; + return new DefaultMemoryPackSerializer(_name, options); + }); + } +} diff --git a/serialization/EasyCaching.Serialization.MemoryPack/DefaultMemoryPackSerializer.cs b/serialization/EasyCaching.Serialization.MemoryPack/DefaultMemoryPackSerializer.cs new file mode 100644 index 00000000..e8d01528 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/DefaultMemoryPackSerializer.cs @@ -0,0 +1,33 @@ +using EasyCaching.Core.Serialization; +using MemoryPack; + +namespace EasyCaching.Serialization.MemoryPack; + +/// +/// Default MemoryPack serializer +/// +public class DefaultMemoryPackSerializer : IEasyCachingSerializer +{ + private readonly string _name; + private readonly MemoryPackSerializerOptions _memoryPackSerializerOptions; + + public string Name => _name; + + public DefaultMemoryPackSerializer(string name, MemoryPackSerializerOptions options = null) + { + _name = name; + _memoryPackSerializerOptions = options; + } + + public T Deserialize(byte[] bytes) => MemoryPackSerializer.Deserialize(bytes, _memoryPackSerializerOptions); + public object Deserialize(byte[] bytes, Type type) => MemoryPackSerializer.Deserialize(type, bytes, _memoryPackSerializerOptions); + public object DeserializeObject(ArraySegment value) => throw new NotImplementedException("this is not supported in MemoryPack serializer"); + public byte[] Serialize(T value) => MemoryPackSerializer.Serialize(value, _memoryPackSerializerOptions); + + public ArraySegment SerializeObject(object obj) + { + var bytes = MemoryPackSerializer.Serialize(obj.GetType(), obj, _memoryPackSerializerOptions); + return new ArraySegment(bytes); + } +} + diff --git a/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj b/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj new file mode 100644 index 00000000..91d53fd7 --- /dev/null +++ b/serialization/EasyCaching.Serialization.MemoryPack/EasyCaching.Serialization.MemoryPack.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + + + + + + + + diff --git a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs index 9b359f10..0e70a5ba 100644 --- a/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs +++ b/test/EasyCaching.UnitTests/CachingTests/BaseCachingProviderTest.cs @@ -765,7 +765,7 @@ public async Task Remove_Cached_Value_Async_Should_Succeed() var cacheValue = "value"; await _provider.SetAsync(cacheKey, cacheValue, _defaultTs); - var valBeforeRemove = await _provider.GetAsync(cacheKey, null, _defaultTs); + var valBeforeRemove = await _provider.GetAsync(cacheKey, () => null, _defaultTs); Assert.NotNull(valBeforeRemove); await _provider.RemoveAsync(cacheKey); @@ -870,7 +870,7 @@ protected virtual async Task RemoveByPrefixAsync_Should_Succeed() } #endregion - + #region RemoveByPattern/RemoveByPatternAsync [Fact] @@ -878,7 +878,7 @@ public virtual void RemoveByPattern_Should_Succeed() { SetCacheItem("garden:pots:flowers", "ok"); SetCacheItem("garden:pots:flowers:test", "ok"); - SetCacheItem("garden:flowerspots:test", "ok" ); + SetCacheItem("garden:flowerspots:test", "ok"); SetCacheItem("boo:foo", "ok"); SetCacheItem("boo:test:foo", "ok"); SetCacheItem("sky:birds:bar", "ok"); @@ -933,13 +933,13 @@ public virtual void RemoveByPattern_Should_Succeed() Assert.False(val15.HasValue); Assert.False(val16.HasValue); } - + [Fact] public virtual async Task RemoveByPatternAsync_Should_Succeed() { SetCacheItem("garden:pots:flowers", "ok"); SetCacheItem("garden:pots:flowers:test", "ok"); - SetCacheItem("garden:flowerspots:test", "ok" ); + SetCacheItem("garden:flowerspots:test", "ok"); SetCacheItem("boo:foo", "ok"); SetCacheItem("boo:test:foo", "ok"); SetCacheItem("sky:birds:bar", "ok"); diff --git a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj index 26b51b1d..4c5c5b3b 100644 --- a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj +++ b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj @@ -30,15 +30,6 @@ - - - - - - - - - @@ -51,4 +42,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs b/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs index 49dbcda4..bfc5a26d 100644 --- a/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs +++ b/test/EasyCaching.UnitTests/SerializerTests/BaseSerializerTest.cs @@ -2,6 +2,7 @@ { using System; using EasyCaching.Core.Serialization; + using MemoryPack; using ProtoBuf; using Xunit; @@ -38,7 +39,7 @@ public void SerializeObject_should_Succeed() } [Fact] - public void DeserializeObject_should_Succeed() + public virtual void DeserializeObject_should_Succeed() { object obj = new Model { Prop = "abc" }; @@ -76,7 +77,8 @@ public void Deserialize_String_Should_Succeed(string str) [Serializable] [ProtoContract] - public class Model + [MemoryPackable] + public partial class Model { [ProtoMember(1)] public string Prop { get; set; } diff --git a/test/EasyCaching.UnitTests/SerializerTests/MemoryPackSerializerTest.cs b/test/EasyCaching.UnitTests/SerializerTests/MemoryPackSerializerTest.cs new file mode 100644 index 00000000..32cbbfb9 --- /dev/null +++ b/test/EasyCaching.UnitTests/SerializerTests/MemoryPackSerializerTest.cs @@ -0,0 +1,96 @@ +using System; +using EasyCaching.Serialization.MemoryPack; +using MemoryPack; +using Xunit; + +namespace EasyCaching.UnitTests; + + +public class MemoryPackSerializerTest : BaseSerializerTest +{ + public MemoryPackSerializerTest() + { + _serializer = new DefaultMemoryPackSerializer("mempack"); + } + + //This should be overrided becuse it is not supported by memory-pack + public override void DeserializeObject_should_Succeed() + { + Person input = new("test", "test1"); + var serialized = _serializer.Serialize(input); + + Assert.Throws(() => + { + _serializer.DeserializeObject(new System.ArraySegment(serialized)); + }); + } + + [Fact] + public void GivenSampleRecord_ShouldSerializeAndDeserializeSuccessfuly() + { + Person input = new("test", "test1"); + var bytes = _serializer.Serialize(input); + + Person output = _serializer.Deserialize(bytes); + + Assert.Equal(input, output); + } + + [Fact] + public void GivenSampleRecord_ShouldHandleNestedObjectSuccessfuly() + { + NestedPerson item1 = new() { Name = "test", Lastname = "test1" }; + NestedPerson expected = new() { Name = "test2", Lastname = "test3", Inner = item1 }; + + var bytes = _serializer.Serialize(expected); + + NestedPerson output = _serializer.Deserialize(bytes); + + Assert.Equal(expected, output); + } + + [Fact] + public void GivenSampleInput_ShouldHandleCircularRefSuccessfuly() + { + CircularPerson person = new CircularPerson() + { + Name = "test" + }; + + person.Self = person; + + var bytes = _serializer.Serialize(person); + var output = _serializer.Deserialize(bytes); + + bool expected = + person.Name == output.Name && + output.Self == output && + output.Name == output.Self.Name; + + Assert.True(expected); + } +} + +#region Test Models +[MemoryPackable(GenerateType.CircularReference)] +internal partial class CircularPerson +{ + [MemoryPackOrder(0)] + public string Name { set; get; } + [MemoryPackOrder(1)] + public CircularPerson Self { set; get; } +} + +[MemoryPackable] +internal partial record struct Person(string Name, string Lastname); + +[MemoryPackable] +internal partial record class NestedPerson +{ + public string Name { set; get; } + + public string Lastname { set; get; } + + public NestedPerson Inner { set; get; } +} +#endregion