diff --git a/EasyCaching.sln b/EasyCaching.sln index e8f9f0fc..6436474b 100644 --- a/EasyCaching.sln +++ b/EasyCaching.sln @@ -1,51 +1,54 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2026 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A0F5CC7E-155F-4726-8DEB-E966950B3FE9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F88D727A-9F9C-43D9-90B1-D4A02BF8BC98}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{EBB55F65-7D07-4281-8D5E-7B0CA88E1AD0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Core", "src\EasyCaching.Core\EasyCaching.Core.csproj", "{CE61FAA2-0233-451C-991D-4222ED61C84B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Core", "src\EasyCaching.Core\EasyCaching.Core.csproj", "{CE61FAA2-0233-451C-991D-4222ED61C84B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.InMemory", "src\EasyCaching.InMemory\EasyCaching.InMemory.csproj", "{B9490432-737B-4518-B851-9D40FD29B392}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.InMemory", "src\EasyCaching.InMemory\EasyCaching.InMemory.csproj", "{B9490432-737B-4518-B851-9D40FD29B392}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Serialization.MessagePack", "src\EasyCaching.Serialization.MessagePack\EasyCaching.Serialization.MessagePack.csproj", "{50D8C42C-1BC0-4133-8B70-63A6649DBD74}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Serialization.MessagePack", "src\EasyCaching.Serialization.MessagePack\EasyCaching.Serialization.MessagePack.csproj", "{50D8C42C-1BC0-4133-8B70-63A6649DBD74}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Interceptor.AspectCore", "src\EasyCaching.Interceptor.AspectCore\EasyCaching.Interceptor.AspectCore.csproj", "{BED4832E-A790-42A6-978D-E0C1A215E638}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Interceptor.AspectCore", "src\EasyCaching.Interceptor.AspectCore\EasyCaching.Interceptor.AspectCore.csproj", "{BED4832E-A790-42A6-978D-E0C1A215E638}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Bus.Redis", "src\EasyCaching.Bus.Redis\EasyCaching.Bus.Redis.csproj", "{07326A03-B144-469F-837B-31DD3E0EB1AC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Bus.Redis", "src\EasyCaching.Bus.Redis\EasyCaching.Bus.Redis.csproj", "{07326A03-B144-469F-837B-31DD3E0EB1AC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Bus.RabbitMQ", "src\EasyCaching.Bus.RabbitMQ\EasyCaching.Bus.RabbitMQ.csproj", "{265FB0C5-2C34-438D-B671-63836954EB5A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Bus.RabbitMQ", "src\EasyCaching.Bus.RabbitMQ\EasyCaching.Bus.RabbitMQ.csproj", "{265FB0C5-2C34-438D-B671-63836954EB5A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Interceptor.Castle", "src\EasyCaching.Interceptor.Castle\EasyCaching.Interceptor.Castle.csproj", "{7B55B6D9-4221-4E82-AED6-BEC9A60C99D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Interceptor.Castle", "src\EasyCaching.Interceptor.Castle\EasyCaching.Interceptor.Castle.csproj", "{7B55B6D9-4221-4E82-AED6-BEC9A60C99D2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Serialization.Json", "src\EasyCaching.Serialization.Json\EasyCaching.Serialization.Json.csproj", "{F0302BE8-188A-4EAF-8391-514CA9352DB3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Serialization.Json", "src\EasyCaching.Serialization.Json\EasyCaching.Serialization.Json.csproj", "{F0302BE8-188A-4EAF-8391-514CA9352DB3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Serialization.Protobuf", "src\EasyCaching.Serialization.Protobuf\EasyCaching.Serialization.Protobuf.csproj", "{3AAEDDBA-5037-4061-84E5-667F89C11F8E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Serialization.Protobuf", "src\EasyCaching.Serialization.Protobuf\EasyCaching.Serialization.Protobuf.csproj", "{3AAEDDBA-5037-4061-84E5-667F89C11F8E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.ResponseCaching", "src\EasyCaching.ResponseCaching\EasyCaching.ResponseCaching.csproj", "{261EE728-4965-4C0E-B99B-3B188442263A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.ResponseCaching", "src\EasyCaching.ResponseCaching\EasyCaching.ResponseCaching.csproj", "{261EE728-4965-4C0E-B99B-3B188442263A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Demo.Providers", "sample\EasyCaching.Demo.Providers\EasyCaching.Demo.Providers.csproj", "{4193DF5A-E034-4917-83F4-380E8D7DCF83}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Demo.Providers", "sample\EasyCaching.Demo.Providers\EasyCaching.Demo.Providers.csproj", "{4193DF5A-E034-4917-83F4-380E8D7DCF83}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Demo.Interceptors", "sample\EasyCaching.Demo.Interceptors\EasyCaching.Demo.Interceptors.csproj", "{F7DD4826-1375-42B3-97BA-37685460282E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Demo.Interceptors", "sample\EasyCaching.Demo.Interceptors\EasyCaching.Demo.Interceptors.csproj", "{F7DD4826-1375-42B3-97BA-37685460282E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.SQLite", "src\EasyCaching.SQLite\EasyCaching.SQLite.csproj", "{50089D69-50CF-49B7-8939-59C309A22336}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.SQLite", "src\EasyCaching.SQLite\EasyCaching.SQLite.csproj", "{50089D69-50CF-49B7-8939-59C309A22336}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Redis", "src\EasyCaching.Redis\EasyCaching.Redis.csproj", "{F58E5C6F-407F-4E80-9282-2028E9810F55}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Redis", "src\EasyCaching.Redis\EasyCaching.Redis.csproj", "{F58E5C6F-407F-4E80-9282-2028E9810F55}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Memcached", "src\EasyCaching.Memcached\EasyCaching.Memcached.csproj", "{709D2333-42A7-45E6-B175-E630DCC1D807}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Memcached", "src\EasyCaching.Memcached\EasyCaching.Memcached.csproj", "{709D2333-42A7-45E6-B175-E630DCC1D807}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.HybridCache", "src\EasyCaching.HybridCache\EasyCaching.HybridCache.csproj", "{18DB08CD-B8CC-4409-9C7C-2906BCAA8827}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.HybridCache", "src\EasyCaching.HybridCache\EasyCaching.HybridCache.csproj", "{18DB08CD-B8CC-4409-9C7C-2906BCAA8827}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Demo.ResponseCaching", "sample\EasyCaching.Demo.ResponseCaching\EasyCaching.Demo.ResponseCaching.csproj", "{76A5C2E3-6525-45A0-9CE7-649F3235A443}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.Demo.ResponseCaching", "sample\EasyCaching.Demo.ResponseCaching\EasyCaching.Demo.ResponseCaching.csproj", "{76A5C2E3-6525-45A0-9CE7-649F3235A443}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.UnitTests", "test\EasyCaching.UnitTests\EasyCaching.UnitTests.csproj", "{2A8D7103-DF64-47B6-A406-8F8559AF7E7F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.UnitTests", "test\EasyCaching.UnitTests\EasyCaching.UnitTests.csproj", "{2A8D7103-DF64-47B6-A406-8F8559AF7E7F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.PerformanceTests", "test\EasyCaching.PerformanceTests\EasyCaching.PerformanceTests.csproj", "{6EBE36A2-F128-4C63-B90A-B700D8C2F2E8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.PerformanceTests", "test\EasyCaching.PerformanceTests\EasyCaching.PerformanceTests.csproj", "{6EBE36A2-F128-4C63-B90A-B700D8C2F2E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.CSRedis", "src\EasyCaching.CSRedis\EasyCaching.CSRedis.csproj", "{6584761E-E51C-408F-BE51-CA0F6269589B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasyCaching.CSRedis", "src\EasyCaching.CSRedis\EasyCaching.CSRedis.csproj", "{6584761E-E51C-408F-BE51-CA0F6269589B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.SQLServerCache", "src\EasyCaching.SQLServerCache\EasyCaching.SQLServerCache.csproj", "{C9D8DB1F-8257-4C01-B173-388D659FB1E5}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EasyCaching.Bus.CSRedis", "src\EasyCaching.Bus.CSRedis\EasyCaching.Bus.CSRedis.csproj", "{861E5373-BEF6-4AA2-92C7-8F4941A079E7}" EndProject @@ -135,11 +138,18 @@ Global {6584761E-E51C-408F-BE51-CA0F6269589B}.Debug|Any CPU.Build.0 = Debug|Any CPU {6584761E-E51C-408F-BE51-CA0F6269589B}.Release|Any CPU.ActiveCfg = Release|Any CPU {6584761E-E51C-408F-BE51-CA0F6269589B}.Release|Any CPU.Build.0 = Release|Any CPU + {C9D8DB1F-8257-4C01-B173-388D659FB1E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9D8DB1F-8257-4C01-B173-388D659FB1E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9D8DB1F-8257-4C01-B173-388D659FB1E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9D8DB1F-8257-4C01-B173-388D659FB1E5}.Release|Any CPU.Build.0 = Release|Any CPU {861E5373-BEF6-4AA2-92C7-8F4941A079E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {861E5373-BEF6-4AA2-92C7-8F4941A079E7}.Debug|Any CPU.Build.0 = Debug|Any CPU {861E5373-BEF6-4AA2-92C7-8F4941A079E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {861E5373-BEF6-4AA2-92C7-8F4941A079E7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection GlobalSection(NestedProjects) = preSolution {CE61FAA2-0233-451C-991D-4222ED61C84B} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} {B9490432-737B-4518-B851-9D40FD29B392} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} @@ -161,6 +171,10 @@ Global {2A8D7103-DF64-47B6-A406-8F8559AF7E7F} = {EBB55F65-7D07-4281-8D5E-7B0CA88E1AD0} {6EBE36A2-F128-4C63-B90A-B700D8C2F2E8} = {EBB55F65-7D07-4281-8D5E-7B0CA88E1AD0} {6584761E-E51C-408F-BE51-CA0F6269589B} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} + {C9D8DB1F-8257-4C01-B173-388D659FB1E5} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} {861E5373-BEF6-4AA2-92C7-8F4941A079E7} = {A0F5CC7E-155F-4726-8DEB-E966950B3FE9} EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {71E7E21A-1DC2-4918-9FA7-3B6879856B2C} + EndGlobalSection EndGlobal diff --git a/src/EasyCaching.Core/Configurations/EasyCachingApplicationBuliderExtensions.cs b/src/EasyCaching.Core/Configurations/EasyCachingApplicationBuilderExtensions.cs similarity index 75% rename from src/EasyCaching.Core/Configurations/EasyCachingApplicationBuliderExtensions.cs rename to src/EasyCaching.Core/Configurations/EasyCachingApplicationBuilderExtensions.cs index c7daae67..40a2de7b 100644 --- a/src/EasyCaching.Core/Configurations/EasyCachingApplicationBuliderExtensions.cs +++ b/src/EasyCaching.Core/Configurations/EasyCachingApplicationBuilderExtensions.cs @@ -3,7 +3,7 @@ using EasyCaching.Core.Configurations; using Microsoft.AspNetCore.Builder; - public static class EasyCachingApplicationBuliderExtensions + public static class EasyCachingApplicationBuilderExtensions { /// /// Uses the easy caching. @@ -16,9 +16,9 @@ public static IApplicationBuilder UseEasyCaching(this IApplicationBuilder app) var options = app.ApplicationServices.GetService(typeof(EasyCachingOptions)); - if (options != null && options is EasyCachingOptions) + if (options is EasyCachingOptions cachingOptions) { - foreach (var serviceExtension in ((EasyCachingOptions)options).Extensions) + foreach (var serviceExtension in cachingOptions.Extensions) { serviceExtension.WithServices(app); } diff --git a/src/EasyCaching.Core/EasyCaching.Core.csproj b/src/EasyCaching.Core/EasyCaching.Core.csproj index b10c256d..578daa55 100644 --- a/src/EasyCaching.Core/EasyCaching.Core.csproj +++ b/src/EasyCaching.Core/EasyCaching.Core.csproj @@ -22,7 +22,6 @@ - diff --git a/src/EasyCaching.Core/Internal/CachingProviderType.cs b/src/EasyCaching.Core/Internal/CachingProviderType.cs index d8e198c4..a2fa5021 100644 --- a/src/EasyCaching.Core/Internal/CachingProviderType.cs +++ b/src/EasyCaching.Core/Internal/CachingProviderType.cs @@ -9,7 +9,7 @@ public enum CachingProviderType Memcached, Redis, SQLite, - Ext1, + SQLServer, Ext2 } } diff --git a/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs b/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs index 9d94e559..66d28d8f 100644 --- a/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs +++ b/src/EasyCaching.Core/Internal/EasyCachingConstValue.cs @@ -29,6 +29,10 @@ public class EasyCachingConstValue /// The SQLite section. /// public const string SQLiteSection = "easycaching:sqlite"; + /// + /// The SQLServer section. + /// + public const string SQLServerSection = "easycaching:sqlserver"; /// /// The in-memory section. @@ -69,5 +73,10 @@ public class EasyCachingConstValue /// The default name of the SQLite. /// public const string DefaultSQLiteName = "DefaultSQLite"; + + /// + /// The default name of the SQL Server. + /// + public const string DefaultSQLServerName = "DefaultSQLServer"; } } diff --git a/src/EasyCaching.SQLServerCache/Configurations/ConstSQL.cs b/src/EasyCaching.SQLServerCache/Configurations/ConstSQL.cs new file mode 100644 index 00000000..ba1390ef --- /dev/null +++ b/src/EasyCaching.SQLServerCache/Configurations/ConstSQL.cs @@ -0,0 +1,123 @@ +namespace EasyCaching.SQLServer.Configurations +{ + /// + /// Const sql. + /// + public static class ConstSQL + { + /// + /// The setsql. + /// + public const string SETSQL = @" + DELETE FROM [{0}].[{1}] WHERE [cachekey] = @cachekey AND [name]=@name; + INSERT INTO [{0}].[{1}] + ([name] + ,[cachekey] + ,[cachevalue] + ,[expiration]) + VALUES + (@name + ,@cachekey + ,@cachevalue + ,DATEADD(second, @expiration, getutcdate()));"; + + /// + /// The trysetsql. + /// + public const string TRYSETSQL = @" + INSERT INTO [{0}].[{1}] + ([name] + ,[cachekey] + ,[cachevalue] + ,[expiration]) + SELECT @name,@cachekey,@cachevalue,DATEADD(second, @expiration, getutcdate()) + WHERE NOT EXISTS (SELECT 1 FROM [{0}].[{1}] WHERE [cachekey] = @cachekey AND [name]=@name AND [expiration] > getutcdate());"; + + + + /// + /// The getsql. + /// + public const string GETSQL = @"SELECT [cachevalue] + FROM [{0}].[{1}] + WHERE [cachekey] = @cachekey AND [name]=@name AND [expiration] > getutcdate()"; + + /// + /// The getallsql. + /// + public const string GETALLSQL = @"SELECT [cachekey],[cachevalue] + FROM [{0}].[{1}] + WHERE [cachekey] IN @cachekey AND [name]=@name AND [expiration] > getutcdate()"; + + /// + /// The getbyprefixsql. + /// + public const string GETBYPREFIXSQL = @"SELECT [cachekey],[cachevalue] + FROM [{0}].[{1}] + WHERE [cachekey] LIKE @cachekey AND [name]=@name AND [expiration] > getutcdate()"; + + /// + /// The removesql. + /// + public const string REMOVESQL = @"DELETE FROM [{0}].[{1}] WHERE [cachekey] = @cachekey AND [name] = @name "; + + /// + /// The removebyprefixsql. + /// + public const string REMOVEBYPREFIXSQL = @"DELETE FROM [{0}].[{1}] WHERE [cachekey] like @cachekey AND [name]=@name"; + + /// + /// The existssql. + /// + public const string EXISTSSQL = @"SELECT COUNT(1) + FROM [{0}].[{1}] + WHERE [cachekey] = @cachekey AND [name]=@name AND [expiration] > getutcdate()"; + + /// + /// The countallsql. + /// + public const string COUNTALLSQL = @"SELECT COUNT(1) + FROM [{0}].[{1}] + WHERE [expiration] > getutcdate() AND [name]=@name"; + + /// + /// The countprefixsql. + /// + public const string COUNTPREFIXSQL = @"SELECT COUNT(1) + FROM [{0}].[{1}] + WHERE [cachekey] like @cachekey AND [name]=@name AND [expiration] > getutcdate()"; + + /// + /// The flushsql. + /// + public const string FLUSHSQL = @"DELETE FROM [{0}].[{1}] WHERE [name]=@name"; + + /// + /// The sql for cleaning up db. Remove all expired entries from the db. + /// + public const string CLEANEXPIREDSQL = @"DELETE FROM [{0}].[{1}] WHERE [expiration] < getutcdate()"; + + /// + /// The createsql. + /// + public const string CREATESQL = @"IF NOT EXISTS ( + SELECT schema_name + FROM information_schema.schemata + WHERE schema_name = '{0}' ) + + BEGIN + EXEC sp_executesql N'CREATE SCHEMA {0}' + END + + IF NOT EXISTS (SELECT * FROM sys.objects + WHERE object_id = OBJECT_ID(N'[{0}].[{1}]') AND type in (N'U')) + BEGIN + CREATE TABLE [{0}].[{1}] ( + [ID] INT IDENTITY(1,1) PRIMARY KEY + , [name] VARCHAR(255) + , [cachekey] VARCHAR(255) + , [cachevalue] NVARCHAR(MAX) + , [expiration] DATETIME) + END"; + } +} diff --git a/src/EasyCaching.SQLServerCache/Configurations/EasyCachingOptionsExtensions.cs b/src/EasyCaching.SQLServerCache/Configurations/EasyCachingOptionsExtensions.cs new file mode 100644 index 00000000..105947b4 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/Configurations/EasyCachingOptionsExtensions.cs @@ -0,0 +1,58 @@ +using System; +using EasyCaching.Core; +using EasyCaching.Core.Configurations; +using Microsoft.Extensions.Configuration; + +namespace EasyCaching.SQLServer.Configurations +{ + /// + /// Easy caching options extensions. + /// + public static class EasyCachingOptionsExtensions + { + /// + /// Uses the SQLite. + /// + /// The SQL ite. + /// Options. + /// Configure. + /// Name. + public static EasyCachingOptions UseSQLServer(this EasyCachingOptions options, Action configure, string name = "") + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + options.RegisterExtension(new SQLServerOptionsExtension(name, configure)); + return options; + } + + /// + /// Uses the SQLite. + /// + /// The SQL ite. + /// Options. + /// Configuration. + /// Name. + /// Section name. + public static EasyCachingOptions UseSQLServer(this EasyCachingOptions options, IConfiguration configuration, string name = "", string sectionName = EasyCachingConstValue.SQLServerSection) + { + var dbConfig = configuration.GetSection(sectionName); + var serverOptions = new SQLServerOptions(); + dbConfig.Bind(serverOptions); + + void configure(SQLServerOptions x) + { + x.CachingProviderType = serverOptions.CachingProviderType; + x.EnableLogging = serverOptions.EnableLogging; + x.MaxRdSecond = serverOptions.MaxRdSecond; + x.Order = serverOptions.Order; + x.DBConfig = serverOptions.DBConfig; + } + + options.RegisterExtension(new SQLServerOptionsExtension(name, configure)); + return options; + } + } +} diff --git a/src/EasyCaching.SQLServerCache/Configurations/ISQLDatabaseProvider.cs b/src/EasyCaching.SQLServerCache/Configurations/ISQLDatabaseProvider.cs new file mode 100644 index 00000000..5a6c56b2 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/Configurations/ISQLDatabaseProvider.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; + +namespace EasyCaching.SQLServer.Configurations +{ + public interface ISQLDatabaseProvider + { + /// + /// Gets the connection. + /// + /// The connection. + IDbConnection GetConnection(); + + string DBProviderName { get; } + } +} diff --git a/src/EasyCaching.SQLServerCache/Configurations/SQLDBOptions.cs b/src/EasyCaching.SQLServerCache/Configurations/SQLDBOptions.cs new file mode 100644 index 00000000..c0a955d4 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/Configurations/SQLDBOptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EasyCaching.SQLServer.Configurations +{ + public class SQLDBOptions + { + public string ConnectionString { get; set; } + public string SchemaName { get; set; } + public string TableName { get; set; } + public TimeSpan ExpirationScanFrequency { get; set; } = TimeSpan.FromMinutes(1); + } +} diff --git a/src/EasyCaching.SQLServerCache/Configurations/SQLDatabaseProvider.cs b/src/EasyCaching.SQLServerCache/Configurations/SQLDatabaseProvider.cs new file mode 100644 index 00000000..e84e57a6 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/Configurations/SQLDatabaseProvider.cs @@ -0,0 +1,34 @@ +using EasyCaching.Core; +using Microsoft.Extensions.Options; +using System.Data; +using System.Data.SqlClient; + +namespace EasyCaching.SQLServer.Configurations +{ + public class SQLDatabaseProvider : ISQLDatabaseProvider + { + /// + /// The options. + /// + private readonly SQLDBOptions _options; + + public SQLDatabaseProvider(IOptionsMonitor optionAction) + { + this._options = optionAction.CurrentValue.DBConfig; + } + + public SQLDatabaseProvider(string name, SQLServerOptions options) + { + this._name = name; + this._options = options.DBConfig; + } + + public IDbConnection GetConnection() + { + return new SqlConnection(_options.ConnectionString); + } + + private readonly string _name = EasyCachingConstValue.DefaultSQLServerName; + public string DBProviderName => _name; + } +} \ No newline at end of file diff --git a/src/EasyCaching.SQLServerCache/Configurations/SQLServerOptions.cs b/src/EasyCaching.SQLServerCache/Configurations/SQLServerOptions.cs new file mode 100644 index 00000000..ad32628b --- /dev/null +++ b/src/EasyCaching.SQLServerCache/Configurations/SQLServerOptions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; +using EasyCaching.Core; +using EasyCaching.Core.Configurations; + +namespace EasyCaching.SQLServer.Configurations +{ + public class SQLServerOptions : BaseProviderOptions + { + public SQLServerOptions() + { + base.CachingProviderType = CachingProviderType.SQLServer; + } + + public SQLDBOptions DBConfig { get; set; } = new SQLDBOptions(); + } +} diff --git a/src/EasyCaching.SQLServerCache/Configurations/SQLServerOptionsExtension.cs b/src/EasyCaching.SQLServerCache/Configurations/SQLServerOptionsExtension.cs new file mode 100644 index 00000000..2a24f6d3 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/Configurations/SQLServerOptionsExtension.cs @@ -0,0 +1,106 @@ +using System; +using Dapper; +using EasyCaching.Core; +using EasyCaching.Core.Configurations; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace EasyCaching.SQLServer.Configurations +{ + /// + /// SQLite options extension. + /// + internal sealed class SQLServerOptionsExtension : IEasyCachingOptionsExtension + { + /// + /// The name. + /// + private readonly string _name; + /// + /// The configure. + /// + private readonly Action configure; + + /// + /// Initializes a new instance of the class. + /// + /// Name. + /// Configure. + public SQLServerOptionsExtension(string name, Action configure) + { + this._name = name; + this.configure = configure; + } + + /// + /// Adds the services. + /// + /// Services. + public void AddServices(IServiceCollection services) + { + services.AddOptions(); + + if (string.IsNullOrWhiteSpace(_name)) + { + services.Configure(configure); + + services.TryAddSingleton(); + services.AddSingleton(); + } + else + { + services.Configure(_name, configure); + + services.AddSingleton(); + services.AddSingleton(x => + { + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(_name); + return new SQLDatabaseProvider(_name, options); + }); + + services.AddSingleton(x => + { + var dbProviders = x.GetServices(); + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(_name); + var factory = x.GetService(); + return new DefaultSQLServerCachingProvider(_name, dbProviders, options, factory); + }); + } + } + + /// + /// Withs the services. + /// + /// App. + public void WithServices(IApplicationBuilder app) + { + try + { + var dbProviders = app.ApplicationServices.GetServices(); + var optionsMon = app.ApplicationServices.GetRequiredService>(); + foreach (var dbProvider in dbProviders) + { + var conn = dbProvider.GetConnection(); + + if (conn.State == System.Data.ConnectionState.Closed) + { + conn.Open(); + } + + var options = optionsMon.Get(dbProvider.DBProviderName); + conn.Execute(string.Format(ConstSQL.CREATESQL, options.DBConfig.SchemaName, + options.DBConfig.TableName)); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + } +} diff --git a/src/EasyCaching.SQLServerCache/DefaultSQLServerCachingProvider.cs b/src/EasyCaching.SQLServerCache/DefaultSQLServerCachingProvider.cs new file mode 100644 index 00000000..08e73d15 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/DefaultSQLServerCachingProvider.cs @@ -0,0 +1,850 @@ +using Dapper; +using EasyCaching.Core; +using EasyCaching.SQLServer.Configurations; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; + +namespace EasyCaching.SQLServer +{ + /// + /// SQLiteCaching provider. + /// + public class DefaultSQLServerCachingProvider : IEasyCachingProvider + { + /// + /// The cache. + /// + private ISQLDatabaseProvider _dbProvider; + + /// + /// The options. + /// + private readonly SQLServerOptions _options; + + /// + /// The logger. + /// + private readonly ILogger _logger; + + /// + /// The cache stats. + /// + private readonly CacheStats _cacheStats; + + /// + /// The name. + /// + private readonly string _name; + + private DateTimeOffset _lastScanTime; + + /// + /// Initializes a new instance of the class. + /// + /// dbProvider. + /// The options. + /// The logger factory. + public DefaultSQLServerCachingProvider( + ISQLDatabaseProvider dbProvider, + IOptionsMonitor options, + ILoggerFactory loggerFactory = null) + { + this._dbProvider = dbProvider; + this._options = options.CurrentValue; + this._logger = loggerFactory?.CreateLogger(); + this._cacheStats = new CacheStats(); + this._name = EasyCachingConstValue.DefaultSQLServerName; + _lastScanTime = SystemClock.UtcNow; + } + + public DefaultSQLServerCachingProvider( + string name, + IEnumerable dbProviders, + SQLServerOptions options, + ILoggerFactory loggerFactory = null) + { + this._dbProvider = dbProviders.FirstOrDefault(x => x.DBProviderName.Equals(name)); + this._options = options; + this._logger = loggerFactory?.CreateLogger(); + this._cacheStats = new CacheStats(); + this._name = name; + _lastScanTime = SystemClock.UtcNow; + } + + /// + /// is distributed cache. + /// + /// + /// true if is distributed cache; otherwise, false. + /// + public bool IsDistributedCache => true; + + /// + /// Gets the order. + /// + /// The order. + public int Order => _options.Order; + + /// + /// Gets the max random second. + /// + /// The max random second. + public int MaxRdSecond => _options.MaxRdSecond; + + /// + /// Gets the type of the caching provider. + /// + /// The type of the caching provider. + public CachingProviderType CachingProviderType => _options.CachingProviderType; + + /// + /// Gets the cache stats. + /// + /// The cache stats. + public CacheStats CacheStats => _cacheStats; + + /// + /// Gets the name. + /// + /// The name. + public string Name => this._name; + + /// + /// Check whether the specified cacheKey exists or not. + /// + /// The exists. + /// Cache key. + public bool Exists(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + using (var connection = _dbProvider.GetConnection()) + { + var dbResult = connection.ExecuteScalar(GetSQL(ConstSQL.EXISTSSQL), new + { + cachekey = cacheKey, + name = _name + }); + + return dbResult == 1; + } + } + + /// + /// Check whether the specified cacheKey exists or not. + /// + /// The async. + /// Cache key. + public async Task ExistsAsync(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + using (var connection = _dbProvider.GetConnection()) + { + var dbResult = await connection.ExecuteScalarAsync(GetSQL(ConstSQL.EXISTSSQL), new + { + cachekey = cacheKey, + name = _name + }); + + return dbResult == 1; + } + } + + /// + /// Get the specified cacheKey, dataRetriever and expiration. + /// + /// The get. + /// Cache key. + /// Data retriever. + /// Expiration. + /// The 1st type parameter. + public CacheValue Get(string cacheKey, Func dataRetriever, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + using (var connection = _dbProvider.GetConnection()) + { + var dbResult = connection.Query(GetSQL(ConstSQL.GETSQL), new + { + cachekey = cacheKey, + name = _name + }).ToList().FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(dbResult)) + { + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + + CacheStats.OnHit(); + + return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), 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; + } + } + + /// + /// Gets the specified cacheKey, dataRetriever and expiration async. + /// + /// The async. + /// Cache key. + /// Data retriever. + /// Expiration. + /// The 1st type parameter. + public async Task> GetAsync(string cacheKey, Func> dataRetriever, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + using (var connection = _dbProvider.GetConnection()) + { + var list = (await connection.QueryAsync(GetSQL(ConstSQL.GETSQL), new + { + cachekey = cacheKey, + name = _name + })).ToList(); + + var dbResult = list.FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(dbResult)) + { + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + + CacheStats.OnHit(); + + return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); + } + } + + CacheStats.OnMiss(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); + + var item = await dataRetriever?.Invoke(); + + if (item != null) + { + await SetAsync(cacheKey, item, expiration); + return new CacheValue(item, true); + } + else + { + return CacheValue.NoValue; + } + } + + /// + /// Get the specified cacheKey. + /// + /// The get. + /// Cache key. + /// The 1st type parameter. + public CacheValue Get(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + using (var connection = _dbProvider.GetConnection()) + { + var dbResult = connection.Query(GetSQL(ConstSQL.GETSQL), new + { + cachekey = cacheKey, + name = _name + }).ToList().FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(dbResult)) + { + CacheStats.OnHit(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + + return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); + } + else + { + CacheStats.OnMiss(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); + + return CacheValue.NoValue; + } + } + } + + /// + /// Gets the specified cacheKey async. + /// + /// The async. + /// Cache key. + /// The 1st type parameter. + public async Task> GetAsync(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + + using (var connection = _dbProvider.GetConnection()) + { + var list = (await connection.QueryAsync(GetSQL(ConstSQL.GETSQL), new + { + cachekey = cacheKey, + name = _name + })).ToList(); + + var dbResult = list.FirstOrDefault(); + + if (!string.IsNullOrWhiteSpace(dbResult)) + { + CacheStats.OnHit(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Hit : cachekey = {cacheKey}"); + + return new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(dbResult), true); + } + else + { + CacheStats.OnMiss(); + + if (_options.EnableLogging) + _logger?.LogInformation($"Cache Missed : cachekey = {cacheKey}"); + + return CacheValue.NoValue; + } + } + } + + /// + /// Remove the specified cacheKey. + /// + /// The remove. + /// Cache key. + public void Remove(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + using (var connection = _dbProvider.GetConnection()) + { + connection.Execute(GetSQL(ConstSQL.REMOVESQL), new {cachekey = cacheKey, name = _name}); + } + } + + /// + /// Removes the specified cacheKey async. + /// + /// The async. + /// Cache key. + public async Task RemoveAsync(string cacheKey) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + using (var connection = _dbProvider.GetConnection()) + { + await connection.ExecuteAsync(GetSQL(ConstSQL.REMOVESQL), new {cachekey = cacheKey, name = _name}); + } + } + + /// + /// Set the specified cacheKey, cacheValue and expiration. + /// + /// The set. + /// Cache key. + /// Cache value. + /// Expiration. + /// The 1st type parameter. + public void Set(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + if (MaxRdSecond > 0) + { + var addSec = new Random().Next(1, MaxRdSecond); + expiration.Add(new TimeSpan(0, 0, addSec)); + } + + using (var connection = _dbProvider.GetConnection()) + { + connection.Execute(GetSQL(ConstSQL.SETSQL), new + { + cachekey = cacheKey, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(cacheValue), + expiration = expiration.Ticks / 10000000 + }); + } + + CleanExpiredEntries(); + } + + /// + /// Sets the specified cacheKey, cacheValue and expiration async. + /// + /// The async. + /// Cache key. + /// Cache value. + /// Expiration. + /// The 1st type parameter. + public async Task SetAsync(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + if (MaxRdSecond > 0) + { + var addSec = new Random().Next(1, MaxRdSecond); + expiration.Add(new TimeSpan(0, 0, addSec)); + } + + using (var connection = _dbProvider.GetConnection()) + { + await connection.ExecuteAsync(GetSQL(ConstSQL.SETSQL), new + { + cachekey = cacheKey, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(cacheValue), + expiration = expiration.Ticks / 10000000 + }); + } + + CleanExpiredEntries(); + } + + /// + /// Refresh the specified cacheKey, cacheValue and expiration. + /// + /// Cache key. + /// Cache value. + /// Expiration. + /// The 1st type parameter. + public void Refresh(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + this.Remove(cacheKey); + this.Set(cacheKey, cacheValue, expiration); + } + + /// + /// Refreshs the specified cacheKey, cacheValue and expiration. + /// + /// The async. + /// Cache key. + /// Cache value. + /// Expiration. + /// The 1st type parameter. + public async Task RefreshAsync(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + await this.RemoveAsync(cacheKey); + await this.SetAsync(cacheKey, cacheValue, expiration); + } + + /// + /// Removes cached item by cachekey's prefix. + /// + /// Prefix of CacheKey. + public void RemoveByPrefix(string prefix) + { + ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); + + if (_options.EnableLogging) + _logger?.LogInformation($"RemoveByPrefix : prefix = {prefix}"); + using (var connection = _dbProvider.GetConnection()) + { + connection.Execute(GetSQL(ConstSQL.REMOVEBYPREFIXSQL), + new {cachekey = string.Concat(prefix, "%"), name = _name}); + } + } + + /// + /// Removes cached item by cachekey's prefix async. + /// + /// Prefix of CacheKey. + public async Task RemoveByPrefixAsync(string prefix) + { + ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); + + if (_options.EnableLogging) + _logger?.LogInformation($"RemoveByPrefixAsync : prefix = {prefix}"); + using (var connection = _dbProvider.GetConnection()) + { + await connection.ExecuteAsync(GetSQL(ConstSQL.REMOVEBYPREFIXSQL), + new {cachekey = string.Concat(prefix, "%"), name = _name}); + } + } + + /// + /// Sets all. + /// + /// Values. + /// Expiration. + /// The 1st type parameter. + public void SetAll(IDictionary values, TimeSpan expiration) + { + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); + + using (var connection = _dbProvider.GetConnection()) + { + var paramList = new List(); + foreach (var item in values) + { + paramList.Add(new + { + cachekey = item.Key, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(item.Value), + expiration = expiration.Ticks / 10000000 + }); + } + + connection.Execute(GetSQL(ConstSQL.SETSQL), paramList); + + } + + CleanExpiredEntries(); + } + + /// + /// Sets all async. + /// + /// The all async. + /// Values. + /// Expiration. + /// The 1st type parameter. + public async Task SetAllAsync(IDictionary values, TimeSpan expiration) + { + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + ArgumentCheck.NotNullAndCountGTZero(values, nameof(values)); + + using (var connection = _dbProvider.GetConnection()) + { + var paramList = new List(); + foreach (var item in values) + { + paramList.Add(new + { + cachekey = item.Key, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(item.Value), + expiration = expiration.Ticks / 10000000 + }); + } + + await connection.ExecuteAsync(GetSQL(ConstSQL.SETSQL), paramList); + } + + CleanExpiredEntries(); + } + + /// + /// Gets all. + /// + /// The all. + /// Cache keys. + /// The 1st type parameter. + public IDictionary> GetAll(IEnumerable cacheKeys) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + + using (var connection = _dbProvider.GetConnection()) + { + var list = connection.Query(GetSQL(ConstSQL.GETALLSQL), new + { + cachekey = cacheKeys.ToArray(), + name = _name + }).ToList(); + + return GetDict(list); + } + } + + /// + /// Gets all async. + /// + /// The all async. + /// Cache keys. + /// The 1st type parameter. + public async Task>> GetAllAsync(IEnumerable cacheKeys) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + + using (var connection = _dbProvider.GetConnection()) + { + var list = (await connection.QueryAsync(GetSQL(ConstSQL.GETALLSQL), new + { + cachekey = cacheKeys.ToArray(), + name = _name + })).ToList(); + + return GetDict(list); + } + } + + /// + /// Gets the dict. + /// + /// The dict. + /// List. + /// The 1st type parameter. + private IDictionary> GetDict(List list) + { + var result = new Dictionary>(); + foreach (var item in list) + { + if (!string.IsNullOrWhiteSpace(item.cachekey)) + result.Add(item.cachekey, new CacheValue(Newtonsoft.Json.JsonConvert.DeserializeObject(item.cachevalue), true)); + else + result.Add(item.cachekey, CacheValue.NoValue); + } + return result; + } + + /// + /// Gets the by prefix. + /// + /// The by prefix. + /// Prefix. + /// The 1st type parameter. + public IDictionary> GetByPrefix(string prefix) + { + ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); + + using (var connection = _dbProvider.GetConnection()) + { + var list = connection.Query(GetSQL(ConstSQL.GETBYPREFIXSQL), new + { + cachekey = string.Concat(prefix, "%"), + name = _name + }).ToList(); + + return GetDict(list); + } + } + + /// + /// Gets the by prefix async. + /// + /// The by prefix async. + /// Prefix. + /// The 1st type parameter. + public async Task>> GetByPrefixAsync(string prefix) + { + ArgumentCheck.NotNullOrWhiteSpace(prefix, nameof(prefix)); + + using (var connection = _dbProvider.GetConnection()) + { + var list = (await connection.QueryAsync(GetSQL(ConstSQL.GETBYPREFIXSQL), new + { + cachekey = string.Concat(prefix, "%"), + name = _name + })).ToList(); + + return GetDict(list); + } + } + + /// + /// Removes all. + /// + /// Cache keys. + public void RemoveAll(IEnumerable cacheKeys) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + + using (var connection = _dbProvider.GetConnection()) + { + var paramList = new List(); + foreach (var item in cacheKeys) + { + paramList.Add(new { cachekey = item, name = _name }); + } + connection.Execute(GetSQL(ConstSQL.REMOVESQL), paramList); + } + } + + /// + /// Removes all async. + /// + /// The all async. + /// Cache keys. + public async Task RemoveAllAsync(IEnumerable cacheKeys) + { + ArgumentCheck.NotNullAndCountGTZero(cacheKeys, nameof(cacheKeys)); + + using (var connection = _dbProvider.GetConnection()) + { + var paramList = new List(); + foreach (var item in cacheKeys) + { + paramList.Add(new { cachekey = item, name = _name }); + } + await connection.ExecuteAsync(GetSQL(ConstSQL.REMOVESQL), paramList); + + } + } + + /// + /// Gets the count. + /// + /// The count. + /// Prefix. + public int GetCount(string prefix = "") + { + using (var connection = _dbProvider.GetConnection()) + { + if (string.IsNullOrWhiteSpace(prefix)) + { + return connection.ExecuteScalar(GetSQL(ConstSQL.COUNTALLSQL), new {name = _name}); + } + else + { + return connection.ExecuteScalar(GetSQL(ConstSQL.COUNTPREFIXSQL), + new {cachekey = string.Concat(prefix, "%"), name = _name}); + } + } + } + + /// + /// Flush All Cached Item. + /// + public void Flush() + { + using (var connection = _dbProvider.GetConnection()) + { + connection.Execute(GetSQL(ConstSQL.FLUSHSQL), new {name = _name}); + } + } + + /// + /// Flush All Cached Item async. + /// + /// The async. + public async Task FlushAsync() + { + using (var connection = _dbProvider.GetConnection()) + { + await connection.ExecuteAsync(GetSQL(ConstSQL.FLUSHSQL), new {name = _name}); + } + } + + /// + /// Tries the set. + /// + /// true, if set was tryed, false otherwise. + /// Cache key. + /// Cache value. + /// Expiration. + /// The 1st type parameter. + public bool TrySet(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + if (MaxRdSecond > 0) + { + var addSec = new Random().Next(1, MaxRdSecond); + expiration.Add(new TimeSpan(0, 0, addSec)); + } + + using (var connection = _dbProvider.GetConnection()) + { + var rows = connection.Execute(GetSQL(ConstSQL.TRYSETSQL), new + { + cachekey = cacheKey, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(cacheValue), + expiration = expiration.Ticks / 10000000 + }); + + return rows > 0; + } + } + + /// + /// Tries the set async. + /// + /// The set async. + /// Cache key. + /// Cache value. + /// Expiration. + /// The 1st type parameter. + public async Task TrySetAsync(string cacheKey, T cacheValue, TimeSpan expiration) + { + ArgumentCheck.NotNullOrWhiteSpace(cacheKey, nameof(cacheKey)); + ArgumentCheck.NotNull(cacheValue, nameof(cacheValue)); + ArgumentCheck.NotNegativeOrZero(expiration, nameof(expiration)); + + if (MaxRdSecond > 0) + { + var addSec = new Random().Next(1, MaxRdSecond); + expiration.Add(new TimeSpan(0, 0, addSec)); + } + + using (var connection = _dbProvider.GetConnection()) + { + var rows = await connection.ExecuteAsync(GetSQL(ConstSQL.TRYSETSQL), new + { + cachekey = cacheKey, + name = _name, + cachevalue = Newtonsoft.Json.JsonConvert.SerializeObject(cacheValue), + expiration = expiration.Ticks / 10000000 + }); + + return rows > 0; + } + } + + + private void CleanExpiredEntries() + { + if (SystemClock.UtcNow > _lastScanTime.Add(_options.DBConfig.ExpirationScanFrequency)) + { + Task.Run(() => + { + using (var connection = _dbProvider.GetConnection()) + { + connection.Execute(GetSQL(ConstSQL.CLEANEXPIREDSQL)); + } + }); + _lastScanTime = SystemClock.UtcNow; + } + } + + private string GetSQL(string sql) + { + return string.Format(sql, _options.DBConfig.SchemaName, _options.DBConfig.TableName); + } + } +} \ No newline at end of file diff --git a/src/EasyCaching.SQLServerCache/EasyCaching.SQLServerCache.csproj b/src/EasyCaching.SQLServerCache/EasyCaching.SQLServerCache.csproj new file mode 100644 index 00000000..0202f38a --- /dev/null +++ b/src/EasyCaching.SQLServerCache/EasyCaching.SQLServerCache.csproj @@ -0,0 +1,33 @@ + + + + netstandard2.0 + EasyCaching.SQLServer + EasyCaching.SQLServer + 1.0.0.0 + 1.0.0.0 + 1.0.0 + false + Wasenshi + Use SQL server as a provider for caching. + + + + + + + + + + + + + + ..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.csharp\4.3.0\ref\netstandard1.0\Microsoft.CSharp.dll + + + ..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.extensions.logging.abstractions\2.1.1\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + + diff --git a/src/EasyCaching.SQLServerCache/NUGET.txt b/src/EasyCaching.SQLServerCache/NUGET.txt new file mode 100644 index 00000000..8025a021 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/NUGET.txt @@ -0,0 +1,3 @@ +nuget pack src\EasyCaching.SQLServerCache\EasyCaching.SQLServerCache.csproj -version 1.0.0 -IncludeReferencedProjects -properties Configuration=Debug + +nuget push EasyCaching.SQLServer.1.0.0.nupkg AppMan2004zZ -Source https://appmannuget.azurewebsites.net/nuget diff --git a/src/EasyCaching.SQLServerCache/SQLServerApplicationBuliderExtensions.cs b/src/EasyCaching.SQLServerCache/SQLServerApplicationBuliderExtensions.cs new file mode 100644 index 00000000..0a108dbb --- /dev/null +++ b/src/EasyCaching.SQLServerCache/SQLServerApplicationBuliderExtensions.cs @@ -0,0 +1,45 @@ +using Dapper; +using EasyCaching.SQLServer.Configurations; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.Options; + +namespace EasyCaching.SQLServer +{ + /// + /// SQLite cache application bulider extensions. + /// + public static class SQLServerApplicationBuliderExtensions + { + /// + /// Uses the SQLite cache. + /// + /// The SQLite cache. + /// App. + public static IApplicationBuilder UseSQLServerCache(this IApplicationBuilder app) + { + try + { + var dbProvider = app.ApplicationServices.GetService(); + + var conn = dbProvider.GetConnection(); + + if (conn.State == System.Data.ConnectionState.Closed) + { + conn.Open(); + } + + var optionsMon = app.ApplicationServices.GetRequiredService>(); + var options = optionsMon.CurrentValue.DBConfig; + conn.Execute(string.Format(ConstSQL.CREATESQL, options.SchemaName, + options.TableName)); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + return app; + } + } +} \ No newline at end of file diff --git a/src/EasyCaching.SQLServerCache/SQLServerCacheServiceCollectionExtensions.cs b/src/EasyCaching.SQLServerCache/SQLServerCacheServiceCollectionExtensions.cs new file mode 100644 index 00000000..16d41205 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/SQLServerCacheServiceCollectionExtensions.cs @@ -0,0 +1,145 @@ +using EasyCaching.Core; +using EasyCaching.SQLServer.Configurations; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System; + +namespace EasyCaching.SQLServer +{ + public static class SQLServerCacheServiceCollectionExtensions + { + /// + /// Adds the SQL ite cache. + /// + /// The SQL ite cache. + /// Services. + /// Provider action. + public static IServiceCollection AddSQLServerCache( + this IServiceCollection services, + Action providerAction) + { + ArgumentCheck.NotNull(services, nameof(services)); + + services.AddOptions() + .Configure(providerAction); + + services.TryAddSingleton(); + services.AddSingleton(); + + return services; + } + + /// + /// Adds the SQLite cache. + /// + /// The SQL ite cache. + /// + /// + /// + /// Services. + /// Configuration. + public static IServiceCollection AddSQLServerCache( + this IServiceCollection services, + IConfiguration configuration) + { + var dbConfig = configuration.GetSection(EasyCachingConstValue.SQLServerSection); + services.Configure(dbConfig); + + services.TryAddSingleton(); + services.AddSingleton(); + + return services; + } + + /// + /// Adds the SQL server cache with factory. + /// + /// The SQL ite cache with factory. + /// Services. + /// Provider name. + /// Provider action. + public static IServiceCollection AddSQLServerCacheWithFactory( + this IServiceCollection services, + string providerName, + Action providerAction) + { + ArgumentCheck.NotNull(services, nameof(services)); + ArgumentCheck.NotNullOrWhiteSpace(providerName, nameof(providerName)); + + services.AddOptions() + .Configure(providerName, providerAction) + .AddSingleton() + .AddSingleton(x => + { + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(providerName); + return new SQLDatabaseProvider(providerName, options); + }) + .AddSingleton(x => + { + var dbProviders = x.GetServices(); + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(providerName); + var factory = x.GetService(); + return new DefaultSQLServerCachingProvider(providerName, dbProviders, options, factory); + }); + + return services; + } + + /// + /// Adds the SQL server cache with factory. + /// + /// The SQL ite cache with factory. + /// Services. + /// Provider name. + /// Section name. + /// Configuration. + public static IServiceCollection AddSQLServerCacheWithFactory( + this IServiceCollection services, + string providerName, + string sectionName, + IConfiguration configuration) + { + ArgumentCheck.NotNullOrWhiteSpace(providerName, nameof(providerName)); + ArgumentCheck.NotNullOrWhiteSpace(sectionName, nameof(sectionName)); + + var cacheConfig = configuration.GetSection(sectionName); + services.Configure(providerName, cacheConfig) + .AddSingleton() + .AddSingleton(x => + { + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(providerName); + return new SQLDatabaseProvider(providerName, options); + }); + + services.AddSingleton(x => + { + var dbProviders = x.GetServices(); + var optionsMon = x.GetRequiredService>(); + var options = optionsMon.Get(providerName); + var factory = x.GetService(); + return new DefaultSQLServerCachingProvider(providerName, dbProviders, options, factory); + }); + + return services; + } + } +} \ No newline at end of file diff --git a/src/EasyCaching.SQLServerCache/SystemClock.cs b/src/EasyCaching.SQLServerCache/SystemClock.cs new file mode 100644 index 00000000..a5874f30 --- /dev/null +++ b/src/EasyCaching.SQLServerCache/SystemClock.cs @@ -0,0 +1,28 @@ +using System; + +namespace EasyCaching.SQLServer +{ + public interface ISystemClock + { + DateTimeOffset UtcNow(); + } + + internal class DefaultSystemClock : ISystemClock + { + public static readonly DefaultSystemClock Instance = new DefaultSystemClock(); + + public DateTimeOffset UtcNow() => DateTimeOffset.UtcNow; + } + + internal static class SystemClock + { + private static ISystemClock _instance = DefaultSystemClock.Instance; + public static ISystemClock Instance + { + get => _instance ?? DefaultSystemClock.Instance; + set => _instance = value; + } + + public static DateTimeOffset UtcNow => Instance.UtcNow(); + } +} diff --git a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs index 45ffc180..87be581a 100644 --- a/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs +++ b/src/EasyCaching.SQLite/DefaultSQLiteCachingProvider.cs @@ -60,7 +60,7 @@ public DefaultSQLiteCachingProvider( this._logger = loggerFactory?.CreateLogger(); this._cache = _dbProvider.GetConnection(); this._cacheStats = new CacheStats(); - this._name = EasyCachingConstValue.DefaultRedisName; + this._name = EasyCachingConstValue.DefaultSQLiteName; } public DefaultSQLiteCachingProvider( diff --git a/test/EasyCaching.UnitTests/CachingTests/SQLServerCachingTest.cs b/test/EasyCaching.UnitTests/CachingTests/SQLServerCachingTest.cs new file mode 100644 index 00000000..fc9826c4 --- /dev/null +++ b/test/EasyCaching.UnitTests/CachingTests/SQLServerCachingTest.cs @@ -0,0 +1,307 @@ +using System.Threading; +using EasyCaching.SQLServer; +using EasyCaching.SQLServer.Configurations; +using Microsoft.Extensions.Options; + +namespace EasyCaching.UnitTests +{ + using Dapper; + using EasyCaching.Core; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using System; + using System.IO; + using System.Threading.Tasks; + using Xunit; + + public class SQLServerCachingTest : BaseCachingProviderTest + { + private readonly ISQLDatabaseProvider _dbProvider; + + private const string CONNECTION_STRING = "Data Source=.\\sqlexpress;Initial Catalog=EasyCacheDB;Integrated Security=True;"; + private const string SCHEMA_NAME = "Easy"; + private const string TABLE_NAME = "Cache"; + + public SQLServerCachingTest() + { + IServiceCollection services = new ServiceCollection(); + services.AddSQLServerCache(options => + { + options.DBConfig = new SQLDBOptions + { + ConnectionString = CONNECTION_STRING, + SchemaName = SCHEMA_NAME, + TableName = TABLE_NAME, + ExpirationScanFrequency = TimeSpan.FromSeconds(1) + }; + }); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + _dbProvider = serviceProvider.GetService(); + var optionsMon = serviceProvider.GetRequiredService>(); + + var conn = _dbProvider.GetConnection(); + if (conn.State == System.Data.ConnectionState.Closed) + { + conn.Open(); + } + var op = optionsMon.CurrentValue.DBConfig; + var sql = string.Format(ConstSQL.CREATESQL, op.SchemaName, + op.TableName); + conn.Execute(sql); + + _provider = new DefaultSQLServerCachingProvider(_dbProvider, new TestOptionMonitorWrapper(optionsMon.CurrentValue)); + _defaultTs = TimeSpan.FromHours(1); + } + + [Fact] + protected override Task GetAsync_Parallel_Should_Succeed() + { + return Task.FromResult(1); + + } + + [Fact] + protected override void Get_Parallel_Should_Succeed() + { + + } + + [Fact] + public async Task Expired_Cache_Should_Be_Removed() + { + var conn = _dbProvider.GetConnection(); + var sql = $"SELECT count(1) FROM {SCHEMA_NAME}.{TABLE_NAME} WHERE [cacheKey]=@cacheKey"; + + var cacheKey = $"TestExpire-{Guid.NewGuid().ToString()}"; + var cacheKey2 = cacheKey + "_2"; + var cacheValue = "value"; + + _provider.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(1)); + _provider.Set(cacheKey2, cacheValue, _defaultTs); + + await Task.Delay(TimeSpan.FromSeconds(2)); + _provider.Set("RemoveExpire", "", TimeSpan.FromMilliseconds(10)); + + var cache = _provider.Get(cacheKey); + var actualRow = conn.ExecuteScalar(sql, new { cachekey = cacheKey }); + Assert.False(cache.HasValue); + Assert.False(actualRow > 0); + cache = _provider.Get(cacheKey2); + actualRow = conn.ExecuteScalar(sql, new { cachekey = cacheKey2 }); + Assert.True(cache.HasValue); + Assert.True(actualRow > 0); + + await Task.Delay(TimeSpan.FromSeconds(2)); + _provider.Set("RemoveExpire", "", TimeSpan.FromMilliseconds(10)); + + cache = _provider.Get(cacheKey2); + actualRow = conn.ExecuteScalar(sql, new { cachekey = cacheKey2 }); + Assert.True(cache.HasValue); + Assert.True(actualRow > 0); + } + } + + + public class SQLServerCachingProviderWithFactoryTest : BaseCachingProviderWithFactoryTest + { + private const string CONNECTION_STRING = "Data Source=.\\sqlexpress;Initial Catalog=EasyCacheDB;Integrated Security=True;"; + private const string SCHEMA_NAME = "Easy"; + private const string TABLE_NAME = "Cache"; + + public SQLServerCachingProviderWithFactoryTest() + { + IServiceCollection services = new ServiceCollection(); + services.AddSQLServerCacheWithFactory(EasyCachingConstValue.DefaultSQLServerName, options => + { + options.DBConfig = new SQLDBOptions + { + ConnectionString = CONNECTION_STRING, + SchemaName = SCHEMA_NAME, + TableName = TABLE_NAME + }; + + }); + services.AddSQLServerCacheWithFactory(SECOND_PROVIDER_NAME, options => + { + options.DBConfig = new SQLDBOptions + { + ConnectionString = CONNECTION_STRING, + SchemaName = SCHEMA_NAME, + TableName = TABLE_NAME + }; + }); + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetService(); + _provider = factory.GetCachingProvider(EasyCachingConstValue.DefaultSQLServerName); + _secondProvider = factory.GetCachingProvider(SECOND_PROVIDER_NAME); + + var _dbProviders = serviceProvider.GetServices(); + var optionsMon = serviceProvider.GetRequiredService>(); + foreach (var _dbProvider in _dbProviders) + { + var conn = _dbProvider.GetConnection(); + if (conn.State == System.Data.ConnectionState.Closed) + { + conn.Open(); + } + var options = optionsMon.Get(_dbProvider.DBProviderName); + conn.Execute(string.Format(ConstSQL.CREATESQL, options.DBConfig.SchemaName, + options.DBConfig.TableName)); + } + + _defaultTs = TimeSpan.FromSeconds(30); + } + } + + public class SQLServerCachingProviderUseEasyCachingTest : BaseUsingEasyCachingTest + { + private readonly IEasyCachingProvider _secondProvider; + private const string SECOND_PROVIDER_NAME = "second"; + + private const string CONNECTION_STRING = "Data Source=.\\sqlexpress;Initial Catalog=EasyCacheDB;Integrated Security=True;"; + private const string SCHEMA_NAME = "Easy"; + private const string TABLE_NAME = "Cache"; + + public SQLServerCachingProviderUseEasyCachingTest() + { + IServiceCollection services = new ServiceCollection(); + + services.AddEasyCaching(option => + { + option.UseSQLServer(config => + { + config.DBConfig = new SQLDBOptions() + { + ConnectionString = CONNECTION_STRING, + SchemaName = SCHEMA_NAME, + TableName = TABLE_NAME + }; + }, EasyCachingConstValue.DefaultSQLServerName); + option.UseSQLServer(config => + { + config.DBConfig = new SQLDBOptions() + { + ConnectionString = CONNECTION_STRING, + SchemaName = SCHEMA_NAME, + TableName = TABLE_NAME + }; + }, SECOND_PROVIDER_NAME); + }); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + var factory = serviceProvider.GetService(); + _provider = factory.GetCachingProvider(EasyCachingConstValue.DefaultSQLServerName); + _secondProvider = factory.GetCachingProvider(SECOND_PROVIDER_NAME); + + var dbProviders = serviceProvider.GetServices(); + var optionsMon = serviceProvider.GetRequiredService>(); + foreach (var dbProvider in dbProviders) + { + var conn = dbProvider.GetConnection(); + + if (conn.State == System.Data.ConnectionState.Closed) + { + conn.Open(); + } + + var options = optionsMon.Get(dbProvider.DBProviderName); + var sql = string.Format(ConstSQL.CREATESQL, options.DBConfig.SchemaName, + options.DBConfig.TableName); + conn.Execute(sql); + } + + _defaultTs = TimeSpan.FromSeconds(30); + } + + [Fact] + public void Sec_Set_Value_And_Get_Cached_Value_Should_Succeed() + { + var cacheKey = Guid.NewGuid().ToString(); + var cacheValue = "value"; + + _secondProvider.Set(cacheKey, cacheValue, _defaultTs); + + var val = _secondProvider.Get(cacheKey); + Assert.True(val.HasValue); + Assert.Equal(cacheValue, val.Value); + } + + [Fact] + public async Task Sec_Set_Value_And_Get_Cached_Value_Async_Should_Succeed() + { + var cacheKey = Guid.NewGuid().ToString(); + var cacheValue = "value"; + + await _secondProvider.SetAsync(cacheKey, cacheValue, _defaultTs); + + var val = await _secondProvider.GetAsync(cacheKey); + Assert.True(val.HasValue); + Assert.Equal(cacheValue, val.Value); + } + } + + public class SQLServerCachingProviderUseEasyCachingWithConfigTest : BaseUsingEasyCachingTest + { + public SQLServerCachingProviderUseEasyCachingWithConfigTest() + { + IServiceCollection services = new ServiceCollection(); + + var appsettings = @" +{ + 'easycaching': { + 'sqlserver': { + 'MaxRdSecond': 600, + 'Order': 99, + 'dbconfig': { + 'connectionString':'Data Source=.\\sqlexpress;Initial Catalog=EasyCacheDB;Integrated Security=True;', + 'schemaName':'Easy', + 'tableName':'Cache' + } + } + } +}"; + var path = TestHelpers.CreateTempFile(appsettings); + var directory = Path.GetDirectoryName(path); + var fileName = Path.GetFileName(path); + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.SetBasePath(directory); + configurationBuilder.AddJsonFile(fileName); + var config = configurationBuilder.Build(); + + services.AddEasyCaching(option => + { + option.UseSQLServer(config, "mName"); + }); + + IServiceProvider serviceProvider = services.BuildServiceProvider(); + _provider = serviceProvider.GetService(); + + var _dbProviders = serviceProvider.GetServices(); + var optionsMon = serviceProvider.GetRequiredService>(); + foreach (var _dbProvider in _dbProviders) + { + var conn = _dbProvider.GetConnection(); + if (conn.State == System.Data.ConnectionState.Closed) + { + conn.Open(); + } + var options = optionsMon.Get(_dbProvider.DBProviderName); + var sql = string.Format(ConstSQL.CREATESQL, options.DBConfig.SchemaName, + options.DBConfig.TableName); + conn.Execute(sql); + } + + _defaultTs = TimeSpan.FromSeconds(30); + } + + [Fact] + public void Provider_Information_Should_Be_Correct() + { + Assert.Equal(600, _provider.MaxRdSecond); + Assert.Equal(99, _provider.Order); + Assert.Equal("mName", _provider.Name); + } + } +} diff --git a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj index 0858e4d6..5a4457ff 100644 --- a/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj +++ b/test/EasyCaching.UnitTests/EasyCaching.UnitTests.csproj @@ -32,6 +32,7 @@ +