├── .gitignore ├── ColinChang.RedisHelper.Abp ├── ColinChang.RedisHelper.Abp.csproj └── RedisHelperModule.cs ├── ColinChang.RedisHelper.Sample ├── ColinChang.RedisHelper.Sample.csproj ├── Program.cs ├── appsettings.Development.json └── appsettings.json ├── ColinChang.RedisHelper.Test ├── ColinChang.RedisHelper.Test.csproj └── RedisHelperTest.cs ├── ColinChang.RedisHelper.sln ├── ColinChang.RedisHelper ├── ColinChang.RedisHelper.csproj ├── ColinChang.RedisHelper.xml ├── IRedisHelper.cs ├── RedisHelper.cs ├── RedisHelperExtensions.cs └── RedisHelperOptions.cs ├── LICENSE ├── README.md └── icon.png /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | .vscode 33 | .idea 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | **/Properties/launchSettings.json 59 | 60 | # StyleCop 61 | StyleCopReport.xml 62 | 63 | # Files built by Visual Studio 64 | *_i.c 65 | *_p.c 66 | *_i.h 67 | *.ilk 68 | *.meta 69 | *.obj 70 | *.iobj 71 | *.pch 72 | *.pdb 73 | *.ipdb 74 | *.pgc 75 | *.pgd 76 | *.rsp 77 | *.sbr 78 | *.tlb 79 | *.tli 80 | *.tlh 81 | *.tmp 82 | *.tmp_proj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | 235 | # RIA/Silverlight projects 236 | Generated_Code/ 237 | 238 | # Backup & report files from converting an old project file 239 | # to a newer Visual Studio version. Backup files are not needed, 240 | # because we have git ;-) 241 | _UpgradeReport_Files/ 242 | Backup*/ 243 | UpgradeLog*.XML 244 | UpgradeLog*.htm 245 | ServiceFabricBackup/ 246 | *.rptproj.bak 247 | 248 | # SQL Server files 249 | *.mdf 250 | *.ldf 251 | *.ndf 252 | 253 | # Business Intelligence projects 254 | *.rdl.data 255 | *.bim.layout 256 | *.bim_*.settings 257 | *.rptproj.rsuser 258 | 259 | # Microsoft Fakes 260 | FakesAssemblies/ 261 | 262 | # GhostDoc plugin setting file 263 | *.GhostDoc.xml 264 | 265 | # Node.js Tools for Visual Studio 266 | .ntvs_analysis.dat 267 | node_modules/ 268 | 269 | # Visual Studio 6 build log 270 | *.plg 271 | 272 | # Visual Studio 6 workspace options file 273 | *.opt 274 | 275 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 276 | *.vbw 277 | 278 | # Visual Studio LightSwitch build output 279 | **/*.HTMLClient/GeneratedArtifacts 280 | **/*.DesktopClient/GeneratedArtifacts 281 | **/*.DesktopClient/ModelManifest.xml 282 | **/*.Server/GeneratedArtifacts 283 | **/*.Server/ModelManifest.xml 284 | _Pvt_Extensions 285 | 286 | # Paket dependency manager 287 | .paket/paket.exe 288 | paket-files/ 289 | 290 | # FAKE - F# Make 291 | .fake/ 292 | 293 | # JetBrains Rider 294 | .idea/ 295 | *.sln.iml 296 | 297 | # CodeRush 298 | .cr/ 299 | 300 | # Python Tools for Visual Studio (PTVS) 301 | __pycache__/ 302 | *.pyc 303 | 304 | # Cake - Uncomment if you are using it 305 | # tools/** 306 | # !tools/packages.config 307 | 308 | # Tabs Studio 309 | *.tss 310 | 311 | # Telerik's JustMock configuration file 312 | *.jmconfig 313 | 314 | # BizTalk build output 315 | *.btp.cs 316 | *.btm.cs 317 | *.odx.cs 318 | *.xsd.cs 319 | 320 | # OpenCover UI analysis results 321 | OpenCover/ 322 | 323 | # Azure Stream Analytics local run output 324 | ASALocalRun/ 325 | 326 | # MSBuild Binary and Structured Log 327 | *.binlog 328 | 329 | # NVidia Nsight GPU debugger configuration file 330 | *.nvuser 331 | 332 | # MFractors (Xamarin productivity tool) working folder 333 | .mfractor/ 334 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Abp/ColinChang.RedisHelper.Abp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Abp/RedisHelperModule.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Volo.Abp.Modularity; 4 | 5 | namespace ColinChang.RedisHelper.Abp 6 | { 7 | public class RedisHelperModule : AbpModule 8 | { 9 | public override void ConfigureServices(ServiceConfigurationContext context) 10 | { 11 | var services = context.Services; 12 | var configuration = context.Services.GetConfiguration(); 13 | 14 | services.AddOptions() 15 | .Configure(configuration.Bind) 16 | .ValidateDataAnnotations(); 17 | services.AddSingleton(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Sample/ColinChang.RedisHelper.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace ColinChang.RedisHelper.Sample 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | Host.CreateDefaultBuilder(args) 14 | .ConfigureWebHostDefaults(webBuilder => 15 | { 16 | webBuilder.ConfigureServices((context, services) => services 17 | // .AddRedisHelper(options => 18 | // { 19 | // options.ConnectionString = 20 | // "192.168.0.203:6379,password=123123,connectTimeout=1000,connectRetry=1,syncTimeout=10000"; 21 | // options.DbNumber = 0; 22 | // }) 23 | .AddRedisHelper(context.Configuration.GetSection(nameof(RedisHelperOptions)))); 24 | 25 | webBuilder.Configure(app => app.Run(async context => 26 | { 27 | var redis = app.ApplicationServices.GetRequiredService(); 28 | 29 | const string key = "msg"; 30 | await redis.StringSetAsync(key, "hello world"); 31 | var content = await redis.StringGetAsync(key); 32 | await redis.KeyDeleteAsync(new[] {key}); 33 | await context.Response.WriteAsync(content); 34 | })); 35 | }) 36 | .Build() 37 | .Run(); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "RedisHelperOptions": { 11 | "ConnectionString": "127.0.0.1:6379,password=123123,connectTimeout=1000,connectRetry=1,syncTimeout=10000", 12 | "DbNumber": 0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Test/ColinChang.RedisHelper.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | default 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper.Test/RedisHelperTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using StackExchange.Redis; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace ColinChang.RedisHelper.Test 12 | { 13 | public class RedisHelperTest : IClassFixture 14 | { 15 | private readonly IRedisHelper _redis; 16 | private readonly ITestOutputHelper _testOutputHelper; 17 | 18 | public RedisHelperTest(RedisHelperFixture redis, ITestOutputHelper testOutputHelper) 19 | { 20 | _testOutputHelper = testOutputHelper; 21 | _redis = redis.Redis; 22 | } 23 | 24 | [Fact] 25 | public async Task StringTestAsync() 26 | { 27 | const string key = "name"; 28 | const string value = "Colin"; 29 | Assert.True(await _redis.StringSetAsync(key, value)); 30 | Assert.Equal(value, await _redis.StringGetAsync(key)); 31 | Assert.Equal(value, await _redis.StringGetAsync(key)); 32 | Assert.Null(await _redis.StringGetAsync("not_exist")); 33 | 34 | const string objKey = "person"; 35 | var people = new People("colin", 18); 36 | Assert.True(await _redis.StringSetAsync("person", people)); 37 | Assert.Equal(people, await _redis.StringGetAsync(objKey), new PeopleComparer()); 38 | 39 | Assert.Equal(2, await _redis.KeyDeleteAsync(new[] { key, objKey })); 40 | } 41 | 42 | [Fact] 43 | public async Task ListTestAsync() 44 | { 45 | const string key = "redPocket"; 46 | await _redis.EnqueueAsync(key, 4.8); 47 | await _redis.EnqueueAsync(key, 5.2); 48 | 49 | var pockets = await _redis.PeekRangeAsync(key, 1, 1); 50 | Assert.Equal("5.2", pockets.FirstOrDefault()); 51 | 52 | Assert.Equal("4.8", await _redis.DequeueAsync(key)); 53 | Assert.Equal("5.2", await _redis.DequeueAsync(key)); 54 | } 55 | 56 | [Fact] 57 | public async Task SetTestAsync() 58 | { 59 | const string key = "cameras"; 60 | 61 | Assert.True(await _redis.SetAddAsync(key, 0)); 62 | Assert.True(await _redis.SetAddAsync(key, 1)); 63 | Assert.True(await _redis.SetContainsAsync(key, 1)); 64 | Assert.False(await _redis.SetAddAsync(key, 1)); 65 | 66 | var cameras = await _redis.SetMembersAsync(key); 67 | foreach (var camera in cameras) 68 | _testOutputHelper.WriteLine(camera); 69 | 70 | Assert.Equal(2, await _redis.SetRemoveAsync(key, new[] { 0, 1 })); 71 | } 72 | 73 | [Fact] 74 | public async Task SortedSetTestAsync() 75 | { 76 | const string key = "top10"; 77 | 78 | Assert.True(await _redis.SortedSetAddAsync(key, "colin", 8)); 79 | var score0 = await _redis.SortedSetIncrementAsync(key, "colin", 1); 80 | var score1 = await _redis.SortedSetDecrementAsync(key, "colin", 1); 81 | Assert.Equal(1, score0 - score1); 82 | 83 | Assert.True(await _redis.SortedSetAddAsync(key, "robin", 6)); 84 | Assert.True(await _redis.SortedSetAddAsync(key, "tom", 7)); 85 | Assert.True(await _redis.SortedSetAddAsync(key, "bob", 5)); 86 | Assert.True(await _redis.SortedSetAddAsync(key, "elle", 5)); 87 | Assert.True(await _redis.SortedSetAddAsync(key, "helen", 5)); 88 | 89 | //返回排名前五,无论分数多少 90 | var top5 = await _redis.SortedSetRangeByRankWithScoresAsync(key, 0, 4, Order.Descending); 91 | foreach (var (k, v) in top5) 92 | _testOutputHelper.WriteLine($"{k}\t{v}"); 93 | 94 | _testOutputHelper.WriteLine("---------------"); 95 | 96 | //返回6-10分之间前五 97 | var highScore = 98 | await _redis.SortedSetRangeByScoreWithScoresAsync(key, 6, 10, order: Order.Descending, take: 5); 99 | foreach (var (k, v) in highScore) 100 | _testOutputHelper.WriteLine($"{k}\t{v}"); 101 | 102 | await _redis.KeyDeleteAsync(new[] { key }); 103 | } 104 | 105 | [Fact] 106 | public async Task HashTestAsync() 107 | { 108 | const string key = "person"; 109 | await _redis.HashSetAsync(key, new ConcurrentDictionary 110 | { 111 | ["name"] = "colin", 112 | ["age"] = "18" 113 | }); 114 | 115 | Assert.True(await _redis.HashDeleteFieldsAsync(key, new[] { "gender", "name" })); 116 | 117 | await _redis.HashSetFieldsAsync(key, new ConcurrentDictionary 118 | { 119 | ["age"] = "20" 120 | }); 121 | 122 | var dict = await _redis.HashGetFieldsAsync(key, new[] { "age" }); 123 | Assert.Equal("20", dict["age"]); 124 | 125 | await _redis.HashDeleteAsync(key); 126 | } 127 | 128 | 129 | [Fact] 130 | public async Task BatchExecuteAsync() 131 | { 132 | await _redis.ExecuteBatchAsync( 133 | async () => await _redis.StringSetAsync("name", "colin"), 134 | async () => await _redis.SetAddAsync("guys", "robin") 135 | ); 136 | 137 | Assert.Equal("colin", await _redis.StringGetAsync("name")); 138 | Assert.Equal("robin", (await _redis.SetMembersAsync("guys")).FirstOrDefault()); 139 | 140 | await _redis.KeyDeleteAsync(new[] { "name", "guys" }); 141 | } 142 | 143 | [Fact] 144 | public async Task KeyExpiryTestAsync() 145 | { 146 | const string key = "expirytest"; 147 | const string value = "haha"; 148 | 149 | await _redis.StringSetAsync(key, value); 150 | await _redis.KeyExpireAsync(key, TimeSpan.FromSeconds(3)); 151 | 152 | Assert.Equal(value, await _redis.StringGetAsync(key)); 153 | await Task.Delay(3000); 154 | Assert.Null(await _redis.StringGetAsync(key)); 155 | } 156 | 157 | [Fact] 158 | public void GetAllKeys() 159 | { 160 | var keys = _redis.GetAllKeys(); 161 | foreach (var key in keys) 162 | _testOutputHelper.WriteLine(key); 163 | } 164 | 165 | [Fact] 166 | public async Task PubSubTestAsync() 167 | { 168 | const string channel = "message"; 169 | const string message = "hi there"; 170 | 171 | await _redis.SubscribeAsync(channel, (chn, msg) => 172 | { 173 | Assert.Equal(channel, chn); 174 | Assert.Equal(message, msg); 175 | }); 176 | 177 | await _redis.PublishAsync(channel, message); 178 | } 179 | 180 | [Fact] 181 | public async Task LockExecuteTestAsync() 182 | { 183 | const string key = "lockTest"; 184 | 185 | var func = new Func>(async (a, b) => 186 | { 187 | // _testOutputHelper.WriteLine( 188 | // $"thread-{Thread.CurrentThread.ManagedThreadId.ToString()} get the lock."); 189 | await Task.Delay(1000); 190 | return await Task.FromResult(a + b); 191 | }); 192 | 193 | var rdm = new Random(); 194 | for (var i = 0; i < 3; i++) 195 | { 196 | new Thread(async () => 197 | { 198 | var success = _redis.LockExecute(key, Guid.NewGuid().ToString(), func, 199 | out var result, 200 | TimeSpan.MaxValue, 201 | 3000, rdm.Next(0, 10), 0); 202 | 203 | if (success) 204 | { 205 | var res = await (result as Task); 206 | _testOutputHelper.WriteLine( 207 | $"result is {res}.\t{DateTime.Now.ToLongTimeString()}"); 208 | } 209 | else 210 | _testOutputHelper.WriteLine( 211 | $"failed to get lock.\t{DateTime.Now.ToLongTimeString()}"); 212 | }) 213 | { IsBackground = true } 214 | .Start(); 215 | await Task.Delay(500); 216 | } 217 | 218 | await Task.Delay(5000); 219 | _testOutputHelper.WriteLine("all done"); 220 | } 221 | } 222 | 223 | public class People 224 | { 225 | public string Name { get; set; } 226 | 227 | public int Age { get; set; } 228 | 229 | public People(string name, int age) 230 | { 231 | Name = name; 232 | Age = age; 233 | } 234 | } 235 | 236 | public class PeopleComparer : IEqualityComparer 237 | { 238 | public bool Equals(People x, People y) => x.Name == y.Name && x.Age == y.Age; 239 | 240 | public int GetHashCode(People obj) => obj.GetHashCode(); 241 | } 242 | 243 | public class RedisHelperFixture 244 | { 245 | public IRedisHelper Redis { get; } 246 | 247 | public RedisHelperFixture() => 248 | Redis = new RedisHelper(new RedisHelperOptions( 249 | "127.0.0.1:6379,password=123123,connectTimeout=1000,connectRetry=1,syncTimeout=10000", 0)); 250 | } 251 | } -------------------------------------------------------------------------------- /ColinChang.RedisHelper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31808.319 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColinChang.RedisHelper", "ColinChang.RedisHelper\ColinChang.RedisHelper.csproj", "{DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColinChang.RedisHelper.Test", "ColinChang.RedisHelper.Test\ColinChang.RedisHelper.Test.csproj", "{DE65275C-5DA0-4BAC-9A76-6B5568187DCE}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ColinChang.RedisHelper.Sample", "ColinChang.RedisHelper.Sample\ColinChang.RedisHelper.Sample.csproj", "{DC3F7A2A-F120-4395-B567-AA4B24DDD974}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColinChang.RedisHelper.Abp", "ColinChang.RedisHelper.Abp\ColinChang.RedisHelper.Abp.csproj", "{30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|x64 = Debug|x64 18 | Debug|x86 = Debug|x86 19 | Release|Any CPU = Release|Any CPU 20 | Release|x64 = Release|x64 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Debug|Any CPU.ActiveCfg = Release|Any CPU 25 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Debug|Any CPU.Build.0 = Release|Any CPU 26 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Debug|x64.Build.0 = Debug|Any CPU 28 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Debug|x86.Build.0 = Debug|Any CPU 30 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Release|x64.ActiveCfg = Release|Any CPU 33 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Release|x64.Build.0 = Release|Any CPU 34 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Release|x86.ActiveCfg = Release|Any CPU 35 | {DDBF328E-F301-4D26-9E0C-6BAAD96F8B52}.Release|x86.Build.0 = Release|Any CPU 36 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Debug|x64.Build.0 = Debug|Any CPU 40 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Debug|x86.Build.0 = Debug|Any CPU 42 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Release|x64.ActiveCfg = Release|Any CPU 45 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Release|x64.Build.0 = Release|Any CPU 46 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Release|x86.ActiveCfg = Release|Any CPU 47 | {DE65275C-5DA0-4BAC-9A76-6B5568187DCE}.Release|x86.Build.0 = Release|Any CPU 48 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Debug|x64.Build.0 = Debug|Any CPU 52 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Debug|x86.Build.0 = Debug|Any CPU 54 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Release|x64.ActiveCfg = Release|Any CPU 57 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Release|x64.Build.0 = Release|Any CPU 58 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Release|x86.ActiveCfg = Release|Any CPU 59 | {DC3F7A2A-F120-4395-B567-AA4B24DDD974}.Release|x86.Build.0 = Release|Any CPU 60 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Debug|x64.ActiveCfg = Debug|Any CPU 63 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Debug|x64.Build.0 = Debug|Any CPU 64 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Debug|x86.Build.0 = Debug|Any CPU 66 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Release|x64.ActiveCfg = Release|Any CPU 69 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Release|x64.Build.0 = Release|Any CPU 70 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Release|x86.ActiveCfg = Release|Any CPU 71 | {30CEC9DD-6BC5-4C83-B239-18EBFA9E9A84}.Release|x86.Build.0 = Release|Any CPU 72 | EndGlobalSection 73 | GlobalSection(SolutionProperties) = preSolution 74 | HideSolutionNode = FALSE 75 | EndGlobalSection 76 | GlobalSection(ExtensibilityGlobals) = postSolution 77 | SolutionGuid = {1052A95B-B89A-4A28-BF3A-928D8B726B60} 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper/ColinChang.RedisHelper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | Colin Chang 6 | https://ccstudio.com.cn, 7 | https://github.com/colin-chang/RedisHelper 8 | https://github.com/colin-chang/RedisHelper 9 | Git 10 | redis redis.net redis.driver redis.helper redis.net.core StackExchange redis.core 11 | redis common helper based on StackExchange.Redis.It can help you to use the usual types in redis,including String,List,Set,SortedSet,Hash.It also supports Pub/Sub,Patch Commands,distribution lock. 12 | 1.fix bugs 2.update dependencies 13 | 1.2.7 14 | icon.png 15 | https://raw.githubusercontent.com/colin-chang/RedisHelper/main/icon.png 16 | LICENSE 17 | true 18 | RedisHelper 19 | default 20 | 21 | 22 | 23 | bin\Release\ColinChang.RedisHelper.xml 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | True 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper/ColinChang.RedisHelper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ColinChang.RedisHelper 5 | 6 | 7 | 8 | 9 | 从队列中读取数据而不出队 10 | 11 | 12 | 起始位置 13 | 结束位置 14 | 对象类型 15 | 不指定 start、end 则获取所有数据 16 | 17 | 18 | 19 | 按序返回topN 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 删除给定Key 30 | 31 | 待删除的key集合 32 | 删除key的数量 33 | 34 | 35 | 36 | 设置指定key过期时间 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 批量执行Redis操作 45 | 46 | 47 | 48 | 49 | 50 | 获取分布式锁并执行(非阻塞。加锁失败直接返回(false,null)) 51 | 52 | 要锁定的key 53 | 锁定的value,加锁时赋值value,在解锁时必须是同一个value的客户端才能解锁 54 | 加锁成功时执行的业务方法 55 | 持锁超时时间。超时后锁自动释放 56 | 业务方法参数 57 | (success,return value of the del) 58 | 59 | 60 | 61 | 获取分布式锁并执行(阻塞。直到成功加锁或超时) 62 | 63 | 要锁定的key 64 | 锁定的value,加锁时赋值value,在解锁时必须是同一个value的客户端才能解锁 65 | 加锁成功时执行的业务方法 66 | del返回值 67 | 持锁超时时间。超时后锁自动释放 68 | 加锁超时时间(ms).0表示永不超时 69 | 业务方法参数 70 | success 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /ColinChang.RedisHelper/IRedisHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using StackExchange.Redis; 7 | 8 | namespace ColinChang.RedisHelper 9 | { 10 | public interface IRedisHelper 11 | { 12 | #region String 13 | 14 | Task StringSetAsync(string key, T value); 15 | 16 | Task StringGetAsync(string key); // where T : class; 17 | 18 | Task StringIncrementAsync(string key, int value = 1); 19 | 20 | Task StringDecrementAsync(string key, int value = 1); 21 | 22 | #endregion 23 | 24 | #region List 25 | 26 | Task EnqueueAsync(string key, T value); 27 | 28 | Task DequeueAsync(string key) where T : class; 29 | 30 | /// 31 | /// 从队列中读取数据而不出队 32 | /// 33 | /// 34 | /// 起始位置 35 | /// 结束位置 36 | /// 对象类型 37 | /// 不指定 start、end 则获取所有数据 38 | Task> PeekRangeAsync(string key, long start = 0, long stop = -1) 39 | where T : class; 40 | 41 | #endregion 42 | 43 | #region Set 44 | 45 | Task SetAddAsync(string key, T value); 46 | 47 | Task SetRemoveAsync(string key, IEnumerable values); 48 | 49 | Task> SetMembersAsync(string key) where T : class; 50 | 51 | Task SetContainsAsync(string key, T value); 52 | 53 | #endregion 54 | 55 | #region Sortedset 56 | 57 | Task SortedSetAddAsync(string key, string member, double score); 58 | 59 | Task SortedSetRemoveAsync(string key, IEnumerable members); 60 | 61 | Task SortedSetIncrementAsync(string key, string member, double value); 62 | 63 | Task SortedSetDecrementAsync(string key, string member, double value); 64 | 65 | /// 66 | /// 按序返回topN 67 | /// 68 | /// 69 | /// 70 | /// 71 | /// 72 | /// 73 | Task> SortedSetRangeByRankWithScoresAsync(string key, 74 | long start = 0, 75 | long stop = -1, 76 | Order order = Order.Ascending); 77 | 78 | Task> SortedSetRangeByScoreWithScoresAsync(string key, 79 | double start = double.NegativeInfinity, double stop = double.PositiveInfinity, 80 | Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1); 81 | 82 | #endregion 83 | 84 | #region Hash 85 | 86 | Task> HashGetAsync(string key); 87 | 88 | Task> HashGetFieldsAsync(string key, 89 | IEnumerable fields); 90 | 91 | Task HashSetAsync(string key, ConcurrentDictionary entries); 92 | 93 | Task HashSetFieldsAsync(string key, ConcurrentDictionary fields); 94 | 95 | Task HashDeleteAsync(string key); 96 | 97 | Task HashDeleteFieldsAsync(string key, IEnumerable fields); 98 | 99 | #endregion 100 | 101 | #region Key 102 | 103 | IEnumerable GetAllKeys(); 104 | 105 | IEnumerable GetAllKeys(EndPoint endPoint); 106 | 107 | Task KeyExistsAsync(string key); 108 | 109 | /// 110 | /// 删除给定Key 111 | /// 112 | /// 待删除的key集合 113 | /// 删除key的数量 114 | Task KeyDeleteAsync(IEnumerable keys); 115 | 116 | /// 117 | /// 设置指定key过期时间 118 | /// 119 | /// 120 | /// 121 | /// 122 | Task KeyExpireAsync(string key, TimeSpan? expiry); 123 | 124 | Task KeyExpireAsync(string key, DateTime? expiry); 125 | 126 | #endregion 127 | 128 | #region Advanced 129 | 130 | Task PublishAsync(string channel, string msg); 131 | 132 | Task SubscribeAsync(string channel, Action handler); 133 | 134 | /// 135 | /// 批量执行Redis操作 136 | /// 137 | /// 138 | Task ExecuteBatchAsync(params Action[] operations); 139 | 140 | /// 141 | /// 获取分布式锁并执行(非阻塞。加锁失败直接返回(false,null)) 142 | /// 143 | /// 要锁定的key 144 | /// 锁定的value,加锁时赋值value,在解锁时必须是同一个value的客户端才能解锁 145 | /// 加锁成功时执行的业务方法 146 | /// 持锁超时时间。超时后锁自动释放 147 | /// 业务方法参数 148 | /// (success,return value of the del) 149 | Task<(bool, object)> LockExecuteAsync(string key, string value, Delegate del, 150 | TimeSpan expiry, params object[] args); 151 | 152 | /// 153 | /// 获取分布式锁并执行(阻塞。直到成功加锁或超时) 154 | /// 155 | /// 要锁定的key 156 | /// 锁定的value,加锁时赋值value,在解锁时必须是同一个value的客户端才能解锁 157 | /// 加锁成功时执行的业务方法 158 | /// del返回值 159 | /// 持锁超时时间。超时后锁自动释放 160 | /// 加锁超时时间(ms).0表示永不超时 161 | /// 业务方法参数 162 | /// success 163 | bool LockExecute(string key, string value, Delegate del, out object result, TimeSpan expiry, 164 | int timeout = 0, params object[] args); 165 | 166 | bool LockExecute(string key, string value, Action action, TimeSpan expiry, int timeout = 0); 167 | 168 | bool LockExecute(string key, string value, Action action, T arg, TimeSpan expiry, int timeout = 0); 169 | 170 | bool LockExecute(string key, string value, Func func, out T result, TimeSpan expiry, 171 | int timeout = 0); 172 | 173 | bool LockExecute(string key, string value, Func func, T arg, out TResult result, 174 | TimeSpan expiry, int timeout = 0); 175 | 176 | #endregion 177 | } 178 | } -------------------------------------------------------------------------------- /ColinChang.RedisHelper/RedisHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using StackExchange.Redis; 7 | using Newtonsoft.Json; 8 | using System.Net; 9 | using System.Threading; 10 | using Microsoft.Extensions.Options; 11 | using Timer = System.Timers.Timer; 12 | 13 | namespace ColinChang.RedisHelper 14 | { 15 | public class RedisHelper : IRedisHelper 16 | { 17 | private readonly ConnectionMultiplexer _conn; 18 | private readonly IDatabase _db; 19 | 20 | public RedisHelper(IOptionsMonitor options) : this(options.CurrentValue) 21 | { 22 | } 23 | 24 | public RedisHelper(RedisHelperOptions options) 25 | { 26 | var connectionString = options.ConnectionString; 27 | _conn = ConnectionMultiplexer.Connect(connectionString); 28 | 29 | var dbNumber = options.DbNumber; 30 | _db = _conn.GetDatabase(dbNumber); 31 | } 32 | 33 | 34 | #region String 35 | 36 | public async Task StringSetAsync(string key, T value) => 37 | await _db.StringSetAsync(key, value.ToRedisValue()); 38 | 39 | public async Task StringGetAsync(string key) 40 | //where T : class 41 | => 42 | (await _db.StringGetAsync(key)).ToObject(); 43 | 44 | public async Task StringIncrementAsync(string key, int value = 1) => 45 | await _db.StringIncrementAsync(key, value); 46 | 47 | public async Task StringDecrementAsync(string key, int value = 1) => 48 | await _db.StringDecrementAsync(key, value); 49 | 50 | #endregion 51 | 52 | #region List 53 | 54 | public async Task EnqueueAsync(string key, T value) => 55 | await _db.ListRightPushAsync(key, value.ToRedisValue()); 56 | 57 | public async Task DequeueAsync(string key) where T : class => 58 | (await _db.ListLeftPopAsync(key)).ToObject(); 59 | 60 | public async Task> PeekRangeAsync(string key, long start = 0, long stop = -1) 61 | where T : class => 62 | (await _db.ListRangeAsync(key, start, stop)).ToObjects(); 63 | 64 | #endregion 65 | 66 | #region Set 67 | 68 | public async Task SetAddAsync(string key, T value) => 69 | await _db.SetAddAsync(key, value.ToRedisValue()); 70 | 71 | public async Task SetRemoveAsync(string key, IEnumerable values) => 72 | await _db.SetRemoveAsync(key, values.ToRedisValues()); 73 | 74 | public async Task> SetMembersAsync(string key) where T : class => 75 | (await _db.SetMembersAsync(key)).ToObjects(); 76 | 77 | public async Task SetContainsAsync(string key, T value) => 78 | await _db.SetContainsAsync(key, value.ToRedisValue()); 79 | 80 | #endregion 81 | 82 | #region Sortedset 83 | 84 | public async Task SortedSetAddAsync(string key, string member, double score) => 85 | await _db.SortedSetAddAsync(key, member, score); 86 | 87 | public async Task SortedSetRemoveAsync(string key, IEnumerable members) => 88 | await _db.SortedSetRemoveAsync(key, members.ToRedisValues()); 89 | 90 | public async Task SortedSetIncrementAsync(string key, string member, double value) => 91 | await _db.SortedSetIncrementAsync(key, member, value); 92 | 93 | public async Task SortedSetDecrementAsync(string key, string member, double value) => 94 | await _db.SortedSetDecrementAsync(key, member, value); 95 | 96 | public async Task> SortedSetRangeByRankWithScoresAsync(string key, 97 | long start = 0, 98 | long stop = -1, 99 | Order order = Order.Ascending) => 100 | (await _db.SortedSetRangeByRankWithScoresAsync(key, start, stop, order)).ToConcurrentDictionary(); 101 | 102 | public async Task> SortedSetRangeByScoreWithScoresAsync(string key, 103 | double start = double.NegativeInfinity, double stop = double.PositiveInfinity, 104 | Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long take = -1) => 105 | (await _db.SortedSetRangeByScoreWithScoresAsync(key, start, stop, exclude, order, skip, take)) 106 | .ToConcurrentDictionary(); 107 | 108 | #endregion 109 | 110 | #region Hash 111 | 112 | public async Task> HashGetAsync(string key) => 113 | (await _db.HashGetAllAsync(key)).ToConcurrentDictionary(); 114 | 115 | public async Task> HashGetFieldsAsync(string key, 116 | IEnumerable fields) => 117 | (await _db.HashGetAsync(key, fields.ToRedisValues())).ToConcurrentDictionary(fields); 118 | 119 | public async Task HashSetAsync(string key, ConcurrentDictionary entries) 120 | { 121 | var val = entries.ToHashEntries(); 122 | if (val != null) 123 | await _db.HashSetAsync(key, val); 124 | } 125 | 126 | public async Task HashSetFieldsAsync(string key, ConcurrentDictionary fields) 127 | { 128 | if (fields == null || !fields.Any()) 129 | return; 130 | 131 | var hs = await HashGetAsync(key); 132 | foreach (var field in fields) 133 | { 134 | //if(!hs.ContainsKey(field.Key)) 135 | 136 | // continue; 137 | 138 | hs[field.Key] = field.Value; 139 | } 140 | 141 | await HashSetAsync(key, hs); 142 | } 143 | 144 | public async Task HashDeleteAsync(string key) => 145 | await KeyDeleteAsync(new string[] { key }) > 0; 146 | 147 | public async Task HashDeleteFieldsAsync(string key, IEnumerable fields) 148 | { 149 | if (fields == null || !fields.Any()) 150 | return false; 151 | 152 | var success = true; 153 | foreach (var field in fields) 154 | { 155 | if (!await _db.HashDeleteAsync(key, field)) 156 | success = false; 157 | } 158 | 159 | return success; 160 | } 161 | 162 | #endregion 163 | 164 | #region Key 165 | 166 | public IEnumerable GetAllKeys() => 167 | _conn.GetEndPoints().Select(endPoint => _conn.GetServer(endPoint)) 168 | .SelectMany(server => server.Keys().ToStrings()); 169 | 170 | public IEnumerable GetAllKeys(EndPoint endPoint) => 171 | _conn.GetServer(endPoint).Keys().ToStrings(); 172 | 173 | public async Task KeyExistsAsync(string key) => 174 | await _db.KeyExistsAsync(key); 175 | 176 | public async Task KeyDeleteAsync(IEnumerable keys) => 177 | await _db.KeyDeleteAsync(keys.Select(k => (RedisKey)k).ToArray()); 178 | 179 | 180 | public async Task KeyExpireAsync(string key, TimeSpan? expiry) => await _db.KeyExpireAsync(key, expiry); 181 | 182 | public async Task KeyExpireAsync(string key, DateTime? expiry) => await _db.KeyExpireAsync(key, expiry); 183 | 184 | #endregion 185 | 186 | #region Advanced 187 | 188 | public async Task PublishAsync(string channel, string msg) => 189 | await _conn.GetSubscriber().PublishAsync(channel, msg); 190 | 191 | public async Task SubscribeAsync(string channel, Action handler) => 192 | await _conn.GetSubscriber().SubscribeAsync(channel, (chn, msg) => handler(chn, msg)); 193 | 194 | public Task ExecuteBatchAsync(params Action[] operations) => 195 | Task.Run(() => 196 | { 197 | var batch = _db.CreateBatch(); 198 | 199 | foreach (var operation in operations) 200 | operation(); 201 | 202 | batch.Execute(); 203 | }); 204 | 205 | 206 | public async Task<(bool, object)> LockExecuteAsync(string key, string value, Delegate del, 207 | TimeSpan expiry, params object[] args) 208 | { 209 | if (!await _db.LockTakeAsync(key, value, expiry)) 210 | return (false, null); 211 | 212 | try 213 | { 214 | return (true, del.DynamicInvoke(args)); 215 | } 216 | finally 217 | { 218 | _db.LockRelease(key, value); 219 | } 220 | } 221 | 222 | 223 | public bool LockExecute(string key, string value, Delegate del, out object result, TimeSpan expiry, 224 | int timeout = 0, params object[] args) 225 | { 226 | result = null; 227 | if (!GetLock(key, value, expiry, timeout)) 228 | return false; 229 | 230 | try 231 | { 232 | result = del.DynamicInvoke(args); 233 | return true; 234 | } 235 | finally 236 | { 237 | _db.LockRelease(key, value); 238 | } 239 | } 240 | 241 | public bool LockExecute(string key, string value, Action action, TimeSpan expiry, int timeout = 0) 242 | { 243 | return LockExecute(key, value, action, out var _, expiry, timeout); 244 | } 245 | 246 | public bool LockExecute(string key, string value, Action action, T arg, TimeSpan expiry, int timeout = 0) 247 | { 248 | return LockExecute(key, value, action, out var _, expiry, timeout, arg); 249 | } 250 | 251 | public bool LockExecute(string key, string value, Func func, out T result, TimeSpan expiry, 252 | int timeout = 0) 253 | { 254 | result = default; 255 | if (!GetLock(key, value, expiry, timeout)) 256 | return false; 257 | try 258 | { 259 | result = func(); 260 | return true; 261 | } 262 | finally 263 | { 264 | _db.LockRelease(key, value); 265 | } 266 | } 267 | 268 | public bool LockExecute(string key, string value, Func func, T arg, out TResult result, 269 | TimeSpan expiry, int timeout = 0) 270 | { 271 | result = default; 272 | if (!GetLock(key, value, expiry, timeout)) 273 | return false; 274 | try 275 | { 276 | result = func(arg); 277 | return true; 278 | } 279 | finally 280 | { 281 | _db.LockRelease(key, value); 282 | } 283 | } 284 | 285 | private bool GetLock(string key, string value, TimeSpan expiry, int timeout) 286 | { 287 | using var waitHandle = new AutoResetEvent(false); 288 | var timer = new Timer(1000); 289 | timer.Elapsed += (s, e) => 290 | { 291 | if (!_db.LockTake(key, value, expiry)) 292 | return; 293 | try 294 | { 295 | waitHandle.Set(); 296 | timer.Stop(); 297 | } 298 | catch 299 | { 300 | } 301 | }; 302 | timer.Start(); 303 | 304 | 305 | if (timeout > 0) 306 | waitHandle.WaitOne(timeout); 307 | else 308 | waitHandle.WaitOne(); 309 | 310 | timer.Stop(); 311 | timer.Close(); 312 | timer.Dispose(); 313 | 314 | return _db.LockQuery(key) == value; 315 | } 316 | 317 | #endregion 318 | } 319 | 320 | public static class StackExchangeRedisExtension 321 | { 322 | public static IEnumerable ToStrings(this IEnumerable keys) 323 | { 324 | var redisKeys = keys as RedisKey[] ?? keys.ToArray(); 325 | return !redisKeys.Any() ? null : redisKeys.Select(k => (string)k); 326 | } 327 | 328 | public static RedisValue ToRedisValue(this T value) 329 | { 330 | if (value == null) 331 | return RedisValue.Null; 332 | 333 | return value switch 334 | { 335 | ValueType => value.ToString(), 336 | string s => s, 337 | _ => JsonConvert.SerializeObject(value) 338 | }; 339 | } 340 | 341 | 342 | public static RedisValue[] ToRedisValues(this IEnumerable values) 343 | { 344 | var enumerable = values as T[] ?? values.ToArray(); 345 | return !enumerable.Any() ? null : enumerable.Select(v => v.ToRedisValue()).ToArray(); 346 | } 347 | 348 | public static T ToObject(this RedisValue value) 349 | //where T : class 350 | { 351 | if (!value.HasValue) 352 | return default; 353 | 354 | if (typeof(T).IsSubclassOf(typeof(ValueType)) || typeof(T) == typeof(string)) 355 | return (T)Convert.ChangeType(value.ToString(), typeof(T)); 356 | 357 | return JsonConvert.DeserializeObject(value.ToString()); 358 | } 359 | 360 | public static IEnumerable ToObjects(this IEnumerable values) where T : class 361 | { 362 | var redisValues = values as RedisValue[] ?? values.ToArray(); 363 | return !redisValues.Any() ? null : redisValues.Select(v => v.ToObject()); 364 | } 365 | 366 | public static HashEntry[] ToHashEntries(this ConcurrentDictionary entries) 367 | { 368 | if (entries == null || !entries.Any()) 369 | return null; 370 | 371 | var es = new HashEntry[entries.Count]; 372 | for (var i = 0; i < entries.Count; i++) 373 | { 374 | var name = entries.Keys.ElementAt(i); 375 | var value = entries[name]; 376 | es[i] = new HashEntry(name, value); 377 | } 378 | 379 | return es; 380 | } 381 | 382 | public static ConcurrentDictionary ToConcurrentDictionary(this IEnumerable entries) 383 | { 384 | var hashEntries = entries as HashEntry[] ?? entries.ToArray(); 385 | if (!hashEntries.Any()) 386 | return null; 387 | 388 | 389 | var dict = new ConcurrentDictionary(); 390 | foreach (var entry in hashEntries) 391 | dict[entry.Name] = entry.Value; 392 | 393 | return dict; 394 | } 395 | 396 | public static ConcurrentDictionary ToConcurrentDictionary(this RedisValue[] hashValues, 397 | IEnumerable fields) 398 | { 399 | var enumerable = fields as string[] ?? fields.ToArray(); 400 | if (hashValues == null || !hashValues.Any() || !enumerable.Any()) 401 | return null; 402 | 403 | var dict = new ConcurrentDictionary(); 404 | for (var i = 0; i < enumerable.Count(); i++) 405 | dict[enumerable.ElementAt(i)] = hashValues[i]; 406 | 407 | return dict; 408 | } 409 | 410 | public static ConcurrentDictionary ToConcurrentDictionary( 411 | this IEnumerable entries) 412 | { 413 | var sortedSetEntries = entries as SortedSetEntry[] ?? entries.ToArray(); 414 | if (!sortedSetEntries.Any()) 415 | return null; 416 | var dict = new ConcurrentDictionary(); 417 | foreach (var entry in sortedSetEntries) 418 | dict[entry.Element] = entry.Score; 419 | 420 | return dict; 421 | } 422 | } 423 | } -------------------------------------------------------------------------------- /ColinChang.RedisHelper/RedisHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace ColinChang.RedisHelper 6 | { 7 | public static class RedisHelperExtensions 8 | { 9 | public static IServiceCollection AddRedisHelper(this IServiceCollection services, IConfiguration configuration) 10 | { 11 | if (services == null) 12 | throw new ArgumentNullException(nameof(services)); 13 | 14 | if (configuration == null) 15 | throw new ArgumentNullException(nameof(configuration)); 16 | 17 | services.AddOptions() 18 | .Configure(configuration.Bind) 19 | .ValidateDataAnnotations(); 20 | services.AddSingleton(); 21 | return services; 22 | } 23 | 24 | public static IServiceCollection AddRedisHelper(this IServiceCollection services, 25 | Action configureOptions) 26 | { 27 | if (services == null) 28 | throw new ArgumentNullException(nameof(services)); 29 | 30 | if (configureOptions == null) 31 | throw new ArgumentNullException(nameof(configureOptions)); 32 | 33 | services.Configure(configureOptions); 34 | services.AddSingleton(); 35 | return services; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /ColinChang.RedisHelper/RedisHelperOptions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace ColinChang.RedisHelper 4 | { 5 | public class RedisHelperOptions 6 | { 7 | [Required(ErrorMessage = "redis connection string is required")] 8 | public string ConnectionString { get; set; } 9 | 10 | [Range(0, 15, ErrorMessage = "redis db number must be between 0 and 15")] 11 | public int DbNumber { get; set; } 12 | 13 | public RedisHelperOptions() 14 | { 15 | } 16 | 17 | public RedisHelperOptions(string connectionString, int dbNumber) 18 | { 19 | ConnectionString = connectionString; 20 | DbNumber = dbNumber; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Colin Chang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedisHelper 2 | This is a redis operation utility based on StackExchange.Redis.It's built for.Net Standard 2.1 3 | 4 | **[Nuget](https://www.nuget.org/packages/ColinChang.RedisHelper/)** 5 | ```sh 6 | # Package Manager 7 | Install-Package ColinChang.RedisHelper 8 | 9 | # .NET CLI 10 | dotnet add package ColinChang.RedisHelper 11 | ``` 12 | **Supported** 13 | 14 | * Data types:`String,List,Set,SortedSet,Hash` 15 | * Pub/Sub 16 | * Path commands 17 | * Distrubution lock(sync/async) 18 | 19 | **Tips** 20 | we highly recommend that use this as a singleton instance to reuse the redis connection. 21 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colin-chang/RedisHelper/3f9b5467b14292fea16482a18ceaeb5857e0e605/icon.png --------------------------------------------------------------------------------