├── .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
--------------------------------------------------------------------------------