├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CacheDatabaseQueriesApiSample ├── CacheDatabaseQueriesApiSample.csproj ├── Controllers │ ├── DbQueriesController.cs │ └── DbTimeController.cs ├── DbTimeContext.cs ├── DbTimeEntity.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ └── index.html ├── Console.Net461 ├── App.config ├── Console.Net461.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── LICENSE ├── LazyCache.AspNetCore ├── LazyCache.AspNetCore.csproj └── LazyCacheServiceCollectionExtensions.cs ├── LazyCache.Benchmarks ├── BenchmarkConfig.cs ├── ComplexObject.cs ├── LazyCache.Benchmarks.csproj ├── MemoryCacheBenchmarks.cs ├── MemoryCacheBenchmarksRealLifeScenarios.cs ├── Program.cs ├── Properties │ └── launchSettings.json └── README.md ├── LazyCache.Ninject.UnitTests ├── LazyCache.Ninject.UnitTests.csproj └── LazyCacheModuleTest.cs ├── LazyCache.Ninject ├── LazyCache.Ninject.csproj └── LazyCacheModule.cs ├── LazyCache.UnitTests ├── AspNetCoreTests.cs ├── AsyncHelper.cs ├── CachingServiceMemoryCacheProviderTests.cs ├── LazyCache.UnitTests.csproj └── MockCachingServiceTests.cs ├── LazyCache.UnitTestsCore21 ├── CachingServiceTests.cs └── LazyCache.UnitTestsCore21.csproj ├── LazyCache.UnitTestsCore22 ├── CachingServiceTests.cs └── LazyCache.UnitTestsCore22.csproj ├── LazyCache.UnitTestsCore30 ├── CachingServiceTests.cs └── LazyCache.UnitTestsCore30.csproj ├── LazyCache.UnitTestsCore31 ├── CachingServiceTests.cs └── LazyCache.UnitTestsCore31.csproj ├── LazyCache.UnitTestsNet50 ├── CachingServiceTests.cs └── LazyCache.UnitTestsNet50.csproj ├── LazyCache.sln ├── LazyCache ├── AppCacheExtensions.cs ├── AsyncLazy.cs ├── CacheDefaults.cs ├── CacheItemPolicy.cs ├── CachingService.cs ├── ExpirationMode.cs ├── IAppCache.cs ├── ICacheProvider.cs ├── LazyCache.csproj ├── MemoryCacheEntryOptionsExtensions.cs ├── Mocks │ ├── MockCacheEntry.cs │ ├── MockCacheProvider.cs │ └── MockCachingService.cs └── Providers │ └── MemoryCacheProvider.cs ├── README.md ├── ReleaseNotes.md ├── appveyor.yml ├── artwork ├── favicon.ico ├── logo-128.png ├── logo-16.png ├── logo-256.png ├── logo-32.png ├── logo-64.png ├── logo-small.svg └── logo.svg └── build.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us solve your issue 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Here is some example code that allows anyone to easily reproduce the issue I am having. 15 | See [TroubleShooting LazyCache](https://github.com/alastairtree/LazyCache/wiki/Troubleshooting-LazyCache) for an example of a minimal reproducable example. 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | ** Framework and Platform 21 | - OS: [e.g. Windows Server 2019] 22 | - Framework [e.g. netcoreapp2.1] 23 | - LazyCache Version [e.g. 2.0.3] 24 | 25 | **Additional context** 26 | Add any other context about the problem here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | [Use the Thumbs Up reaction to vote for this feature, and please avoid adding comments like "+1" as they create noise for others watching the issue.] 13 | 14 | 15 | 16 | **Is your feature request related to a problem? Please describe.** 17 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 18 | 19 | **Describe the solution you'd like** 20 | A clear and concise description of what you want LazyCache to do. Add some example code of any new APIs that should be added to LazyCache. 21 | 22 | **Describe alternatives you've considered** 23 | A clear and concise description of any alternative solutions or features you've considered. 24 | 25 | **Additional context** 26 | Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # MSTest test Results 20 | [Tt]est[Rr]esult*/ 21 | [Bb]uild[Ll]og.* 22 | 23 | #NUNIT 24 | *.VisualState.xml 25 | TestResult.xml 26 | 27 | # Build Results of an ATL Project 28 | [Dd]ebugPS/ 29 | [Rr]eleasePS/ 30 | dlldata.c 31 | 32 | *_i.c 33 | *_p.c 34 | *_i.h 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.svclog 55 | *.scc 56 | 57 | # Chutzpah Test files 58 | _Chutzpah* 59 | 60 | # Visual C++ cache files 61 | ipch/ 62 | *.aps 63 | *.ncb 64 | *.opensdf 65 | *.sdf 66 | *.cachefile 67 | 68 | # Visual Studio profiler 69 | *.psess 70 | *.vsp 71 | *.vspx 72 | 73 | # TFS 2012 Local Workspace 74 | $tf/ 75 | 76 | # Guidance Automation Toolkit 77 | *.gpState 78 | 79 | # ReSharper is a .NET coding add-in 80 | _ReSharper*/ 81 | *.[Rr]e[Ss]harper 82 | *.DotSettings.user 83 | 84 | # JustCode is a .NET coding addin-in 85 | .JustCode 86 | 87 | # TeamCity is a build add-in 88 | _TeamCity* 89 | 90 | # DotCover is a Code Coverage Tool 91 | *.dotCover 92 | 93 | # NCrunch 94 | *.ncrunch* 95 | _NCrunch_* 96 | .*crunch*.local.xml 97 | 98 | # MightyMoose 99 | *.mm.* 100 | AutoTest.Net/ 101 | 102 | # Web workbench (sass) 103 | .sass-cache/ 104 | 105 | # Installshield output folder 106 | [Ee]xpress/ 107 | 108 | # DocProject is a documentation generator add-in 109 | DocProject/buildhelp/ 110 | DocProject/Help/*.HxT 111 | DocProject/Help/*.HxC 112 | DocProject/Help/*.hhc 113 | DocProject/Help/*.hhk 114 | DocProject/Help/*.hhp 115 | DocProject/Help/Html2 116 | DocProject/Help/html 117 | 118 | # Click-Once directory 119 | publish/ 120 | 121 | # Publish Web Output 122 | *.[Pp]ublish.xml 123 | *.azurePubxml 124 | 125 | # NuGet Packages Directory 126 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 127 | #packages/* 128 | ## TODO: If the tool you use requires repositories.config, also uncomment the next line 129 | #!packages/repositories.config 130 | 131 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 132 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented) 133 | !packages/build/ 134 | 135 | # Windows Azure Build Output 136 | csx/ 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.dbproj.schemaview 151 | *.pfx 152 | *.publishsettings 153 | node_modules/ 154 | 155 | # RIA/Silverlight projects 156 | Generated_Code/ 157 | 158 | # Backup & report files from converting an old project file to a newer 159 | # Visual Studio version. Backup files are not needed, because we have git ;-) 160 | _UpgradeReport_Files/ 161 | Backup*/ 162 | UpgradeLog*.XML 163 | UpgradeLog*.htm 164 | 165 | # SQL Server files 166 | *.mdf 167 | *.ldf 168 | 169 | # Business Intelligence projects 170 | *.rdl.data 171 | *.bim.layout 172 | *.bim_*.settings 173 | 174 | # Microsoft Fakes 175 | FakesAssemblies/ 176 | 177 | # ========================= 178 | # Windows detritus 179 | # ========================= 180 | 181 | # Windows image file caches 182 | Thumbs.db 183 | ehthumbs.db 184 | 185 | # Folder config file 186 | Desktop.ini 187 | 188 | # Recycle Bin used on file shares 189 | $RECYCLE.BIN/ 190 | 191 | packages/ 192 | .vs/ 193 | 194 | # Benchmark Dot Net 195 | **/BenchmarkDotNet.Artifacts/* 196 | **/project.lock.json 197 | tests/output/* 198 | .vs/restore.dg 199 | artifacts/* 200 | BDN.Generated 201 | BenchmarkDotNet.Samples/Properties/launchSettings.json 202 | src/BenchmarkDotNet/Disassemblers/net461/* -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/CacheDatabaseQueriesApiSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/Controllers/DbQueriesController.cs: -------------------------------------------------------------------------------- 1 | using CacheDatabaseQueriesApiSample; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace ApiAsyncCachingSample.Controllers 5 | { 6 | public class DbQueriesController : Controller 7 | { 8 | [HttpGet] 9 | [Route("api/dbQueries")] 10 | public int GetDatabaseRequestCounter() 11 | { 12 | return DbTimeContext.DatabaseRequestCounter(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/Controllers/DbTimeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LazyCache; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace CacheDatabaseQueriesApiSample.Controllers 6 | { 7 | public class DbTimeController : Controller 8 | { 9 | private readonly IAppCache cache; 10 | private readonly string cacheKey = "DbTimeController.Get"; 11 | private readonly DbTimeContext dbContext; 12 | 13 | 14 | public DbTimeController(DbTimeContext context, IAppCache cache) 15 | { 16 | dbContext = context; 17 | this.cache = cache; 18 | } 19 | 20 | [HttpGet] 21 | [Route("api/dbtime")] 22 | public DbTimeEntity Get() 23 | { 24 | Func actionThatWeWantToCache = () => dbContext.GeDbTime(); 25 | 26 | var cachedDatabaseTime = cache.GetOrAdd(cacheKey, actionThatWeWantToCache); 27 | 28 | return cachedDatabaseTime; 29 | } 30 | 31 | [HttpDelete] 32 | [Route("api/dbtime")] 33 | public IActionResult DeleteFromCache() 34 | { 35 | cache.Remove(cacheKey); 36 | var friendlyMessage = new {Message = $"Item with key '{cacheKey}' removed from server in-memory cache"}; 37 | return Ok(friendlyMessage); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/DbTimeContext.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace CacheDatabaseQueriesApiSample 5 | { 6 | public class DbTimeContext : DbContext 7 | { 8 | private static int databaseRequestCounter; //just for demo - don't use static fields for statistics! 9 | 10 | public DbTimeContext(DbContextOptions options) 11 | : base(options) 12 | { 13 | } 14 | 15 | // simulate a table in the database so we can get just one row with the current time 16 | private DbSet Times { get; set; } 17 | 18 | public static int DatabaseRequestCounter() 19 | { 20 | return databaseRequestCounter; 21 | } 22 | 23 | public DbTimeEntity GeDbTime() 24 | { 25 | // get the current time from SQL server right now asynchronously (simulating a slow query) 26 | var result = Times 27 | .FromSql("WAITFOR DELAY '00:00:00:500'; SELECT 1 as [ID], GETDATE() as [TimeNowInTheDatabase]") 28 | .Single(); 29 | 30 | databaseRequestCounter++; 31 | 32 | return result; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/DbTimeEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CacheDatabaseQueriesApiSample 4 | { 5 | /// 6 | /// Simulates loading a record from a table, but really just gets the current datatime from the database 7 | /// 8 | public class DbTimeEntity 9 | { 10 | public DbTimeEntity(DateTime now) 11 | { 12 | TimeNowInTheDatabase = now; 13 | } 14 | 15 | public DbTimeEntity() 16 | { 17 | } 18 | 19 | public virtual int id { get; set; } 20 | 21 | public virtual DateTime TimeNowInTheDatabase { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace CacheDatabaseQueriesApiSample 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | BuildWebHost(args).Run(); 11 | } 12 | 13 | public static IWebHost BuildWebHost(string[] args) 14 | { 15 | return WebHost.CreateDefaultBuilder(args) 16 | .UseStartup() 17 | .Build(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:52671/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "/", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "CacheDatabaseQueriesApiSample": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "/", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:52672/" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace CacheDatabaseQueriesApiSample 8 | { 9 | public class Startup 10 | { 11 | public Startup(IConfiguration configuration) 12 | { 13 | Configuration = configuration; 14 | } 15 | 16 | public IConfiguration Configuration { get; } 17 | 18 | // This method gets called by the runtime. Use this method to add services to the container. 19 | public void ConfigureServices(IServiceCollection services) 20 | { 21 | services.AddMvc(); 22 | 23 | // just for demo - use app settings for db config 24 | var connection = 25 | @"Server=(localdb)\projectsv13;Database=Master;Trusted_Connection=True;ConnectRetryCount=0"; 26 | 27 | // register the database 28 | services.AddDbContext(options => options.UseSqlServer(connection)); 29 | 30 | // Register IAppCache as a singleton CachingService 31 | services.AddLazyCache(); 32 | } 33 | 34 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 35 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 36 | { 37 | if (env.IsDevelopment()) 38 | app.UseDeveloperExceptionPage(); 39 | 40 | app.UseDefaultFiles(); 41 | app.UseStaticFiles(); 42 | app.UseMvc(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CacheDatabaseQueriesApiSample/wwwroot/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Lazy cache sample app 8 | 9 | 10 | 14 | 15 | 16 |
17 |
18 |
0 Database query(s)
19 | 20 |

Sample app to demonstrate using an cache in your API to save database SQL queries and speed up API calls

21 | 22 |

23 | Every 3 seconds we fetch the current time from the database, however because the sql query 24 | result is cached on the server on the first call, the time stays the same untill you clear 25 | the cache and no more SQL queries are made.
26 |
27 | 28 | After you clear the cache you should see the time change because a real SQL query is made.
29 |
30 | 31 | Also note how real SQL queryies are slower than cache hits. 32 |

33 | 34 |
35 | 36 |
37 | 38 | 39 |

40 |
41 |
42 | 43 | 49 | 124 | 125 | 158 | 159 | -------------------------------------------------------------------------------- /Console.Net461/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Console.Net461/Console.Net461.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {32142F20-DFCE-4DF0-A263-093111E5A3FA} 8 | Exe 9 | Console.Net461 10 | Console.Net461 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Microsoft.Extensions.Caching.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll 38 | 39 | 40 | ..\packages\Microsoft.Extensions.Caching.Memory.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll 41 | 42 | 43 | ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll 44 | 45 | 46 | ..\packages\Microsoft.Extensions.Options.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll 47 | 48 | 49 | ..\packages\Microsoft.Extensions.Primitives.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll 50 | 51 | 52 | ..\packages\Ninject.3.3.4\lib\net45\Ninject.dll 53 | 54 | 55 | 56 | ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll 57 | 58 | 59 | ..\packages\System.ComponentModel.Annotations.4.5.0\lib\net461\System.ComponentModel.Annotations.dll 60 | 61 | 62 | 63 | 64 | ..\packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll 65 | 66 | 67 | 68 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll 69 | 70 | 71 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | {6FF349C3-D20C-493C-87A3-5A193538FE13} 91 | LazyCache.Ninject 92 | 93 | 94 | {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7} 95 | LazyCache 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Console.Net461/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LazyCache; 3 | using Ninject; 4 | 5 | namespace Console.Net461 6 | { 7 | class Program 8 | { 9 | static void Main() 10 | { 11 | //check one - basic LazyCache 12 | IAppCache cache = new CachingService(CachingService.DefaultCacheProvider); 13 | 14 | var item = cache.GetOrAdd("Program.Main.Person", () => Tuple.Create("Joe Blogs", DateTime.UtcNow)); 15 | 16 | System.Console.WriteLine(item.Item1); 17 | 18 | //check two - using Ninject 19 | IKernel kernel = new StandardKernel(new LazyCacheModule()); 20 | cache = kernel.Get(); 21 | 22 | item = cache.GetOrAdd("Program.Main.Person", () => Tuple.Create("Joe Blogs", DateTime.UtcNow)); 23 | 24 | System.Console.WriteLine(item.Item1); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Console.Net461/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("Console.Net461")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("Console.Net461")] 12 | [assembly: AssemblyCopyright("Copyright © 2019")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("32142f20-dfce-4df0-a263-093111e5a3fa")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Console.Net461/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 alastairtree 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LazyCache.AspNetCore/LazyCache.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | LazyCache 7 | true 8 | 1.0.0 9 | 10 | $(LazyCacheAspNetCoreVersion)$(LazyCacheAspNetCoreVersionSuffix) 11 | $(APPVEYOR_BUILD_NUMBER) 12 | 0 13 | $(LazyCacheAspNetCoreVersion).$(AppVeyorBuildNumber) 14 | https://github.com/alastairtree 15 | https://github.com/alastairtree 16 | ServiceCollection registrations for LazyCache to initialise the dependency injection collection and add enable IAppCache to be injected into your app 17 | Copyright 2014 - 2018 Alastair Crabtree 18 | https://github.com/alastairtree/LazyCache 19 | logo-128.png 20 | https://github.com/alastairtree/LazyCache 21 | LazyCache DependecyInjection ServiceCollection Singleton Transient 22 | MIT 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LazyCache; 3 | using LazyCache.Providers; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using Microsoft.Extensions.DependencyInjection.Extensions; 6 | 7 | // ReSharper disable once CheckNamespace - MS guidelines say put DI registration in this NS 8 | namespace Microsoft.Extensions.DependencyInjection 9 | { 10 | // See https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCacheServiceCollectionExtensions.cs 11 | public static class LazyCacheServiceCollectionExtensions 12 | { 13 | public static IServiceCollection AddLazyCache(this IServiceCollection services) 14 | { 15 | if (services == null) throw new ArgumentNullException(nameof(services)); 16 | 17 | services.AddOptions(); 18 | services.TryAdd(ServiceDescriptor.Singleton()); 19 | services.TryAdd(ServiceDescriptor.Singleton()); 20 | 21 | services.TryAdd(ServiceDescriptor.Singleton(serviceProvider => 22 | new CachingService( 23 | new Lazy(serviceProvider.GetRequiredService)))); 24 | 25 | return services; 26 | } 27 | 28 | public static IServiceCollection AddLazyCache(this IServiceCollection services, 29 | Func implementationFactory) 30 | { 31 | if (services == null) throw new ArgumentNullException(nameof(services)); 32 | if (implementationFactory == null) throw new ArgumentNullException(nameof(implementationFactory)); 33 | 34 | services.AddOptions(); 35 | services.TryAdd(ServiceDescriptor.Singleton()); 36 | services.TryAdd(ServiceDescriptor.Singleton()); 37 | 38 | services.TryAdd(ServiceDescriptor.Singleton(implementationFactory)); 39 | 40 | return services; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /LazyCache.Benchmarks/BenchmarkConfig.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Analysers; 2 | using BenchmarkDotNet.Columns; 3 | using BenchmarkDotNet.Configs; 4 | using BenchmarkDotNet.Diagnosers; 5 | using BenchmarkDotNet.Jobs; 6 | using BenchmarkDotNet.Loggers; 7 | using BenchmarkDotNet.Reports; 8 | using Perfolizer.Horology; 9 | 10 | namespace LazyCache.Benchmarks 11 | { 12 | public class BenchmarkConfig: ManualConfig 13 | { 14 | public BenchmarkConfig() 15 | => AddJob(Job.ShortRun) 16 | .AddDiagnoser(MemoryDiagnoser.Default) 17 | .AddLogger(new ConsoleLogger()) 18 | .AddColumn(TargetMethodColumn.Method) 19 | .AddAnalyser(EnvironmentAnalyser.Default) 20 | .WithSummaryStyle(SummaryStyle.Default.WithTimeUnit(TimeUnit.Nanosecond)); 21 | } 22 | } -------------------------------------------------------------------------------- /LazyCache.Benchmarks/ComplexObject.cs: -------------------------------------------------------------------------------- 1 | namespace LazyCache.Benchmarks 2 | { 3 | public class ComplexObject 4 | { 5 | public string String { get; set; } = string.Empty; 6 | public int Int { get; set; } = default; 7 | } 8 | } -------------------------------------------------------------------------------- /LazyCache.Benchmarks/LazyCache.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | true 7 | Release 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LazyCache.Benchmarks/MemoryCacheBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using BenchmarkDotNet.Attributes; 3 | using BenchmarkDotNet.Configs; 4 | using LazyCache.Providers; 5 | using Microsoft.Extensions.Caching.Memory; 6 | 7 | namespace LazyCache.Benchmarks 8 | { 9 | [Config(typeof(BenchmarkConfig))] 10 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 11 | public class MemoryCacheBenchmarks 12 | { 13 | public const string CacheKey = nameof(CacheKey); 14 | 15 | public IMemoryCache MemCache; 16 | public IMemoryCache PopulatedMemCache; 17 | public IAppCache AppCache; 18 | public IAppCache PopulatedAppCache; 19 | public ComplexObject ComplexObject; 20 | 21 | [GlobalSetup] 22 | public void Setup() 23 | { 24 | ComplexObject = new ComplexObject(); 25 | 26 | MemCache = new MemoryCache(new MemoryCacheOptions()); 27 | PopulatedMemCache = new MemoryCache(new MemoryCacheOptions()); 28 | 29 | AppCache = new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 30 | PopulatedAppCache = new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 31 | 32 | PopulatedAppCache.Add(CacheKey, ComplexObject); 33 | PopulatedMemCache.Set(CacheKey, ComplexObject); 34 | } 35 | 36 | [GlobalCleanup] 37 | public void Cleanup() => MemCache.Dispose(); 38 | 39 | /* 40 | * 41 | * Benchmark Cache Initialization 42 | * 43 | */ 44 | 45 | [Benchmark(Baseline = true), BenchmarkCategory("Init")] 46 | public MemoryCache DotNetMemoryCache_Init() => new MemoryCache(new MemoryCacheOptions()); 47 | 48 | [Benchmark, BenchmarkCategory("Init")] 49 | public CachingService LazyCache_Init() => new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 50 | 51 | /* 52 | * 53 | * Benchmark Add Methods 54 | * 55 | */ 56 | 57 | [Benchmark(Baseline = true), BenchmarkCategory(nameof(IAppCache.Add))] 58 | public void DotNetMemoryCache_Set() => MemCache.Set(CacheKey, ComplexObject); 59 | 60 | [Benchmark, BenchmarkCategory(nameof(IAppCache.Add))] 61 | public void LazyCache_Set() => AppCache.Add(CacheKey, ComplexObject); 62 | 63 | /* 64 | * 65 | * Benchmark Get Methods With a Cache Miss 66 | * 67 | */ 68 | 69 | [Benchmark(Baseline = true), BenchmarkCategory(nameof(IAppCache.Get) + "_Miss")] 70 | public ComplexObject DotNetMemoryCache_Get_Miss() => MemCache.Get(CacheKey); 71 | 72 | [Benchmark, BenchmarkCategory(nameof(IAppCache.Get) + "_Miss")] 73 | public ComplexObject LazyCache_Get_Miss() => AppCache.Get(CacheKey); 74 | 75 | /* 76 | * 77 | * Benchmark Get Methods With a Cache Hit 78 | * 79 | */ 80 | 81 | [Benchmark(Baseline = true), BenchmarkCategory(nameof(IAppCache.Get) + "_Hit")] 82 | public ComplexObject DotNetMemoryCache_Get_Hit() => PopulatedMemCache.Get(CacheKey); 83 | 84 | [Benchmark, BenchmarkCategory(nameof(IAppCache.Get) + "_Hit")] 85 | public ComplexObject LazyCache_Get_Hit() => PopulatedAppCache.Get(CacheKey); 86 | 87 | /* 88 | * 89 | * Benchmark GetOrAdd Methods With Cache Miss 90 | * 91 | */ 92 | 93 | [Benchmark(Baseline = true), BenchmarkCategory(nameof(IAppCache.GetOrAdd) + "_Miss")] 94 | public ComplexObject DotNetMemoryCache_GetOrAdd_Miss() => MemCache.GetOrCreate(CacheKey, entry => ComplexObject); 95 | 96 | [Benchmark, BenchmarkCategory(nameof(IAppCache.GetOrAdd) + "_Miss")] 97 | public ComplexObject LazyCache_GetOrAdd_Miss() => AppCache.GetOrAdd(CacheKey, entry => ComplexObject); 98 | 99 | /* 100 | * 101 | * Benchmark GetOrAdd Methods With Cache Hit 102 | * 103 | */ 104 | 105 | [Benchmark(Baseline = true), BenchmarkCategory(nameof(IAppCache.GetOrAdd) + "_Hit")] 106 | public ComplexObject DotNetMemoryCache_GetOrAdd_Hit() => PopulatedMemCache.GetOrCreate(CacheKey, entry => ComplexObject); 107 | 108 | [Benchmark, BenchmarkCategory(nameof(IAppCache.GetOrAdd) + "_Hit")] 109 | public ComplexObject LazyCache_GetOrAdd_Hit() => PopulatedAppCache.GetOrAdd(CacheKey, entry => ComplexObject); 110 | 111 | /* 112 | * 113 | * Benchmark GetOrAddAsync Methods With Cache Miss 114 | * 115 | */ 116 | 117 | 118 | [Benchmark(Baseline = true), BenchmarkCategory(nameof(IAppCache.GetOrAddAsync) + "_Miss")] 119 | public Task DotNetMemoryCache_GetOrAddAsync_Miss() => MemCache.GetOrCreateAsync(CacheKey, entry => Task.FromResult(ComplexObject)); 120 | 121 | [Benchmark, BenchmarkCategory(nameof(IAppCache.GetOrAddAsync) + "_Miss")] 122 | public Task LazyCache_GetOrAddAsync_Miss() => AppCache.GetOrAddAsync(CacheKey, entry => Task.FromResult(ComplexObject)); 123 | 124 | /* 125 | * 126 | * Benchmark GetOrAddAsync Methods With Cache Hit 127 | * 128 | */ 129 | 130 | [Benchmark(Baseline = true), BenchmarkCategory(nameof(IAppCache.GetOrAddAsync) + "_Hit")] 131 | public Task DotNetMemoryCache_GetOrAddAsync_Hit() => PopulatedMemCache.GetOrCreateAsync(CacheKey, entry => Task.FromResult(ComplexObject)); 132 | 133 | [Benchmark, BenchmarkCategory(nameof(IAppCache.GetOrAddAsync) + "_Hit")] 134 | public Task LazyCache_GetOrAddAsync_Hit() => PopulatedAppCache.GetOrAddAsync(CacheKey, entry => Task.FromResult(ComplexObject)); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /LazyCache.Benchmarks/MemoryCacheBenchmarksRealLifeScenarios.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Management; 3 | using System.Threading.Tasks; 4 | using BenchmarkDotNet.Attributes; 5 | using BenchmarkDotNet.Configs; 6 | using BenchmarkDotNet.Reports; 7 | using LazyCache.Providers; 8 | using Microsoft.Extensions.Caching.Memory; 9 | 10 | namespace LazyCache.Benchmarks 11 | { 12 | [Config(typeof(BenchmarkConfig))] 13 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 14 | public class MemoryCacheBenchmarksRealLifeScenarios 15 | { 16 | public const string CacheKey = nameof(CacheKey); 17 | 18 | public ComplexObject ComplexObject1; 19 | public ComplexObject ComplexObject2; 20 | public ComplexObject ComplexObject3; 21 | public ComplexObject ComplexObject4; 22 | public ComplexObject ComplexObject5; 23 | 24 | // Trying not to introduce artificial allocations below - just measuring what the library itself needs 25 | [GlobalSetup] 26 | public void Setup() 27 | { 28 | ComplexObject1 = new ComplexObject(); 29 | ComplexObject2 = new ComplexObject(); 30 | ComplexObject3 = new ComplexObject(); 31 | ComplexObject4 = new ComplexObject(); 32 | ComplexObject5 = new ComplexObject(); 33 | } 34 | 35 | [Benchmark] 36 | public ComplexObject Init_CRUD() 37 | { 38 | var cache = new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))) as IAppCache; 39 | 40 | cache.Add(CacheKey, ComplexObject1); 41 | 42 | var obj = cache.Get(CacheKey); 43 | 44 | obj.Int = 256; 45 | cache.Add(CacheKey, obj); 46 | 47 | cache.Remove(CacheKey); 48 | 49 | return obj; 50 | } 51 | 52 | // Benchmark memory usage to ensure only a single instance of the object is created 53 | // Due to the nature of AsyncLazy, this test should also only take the the time it takes to create 54 | // one instance of the object. 55 | [Benchmark] 56 | public async Task Several_initializations_of_1Mb_object_with_200ms_delay() 57 | { 58 | var cache = new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))) as IAppCache; 59 | 60 | Task AddByteArrayToCache() => 61 | cache.GetOrAddAsync(CacheKey, async () => 62 | { 63 | await Task.Delay(200); 64 | return await Task.FromResult(new byte[1024 * 1024]); // 1Mb 65 | }); 66 | 67 | // Even though the second and third init attempts are later, this whole operation should still take the time of the first 68 | var creationTask1 = AddByteArrayToCache(); // initialization attempt, or 200ms 69 | var creationTask2 = Delayed(50, AddByteArrayToCache); 70 | var creationTask3 = Delayed(50, AddByteArrayToCache); 71 | 72 | await Task.WhenAll(creationTask1, creationTask2, creationTask3); 73 | 74 | return cache.Get(CacheKey); 75 | } 76 | 77 | private async Task Delayed(int ms, Func action) 78 | { 79 | await Task.Delay(ms); 80 | await action(); 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /LazyCache.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | 3 | namespace LazyCache.Benchmarks 4 | { 5 | public static class Program 6 | { 7 | public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); 8 | } 9 | } -------------------------------------------------------------------------------- /LazyCache.Benchmarks/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "LazyCache.Benchmarks": { 4 | "commandName": "Project" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /LazyCache.Benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # LazyCache.Benchmarks 2 | This project is dedicated towards benchmarking (using [BenchmarkDotNet](https://benchmarkdotnet.org/index.html)) the basic functionality of LazyCache such that contributors and maintainers can verify the efficacy of changes towards the project - for better or for worse. 3 | 4 | ## Note to readers 5 | While it is always a good idea to understand performance of your third party libraries, it is rare that you will be concerned with performance on the scale of nanoseconds such that this library operates on. Be wary of premature optimization. 6 | 7 | # How to run 8 | - Ensure you have the requisite dotnet SDKs found in _LazyCache.Benchmarks.csproj_ 9 | - Clone the project 10 | - Open your favorite terminal, navigate to the Benchmark Project 11 | - `dotnet run -c Release` 12 | - Pick your desired benchmark suite via numeric entry 13 | 14 | If you are interested in benchmarking a specific method (after making changes to it, for instance), you can conveniently filter down to one specific benchmark, e.g. `dotnet run -c Release -- -f *Get` will only run the benchmarks for `IAppCache.Get` implementations, likewise with `*GetOrAddAsync`, or other methods. 15 | 16 | # Contributing 17 | If you have ideas for one or more benchmarks not covered here, please add an issue describing what you would like to see. Pull requests are always welcome! 18 | 19 | # Benchmark Types 20 | There are two types of benchmarks available. 21 | 22 | ## Basics 23 | The basic benchmarks are small and laser-focused on testing individual aspects of LazyCache. This suite of benchmarks uses the out-of-the-box MemoryCache from dotnet [seen here](https://github.com/dotnet/runtime/blob/master/src/libraries/Microsoft.Extensions.Caching.Memory/src/) as a baseline, to demonstrate the "cost" of LazyCache in comparison. 24 | 25 | ## Integration 26 | These benchmarks are designed to showcase full use-cases of LazyCache by chaining together various operations. As an example, with the Memory Diagnoser from BenchmarkDotNet, we can verify that concurrent calls to initialize a cache item correctly spin up one instance of said item, with the subsequent calls awaiting its result. 27 | 28 | ### Gotchas 29 | Remember that BenchmarkDotNet dutifully monitors allocations inside the benchmark method, and _only_ the method. At the time of writing, the default instance of the MemoryCacheProvider is static, and allocations into this cache will **not** be monitored by BenchmarkDotNet. For all benchmarks, please ensure you are creating new instances of the Service, Provider, and backing Cache. 30 | 31 | # Benchmarks 32 | 33 | ``` 34 | // * Summary * 35 | BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.1082 (1903/May2019Update/19H1) 36 | AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores 37 | .NET Core SDK=5.0.100-preview.7.20366.6 38 | [Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT 39 | ShortRun : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT 40 | 41 | Job=ShortRun IterationCount=3 LaunchCount=1 42 | WarmupCount=3 43 | ``` 44 | | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | 45 | |------------------------------------- |-----------:|------------:|---------:|------:|--------:|-------:|-------:|-------:|----------:| 46 | | DotNetMemoryCache_Init | 1,814.2 ns | 1,080.95 ns | 59.25 ns | 1.00 | 0.00 | 0.1850 | 0.0916 | 0.0019 | 1560 B | 47 | | LazyCache_Init | 3,265.5 ns | 599.75 ns | 32.87 ns | 1.80 | 0.07 | 0.3090 | 0.1526 | - | 2600 B | 48 | | | | | | | | | | | | 49 | | DotNetMemoryCache_Set | 504.1 ns | 42.38 ns | 2.32 ns | 1.00 | 0.00 | 0.0496 | - | - | 416 B | 50 | | LazyCache_Set | 841.6 ns | 172.51 ns | 9.46 ns | 1.67 | 0.02 | 0.0801 | - | - | 672 B | 51 | | | | | | | | | | | | 52 | | DotNetMemoryCache_Get_Miss | 201.1 ns | 3.54 ns | 0.19 ns | 1.00 | 0.00 | - | - | - | - | 53 | | LazyCache_Get_Miss | 241.1 ns | 13.94 ns | 0.76 ns | 1.20 | 0.00 | - | - | - | - | 54 | | | | | | | | | | | | 55 | | DotNetMemoryCache_Get_Hit | 242.2 ns | 28.93 ns | 1.59 ns | 1.00 | 0.00 | - | - | - | - | 56 | | LazyCache_Get_Hit | 280.4 ns | 10.45 ns | 0.57 ns | 1.16 | 0.01 | - | - | - | - | 57 | | | | | | | | | | | | 58 | | DotNetMemoryCache_GetOrAdd_Miss | 269.9 ns | 6.57 ns | 0.36 ns | 1.00 | 0.00 | 0.0076 | - | - | 64 B | 59 | | LazyCache_GetOrAdd_Miss | 368.5 ns | 60.35 ns | 3.31 ns | 1.37 | 0.01 | 0.0191 | - | - | 160 B | 60 | | | | | | | | | | | | 61 | | DotNetMemoryCache_GetOrAdd_Hit | 269.1 ns | 4.48 ns | 0.25 ns | 1.00 | 0.00 | 0.0076 | - | - | 64 B | 62 | | LazyCache_GetOrAdd_Hit | 377.1 ns | 10.57 ns | 0.58 ns | 1.40 | 0.00 | 0.0191 | - | - | 160 B | 63 | | | | | | | | | | | | 64 | | DotNetMemoryCache_GetOrAddAsync_Miss | 312.7 ns | 53.05 ns | 2.91 ns | 1.00 | 0.00 | 0.0162 | - | - | 136 B | 65 | | LazyCache_GetOrAddAsync_Miss | 507.5 ns | 33.96 ns | 1.86 ns | 1.62 | 0.02 | 0.0362 | - | - | 304 B | 66 | | | | | | | | | | | | 67 | | DotNetMemoryCache_GetOrAddAsync_Hit | 314.5 ns | 65.34 ns | 3.58 ns | 1.00 | 0.00 | 0.0162 | - | - | 136 B | 68 | | LazyCache_GetOrAddAsync_Hit | 535.9 ns | 47.83 ns | 2.62 ns | 1.70 | 0.03 | 0.0448 | - | - | 376 B | 69 | 70 | | Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | 71 | |------------------------------------------------------- |-----------------:|----------------:|----------------:|-------:|-------:|-------:|-----------:| 72 | | Init_CRUD | 5,115.1 ns | 991.0 ns | 54.32 ns | 0.4730 | 0.2365 | 0.0076 | 3.9 KB | 73 | | Several_initializations_of_1Mb_object_with_200ms_delay | 207,329,988.9 ns | 31,342,899.9 ns | 1,718,010.11 ns | - | - | - | 1031.75 KB | -------------------------------------------------------------------------------- /LazyCache.Ninject.UnitTests/LazyCache.Ninject.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LazyCache.Ninject.UnitTests/LazyCacheModuleTest.cs: -------------------------------------------------------------------------------- 1 | using Ninject; 2 | using NUnit.Framework; 3 | 4 | namespace LazyCache.Ninject.UnitTests 5 | { 6 | public class LazyCacheNinjectModuleTests 7 | { 8 | [Test] 9 | public void CanCreateCache() 10 | { 11 | IKernel kernel = new StandardKernel(new LazyCacheModule()); 12 | IAppCache cache = kernel.Get(); 13 | 14 | var cached = cache.GetOrAdd("some-key", () => new object()); 15 | 16 | Assert.NotNull(cached); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /LazyCache.Ninject/LazyCache.Ninject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | LazyCache 7 | true 8 | 1.0.0 9 | 10 | $(LazyCacheNinjectVersion)$(LazyCacheNinjectVersionSuffix) 11 | $(APPVEYOR_BUILD_NUMBER) 12 | 0 13 | $(LazyCacheNinjectVersion).$(AppVeyorBuildNumber) 14 | https://github.com/alastairtree 15 | https://github.com/alastairtree 16 | Ninject module regististrations for LazyCache to initialise dependency injection 17 | Copyright 2014 - 2018 Alastair Crabtree 18 | https://github.com/alastairtree/LazyCache 19 | logo-128.png 20 | https://github.com/alastairtree/LazyCache 21 | LazyCache DependecyInjection ServiceCollection SingleTon Transient Ninject 22 | MIT 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LazyCache.Ninject/LazyCacheModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using LazyCache.Providers; 3 | using Microsoft.Extensions.Caching.Memory; 4 | using Microsoft.Extensions.Options; 5 | using Ninject.Modules; 6 | 7 | namespace LazyCache 8 | { 9 | public class LazyCacheModule : NinjectModule 10 | { 11 | private readonly Func implementationFactory; 12 | 13 | public LazyCacheModule() 14 | { 15 | } 16 | 17 | public LazyCacheModule(Func implementationFactory) 18 | { 19 | this.implementationFactory = implementationFactory; 20 | } 21 | 22 | // See also https://github.com/aspnet/Caching/blob/dev/src/Microsoft.Extensions.Caching.Memory/MemoryCacheServiceCollectionExtensions.cs 23 | public override void Load() 24 | { 25 | Bind>().ToConstant(Options.Create(new MemoryCacheOptions())); 26 | Bind().To().InSingletonScope(); 27 | Bind().To().InSingletonScope(); 28 | 29 | if (implementationFactory == null) 30 | Bind().To().InSingletonScope(); 31 | else 32 | Bind().ToMethod(context => implementationFactory()).InSingletonScope(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /LazyCache.UnitTests/AspNetCoreTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using NUnit.Framework; 9 | 10 | namespace LazyCache.UnitTests 11 | { 12 | public class AspNetCoreTests 13 | { 14 | [Test] 15 | public void CanResolveCacheFromServiceCollectionAsRequiredService() 16 | { 17 | var container = new ServiceCollection(); 18 | container.AddLazyCache(); 19 | var provider = container.BuildServiceProvider(); 20 | 21 | var cache = provider.GetRequiredService(); 22 | var result = cache?.GetOrAdd("key", () => new object()); 23 | 24 | cache.Should().NotBeNull(); 25 | result.Should().NotBeNull(); 26 | } 27 | 28 | [Test] 29 | public void CanResolveCacheFromServiceCollectionAsService() 30 | { 31 | var container = new ServiceCollection(); 32 | container.AddLazyCache(); 33 | var provider = container.BuildServiceProvider(); 34 | 35 | var cache = provider.GetService(); 36 | var result = cache?.GetOrAdd("key", () => new object()); 37 | 38 | cache.Should().NotBeNull(); 39 | result.Should().NotBeNull(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LazyCache.UnitTests/AsyncHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace LazyCache.UnitTests 5 | { 6 | public static class AsyncHelper 7 | { 8 | public static Task CreateCancelledTask() 9 | { 10 | var tcs = new TaskCompletionSource(); 11 | tcs.SetCanceled(); 12 | return tcs.Task; 13 | } 14 | 15 | public static Task CreateTaskWithException() where TException : Exception 16 | { 17 | var tcs = new TaskCompletionSource(); 18 | tcs.SetException(Activator.CreateInstance()); 19 | return tcs.Task; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using LazyCache.Providers; 8 | using Microsoft.Extensions.Caching.Memory; 9 | using Microsoft.Extensions.Primitives; 10 | using NUnit.Framework; 11 | 12 | namespace LazyCache.UnitTests 13 | { 14 | [TestFixture] 15 | public class CachingServiceMemoryCacheProviderTests 16 | { 17 | private static CachingService BuildCache() 18 | { 19 | return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 20 | } 21 | 22 | private IAppCache sut; 23 | 24 | private readonly MemoryCacheEntryOptions oneHourNonRemoveableMemoryCacheEntryOptions = 25 | new MemoryCacheEntryOptions 26 | { 27 | AbsoluteExpiration = DateTimeOffset.Now.AddHours(1), 28 | Priority = CacheItemPriority.NeverRemove 29 | }; 30 | 31 | private ComplexTestObject testObject = new ComplexTestObject(); 32 | 33 | private class ComplexTestObject 34 | { 35 | public readonly IList SomeItems = new List { 1, 2, 3, "testing123" }; 36 | public string SomeMessage = "testing123"; 37 | } 38 | 39 | private const string TestKey = "testKey"; 40 | 41 | [SetUp] 42 | public void BeforeEachTest() 43 | { 44 | sut = BuildCache(); 45 | testObject = new ComplexTestObject(); 46 | } 47 | 48 | 49 | [Test] 50 | public void AddComplexObjectThenGetGenericReturnsCachedObject() 51 | { 52 | testObject.SomeItems.Add("Another"); 53 | testObject.SomeMessage = "changed-it-up"; 54 | sut.Add(TestKey, testObject); 55 | var actual = sut.Get(TestKey); 56 | var expected = testObject; 57 | Assert.NotNull(actual); 58 | Assert.AreEqual(expected, actual); 59 | testObject.SomeItems.Should().Contain("Another"); 60 | testObject.SomeMessage.Should().Be("changed-it-up"); 61 | } 62 | 63 | [Test] 64 | public void AddComplexObjectThenGetReturnsCachedObject() 65 | { 66 | sut.Add(TestKey, testObject); 67 | var actual = sut.Get(TestKey) as ComplexTestObject; 68 | var expected = testObject; 69 | Assert.NotNull(actual); 70 | Assert.AreEqual(expected, actual); 71 | } 72 | 73 | [Test] 74 | public void AddEmptyKeyThrowsException() 75 | { 76 | Action act = () => sut.Add("", new object()); 77 | act.Should().Throw(); 78 | } 79 | 80 | [Test] 81 | public void AddEmptyKeyThrowsExceptionWithExpiration() 82 | { 83 | Action act = () => sut.Add("", new object(), DateTimeOffset.Now.AddHours(1)); 84 | act.Should().Throw(); 85 | } 86 | 87 | [Test] 88 | public void AddEmptyKeyThrowsExceptionWithPolicy() 89 | { 90 | Action act = () => sut.Add("", new object(), new MemoryCacheEntryOptions()); 91 | act.Should().Throw(); 92 | } 93 | 94 | [Test] 95 | public void AddEmptyKeyThrowsExceptionWithSliding() 96 | { 97 | Action act = () => sut.Add("", new object(), new TimeSpan(1000)); 98 | act.Should().Throw(); 99 | } 100 | 101 | [Test] 102 | public void AddNullKeyThrowsException() 103 | { 104 | Action act = () => sut.Add(null, new object()); 105 | act.Should().Throw(); 106 | } 107 | 108 | [Test] 109 | public void AddNullKeyThrowsExceptionWithExpiration() 110 | { 111 | Action act = () => sut.Add(null, new object(), DateTimeOffset.Now.AddHours(1)); 112 | act.Should().Throw(); 113 | } 114 | 115 | [Test] 116 | public void AddNullKeyThrowsExceptionWithPolicy() 117 | { 118 | Action act = () => sut.Add(null, new object(), new MemoryCacheEntryOptions()); 119 | act.Should().Throw(); 120 | } 121 | 122 | [Test] 123 | public void AddNullKeyThrowsExceptionWithSliding() 124 | { 125 | Action act = () => sut.Add(null, new object(), new TimeSpan(1000)); 126 | act.Should().Throw(); 127 | } 128 | 129 | [Test] 130 | public void AddNullThrowsException() 131 | { 132 | Action act = () => sut.Add(TestKey, null); 133 | act.Should().Throw(); 134 | } 135 | 136 | [Test] 137 | public void AddThenGetReturnsCachedObject() 138 | { 139 | sut.Add(TestKey, "testObject"); 140 | Assert.AreEqual("testObject", sut.Get(TestKey)); 141 | } 142 | 143 | [Test] 144 | public void AddWithOffsetReturnsCachedItem() 145 | { 146 | sut.Add(TestKey, "testObject", DateTimeOffset.Now.AddSeconds(1)); 147 | Assert.AreEqual("testObject", sut.Get(TestKey)); 148 | } 149 | 150 | [Test] 151 | public void AddWithOffsetThatExpiresReturnsNull() 152 | { 153 | sut.Add(TestKey, "testObject", DateTimeOffset.Now.AddSeconds(1)); 154 | Thread.Sleep(1500); 155 | Assert.IsNull(sut.Get(TestKey)); 156 | } 157 | 158 | [Test] 159 | public void AddWithPolicyReturnsCachedItem() 160 | { 161 | sut.Add(TestKey, "testObject", new MemoryCacheEntryOptions()); 162 | Assert.AreEqual("testObject", sut.Get(TestKey)); 163 | } 164 | 165 | [Test] 166 | public void AddWithSlidingReturnsCachedItem() 167 | { 168 | sut.Add(TestKey, "testObject", new TimeSpan(5000)); 169 | Assert.AreEqual("testObject", sut.Get(TestKey)); 170 | } 171 | 172 | [Test] 173 | public void AddWithSlidingThatExpiresReturnsNull() 174 | { 175 | sut.Add(TestKey, "testObject", new TimeSpan(750)); 176 | Thread.Sleep(1500); 177 | Assert.IsNull(sut.Get(TestKey)); 178 | } 179 | 180 | [Test] 181 | public void CacheProviderIsNotNull() 182 | { 183 | sut.CacheProvider.Should().NotBeNull(); 184 | } 185 | 186 | [Test] 187 | public void DefaultContructorThenGetOrAddFromSecondCachingServiceHasSharedUnderlyingCache() 188 | { 189 | var cacheOne = new CachingService(); 190 | var cacheTwo = new CachingService(); 191 | 192 | var resultOne = cacheOne.GetOrAdd(TestKey, () => "resultOne"); 193 | var resultTwo = cacheTwo.GetOrAdd(TestKey, () => "resultTwo"); // should not get executed 194 | 195 | resultOne.Should().Be("resultOne", "GetOrAdd should execute the delegate"); 196 | resultTwo.Should().Be("resultOne", "CachingService should use a shared cache by default"); 197 | } 198 | 199 | [Test] 200 | public void GetCachedNullableStructTypeParamReturnsType() 201 | { 202 | DateTime? cached = new DateTime(); 203 | sut.Add(TestKey, cached); 204 | Assert.AreEqual(cached.Value, sut.Get(TestKey)); 205 | } 206 | 207 | [Test] 208 | public void GetEmptyKeyThrowsException() 209 | { 210 | Action act = () => sut.Get(""); 211 | act.Should().Throw(); 212 | } 213 | 214 | [Test] 215 | public void GetFromCacheTwiceAtSameTimeOnlyAddsOnce() 216 | { 217 | var times = 0; 218 | 219 | var t1 = Task.Factory.StartNew(() => 220 | { 221 | sut.GetOrAdd(TestKey, () => 222 | { 223 | Interlocked.Increment(ref times); 224 | return new DateTime(2001, 01, 01); 225 | }); 226 | }); 227 | 228 | var t2 = Task.Factory.StartNew(() => 229 | { 230 | sut.GetOrAdd(TestKey, () => 231 | { 232 | Interlocked.Increment(ref times); 233 | return new DateTime(2001, 01, 01); 234 | }); 235 | }); 236 | 237 | Task.WaitAll(t1, t2); 238 | 239 | Assert.AreEqual(1, times); 240 | } 241 | 242 | [Test] 243 | public void GetNullKeyThrowsException() 244 | { 245 | Action act = () => sut.Get(null); 246 | act.Should().Throw(); 247 | } 248 | 249 | [Test] 250 | public void GetOrAddAndThenGetObjectReturnsCorrectType() 251 | { 252 | sut.GetOrAdd(TestKey, () => testObject); 253 | var actual = sut.Get(TestKey); 254 | Assert.IsNotNull(actual); 255 | } 256 | 257 | [Test] 258 | public void GetOrAddAndThenGetOrAddDifferentTypeDoesLastInWins() 259 | { 260 | var first = sut.GetOrAdd(TestKey, () => new object()); 261 | var second = sut.GetOrAdd(TestKey, () => testObject); 262 | Assert.IsNotNull(second); 263 | Assert.IsInstanceOf(second); 264 | } 265 | 266 | [Test] 267 | public void GetOrAddAndThenGetValueObjectReturnsCorrectType() 268 | { 269 | sut.GetOrAdd(TestKey, () => 123); 270 | var actual = sut.Get(TestKey); 271 | Assert.AreEqual(123, actual); 272 | } 273 | 274 | [Test] 275 | public void GetOrAddAndThenGetWrongtypeObjectReturnsNull() 276 | { 277 | sut.GetOrAdd(TestKey, () => testObject); 278 | var actual = sut.Get(TestKey); 279 | Assert.IsNull(actual); 280 | } 281 | 282 | 283 | 284 | [Test] 285 | public void GetOrAddAsyncACancelledTaskDoesNotCacheIt() 286 | { 287 | Assert.ThrowsAsync(async () => 288 | await sut.GetOrAddAsync(TestKey, AsyncHelper.CreateCancelledTask)); 289 | 290 | var stillCached = sut.Get>(TestKey); 291 | 292 | Assert.That(stillCached, Is.Null); 293 | } 294 | 295 | [Test] 296 | public void GetOrAddAsyncACancelledTaskReturnsTheCacelledTaskToConsumer() 297 | { 298 | var cancelledTask = sut.GetOrAddAsync(TestKey, AsyncHelper.CreateCancelledTask); 299 | 300 | Assert.That(cancelledTask, Is.Not.Null); 301 | 302 | Assert.Throws(cancelledTask.Wait); 303 | 304 | Assert.That(cancelledTask.IsCanceled, Is.True); 305 | } 306 | 307 | [Test] 308 | public void GetOrAddAsyncAFailingTaskDoesNotCacheIt() 309 | { 310 | Task FetchAsync() 311 | { 312 | return Task.Factory.StartNew(() => throw new ApplicationException()); 313 | } 314 | 315 | Assert.ThrowsAsync(async () => await sut.GetOrAddAsync(TestKey, FetchAsync)); 316 | 317 | var stillCached = sut.Get>(TestKey); 318 | 319 | Assert.That(stillCached, Is.Null); 320 | } 321 | 322 | [Test] 323 | public async Task GetOrAddAsyncAndThenGetAsyncObjectReturnsCorrectType() 324 | { 325 | await sut.GetOrAddAsync(TestKey, () => Task.FromResult(testObject)); 326 | var actual = await sut.GetAsync(TestKey); 327 | Assert.IsNotNull(actual); 328 | Assert.That(actual, Is.EqualTo(testObject)); 329 | } 330 | 331 | [Test] 332 | public async Task GetOrAddAsyncAndThenGetOrAddAsyncDifferentTypeDoesLastInWins() 333 | { 334 | var first = await sut.GetOrAddAsync(TestKey, () => Task.FromResult(new object())); 335 | var second = await sut.GetOrAddAsync(TestKey, () => Task.FromResult(testObject)); 336 | Assert.IsNotNull(second); 337 | Assert.IsInstanceOf(second); 338 | } 339 | 340 | [Test] 341 | public async Task GetOrAddAsyncAndThenGetAsyncWrongObjectReturnsNull() 342 | { 343 | await sut.GetOrAddAsync(TestKey, () => Task.FromResult(testObject)); 344 | var actual = await sut.GetAsync(TestKey); 345 | Assert.IsNull(actual); 346 | } 347 | 348 | [Test] 349 | public async Task GetOrAddAsyncFollowinGetOrAddReturnsTheFirstObjectAndIgnoresTheSecondTask() 350 | { 351 | ComplexTestObject FetchSync() 352 | { 353 | return testObject; 354 | } 355 | 356 | Task FetchAsync() 357 | { 358 | return Task.FromResult(new ComplexTestObject()); 359 | } 360 | 361 | var actualSync = sut.GetOrAdd(TestKey, FetchSync); 362 | var actualAsync = await sut.GetOrAddAsync(TestKey, FetchAsync); 363 | 364 | Assert.IsNotNull(actualSync); 365 | Assert.That(actualSync, Is.EqualTo(testObject)); 366 | 367 | Assert.IsNotNull(actualAsync); 368 | Assert.That(actualAsync, Is.EqualTo(testObject)); 369 | 370 | Assert.AreEqual(actualAsync, actualSync); 371 | } 372 | 373 | [Test] 374 | public async Task GetOrAddAsyncTaskAndThenGetTaskOfAnotherTypeReturnsNull() 375 | { 376 | var cachedAsyncResult = testObject; 377 | await sut.GetOrAddAsync(TestKey, () => Task.FromResult(cachedAsyncResult)); 378 | var actual = sut.Get>(TestKey); 379 | Assert.Null(actual); 380 | } 381 | 382 | [Test] 383 | public async Task GetOrAddAsyncTaskAndThenGetTaskOfObjectReturnsCorrectType() 384 | { 385 | var cachedAsyncResult = testObject; 386 | await sut.GetOrAddAsync(TestKey, () => Task.FromResult(cachedAsyncResult)); 387 | var actual = sut.Get>(TestKey); 388 | Assert.IsNotNull(actual); 389 | Assert.That(actual.Result, Is.EqualTo(cachedAsyncResult)); 390 | } 391 | 392 | [Test] 393 | public async Task GetOrAddAsyncWillAddOnFirstCall() 394 | { 395 | var times = 0; 396 | 397 | var expected = await sut.GetOrAddAsync(TestKey, () => 398 | { 399 | times++; 400 | return Task.FromResult(new DateTime(2001, 01, 01)); 401 | }); 402 | Assert.AreEqual(2001, expected.Year); 403 | Assert.AreEqual(1, times); 404 | } 405 | 406 | 407 | [Test] 408 | public async Task GetOrAddAsyncWillAddOnFirstCallButReturnCachedOnSecond() 409 | { 410 | var times = 0; 411 | 412 | var expectedFirst = await sut.GetOrAdd(TestKey, () => 413 | { 414 | times++; 415 | return Task.FromResult(new DateTime(2001, 01, 01)); 416 | }); 417 | 418 | var expectedSecond = await sut.GetOrAdd(TestKey, () => 419 | { 420 | times++; 421 | return Task.FromResult(new DateTime(2002, 01, 01)); 422 | }); 423 | 424 | Assert.AreEqual(2001, expectedFirst.Year); 425 | Assert.AreEqual(2001, expectedSecond.Year); 426 | Assert.AreEqual(1, times); 427 | } 428 | 429 | [Test] 430 | public async Task GetOrAddAsyncWillNotAddIfExistingData() 431 | { 432 | var times = 0; 433 | 434 | var cached = new DateTime(1999, 01, 01); 435 | sut.Add(TestKey, cached); 436 | 437 | var expected = await sut.GetOrAddAsync(TestKey, () => 438 | { 439 | times++; 440 | return Task.FromResult(new DateTime(2001, 01, 01)); 441 | }); 442 | Assert.AreEqual(1999, expected.Year); 443 | Assert.AreEqual(0, times); 444 | } 445 | 446 | [Test] 447 | [MaxTime(1000)] 448 | public void GetOrAddAsyncWithALongTaskReturnsBeforeTaskCompletes() 449 | { 450 | var cachedResult = testObject; 451 | 452 | Task FetchAsync() 453 | { 454 | return Task.Delay(TimeSpan.FromMinutes(1)) 455 | .ContinueWith(x => cachedResult); 456 | } 457 | 458 | var actualResult = sut.GetOrAddAsync(TestKey, FetchAsync); 459 | 460 | Assert.That(actualResult, Is.Not.Null); 461 | Assert.That(actualResult.IsCompleted, Is.Not.True); 462 | } 463 | 464 | [Test] 465 | public async Task GetOrAddAsyncWithOffsetWillAddAndReturnTaskOfCached() 466 | { 467 | var expectedFirst = await sut.GetOrAddAsync( 468 | TestKey, 469 | () => Task.FromResult(new DateTime(2001, 01, 01)), 470 | DateTimeOffset.Now.AddSeconds(5) 471 | ); 472 | var expectedSecond = await sut.Get>(TestKey); 473 | 474 | Assert.AreEqual(2001, expectedFirst.Year); 475 | Assert.AreEqual(2001, expectedSecond.Year); 476 | } 477 | 478 | [Test] 479 | public async Task GetOrAddAsyncWithPolicyAndThenGetTaskObjectReturnsCorrectType() 480 | { 481 | var item = testObject; 482 | await sut.GetOrAddAsync(TestKey, () => Task.FromResult(item), 483 | oneHourNonRemoveableMemoryCacheEntryOptions); 484 | var actual = await sut.Get>(TestKey); 485 | Assert.That(actual, Is.EqualTo(item)); 486 | } 487 | 488 | [Test] 489 | [MaxTime(20000)] 490 | public async Task GetOrAddAsyncWithPostEvictionCallbacksReturnsTheOriginalCachedKeyEvenIfNotGettedBeforehand() 491 | { 492 | string callbackKey = null; 493 | var memoryCacheEntryOptions = new MemoryCacheEntryOptions 494 | { 495 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100) 496 | }; 497 | memoryCacheEntryOptions.RegisterPostEvictionCallback((key, value, reason, state) => 498 | { 499 | callbackKey = key.ToString(); 500 | }); 501 | await sut.GetOrAddAsync(TestKey, () => Task.FromResult(123), memoryCacheEntryOptions); 502 | 503 | sut.Remove(TestKey); //force removed callback to fire 504 | while (callbackKey == null) 505 | Thread.Sleep(500); 506 | 507 | callbackKey.Should().Be(TestKey); 508 | } 509 | 510 | [Test] 511 | [MaxTime(20000)] 512 | public async Task 513 | GetOrAddAsyncWithPostEvictionCallbacksReturnsTheOriginalCachedObjectEvenIfNotGettedBeforehand() 514 | { 515 | object callbackValue = null; 516 | var memoryCacheEntryOptions = new MemoryCacheEntryOptions 517 | { 518 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100) 519 | }; 520 | memoryCacheEntryOptions.RegisterPostEvictionCallback((key, value, reason, state) => 521 | { 522 | callbackValue = value; 523 | }); 524 | await sut.GetOrAddAsync(TestKey, () => Task.FromResult(123), memoryCacheEntryOptions); 525 | 526 | sut.Remove(TestKey); //force removed callback to fire 527 | while (callbackValue == null) 528 | Thread.Sleep(500); 529 | 530 | Assert.That(callbackValue, Is.AssignableTo>()); 531 | var callbackResultValue = await (Task)callbackValue; 532 | Assert.AreEqual(123, callbackResultValue); 533 | } 534 | 535 | [Test] 536 | public async Task GetOrAddAyncAllowsCachingATask() 537 | { 538 | var cachedResult = testObject; 539 | 540 | Task FetchAsync() 541 | { 542 | return Task.FromResult(cachedResult); 543 | } 544 | 545 | var actualResult = 546 | await sut.GetOrAddAsync(TestKey, FetchAsync, oneHourNonRemoveableMemoryCacheEntryOptions); 547 | 548 | Assert.That(actualResult, Is.EqualTo(cachedResult)); 549 | } 550 | 551 | [Test] 552 | public async Task GetOrAddFollowinGetOrAddAsyncReturnsTheFirstObjectAndUnwrapsTheFirstTask() 553 | { 554 | Task FetchAsync() 555 | { 556 | return Task.FromResult(testObject); 557 | } 558 | 559 | ComplexTestObject FetchSync() 560 | { 561 | return new ComplexTestObject(); 562 | } 563 | 564 | var actualAsync = await sut.GetOrAddAsync(TestKey, FetchAsync); 565 | var actualSync = sut.GetOrAdd(TestKey, FetchSync); 566 | 567 | Assert.IsNotNull(actualAsync); 568 | Assert.That(actualAsync, Is.EqualTo(testObject)); 569 | 570 | Assert.IsNotNull(actualSync); 571 | Assert.That(actualSync, Is.EqualTo(testObject)); 572 | 573 | Assert.AreEqual(actualAsync, actualSync); 574 | } 575 | 576 | [Test] 577 | public void GetOrAddWillAddOnFirstCall() 578 | { 579 | var times = 0; 580 | 581 | 582 | var expected = sut.GetOrAdd(TestKey, () => 583 | { 584 | times++; 585 | return new DateTime(2001, 01, 01); 586 | }); 587 | Assert.AreEqual(2001, expected.Year); 588 | Assert.AreEqual(1, times); 589 | } 590 | 591 | 592 | [Test] 593 | public void GetOrAddWillAddOnFirstCallButReturnCachedOnSecond() 594 | { 595 | var times = 0; 596 | 597 | var expectedFirst = sut.GetOrAdd(TestKey, () => 598 | { 599 | times++; 600 | return new DateTime(2001, 01, 01); 601 | }); 602 | 603 | var expectedSecond = sut.GetOrAdd(TestKey, () => 604 | { 605 | times++; 606 | return new DateTime(2002, 01, 01); 607 | }); 608 | 609 | Assert.AreEqual(2001, expectedFirst.Year); 610 | Assert.AreEqual(2001, expectedSecond.Year); 611 | Assert.AreEqual(1, times); 612 | } 613 | 614 | [Test] 615 | public void GetOrAddWillNotAddIfExistingData() 616 | { 617 | var times = 0; 618 | 619 | var cached = new DateTime(1999, 01, 01); 620 | sut.Add(TestKey, cached); 621 | 622 | var expected = sut.GetOrAdd(TestKey, () => 623 | { 624 | times++; 625 | return new DateTime(2001, 01, 01); 626 | }); 627 | Assert.AreEqual(1999, expected.Year); 628 | Assert.AreEqual(0, times); 629 | } 630 | 631 | [Test] 632 | public void GetOrAddWithOffsetWillAddAndReturnCached() 633 | { 634 | var expectedFirst = sut.GetOrAdd( 635 | TestKey, 636 | () => new DateTime(2001, 01, 01), 637 | DateTimeOffset.Now.AddSeconds(5) 638 | ); 639 | var expectedSecond = sut.Get(TestKey); 640 | 641 | Assert.AreEqual(2001, expectedFirst.Year); 642 | Assert.AreEqual(2001, expectedSecond.Year); 643 | } 644 | 645 | [Test] 646 | public void GetOrAddWithAbsoluteOffsetExpiryAsDateTimeOffsetDoesExpireItems() 647 | { 648 | var millisecondsCacheDuration = 100; 649 | var validResult = sut.GetOrAdd( 650 | TestKey, 651 | () => new ComplexTestObject(), 652 | DateTimeOffset.Now.AddMilliseconds(millisecondsCacheDuration) 653 | ); 654 | // pass expiry time with a delay 655 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 656 | var expiredResult = sut.Get(TestKey); 657 | 658 | Assert.That(validResult, Is.Not.Null); 659 | Assert.That(expiredResult, Is.Null); 660 | } 661 | 662 | [Test] 663 | public void GetOrAddWithAbsoluteOffsetExpiryAsTimeSpanDoesExpireItems() 664 | { 665 | var millisecondsCacheDuration = 100; 666 | var validResult = sut.GetOrAdd( 667 | TestKey, 668 | () => new ComplexTestObject(), 669 | TimeSpan.FromMilliseconds(millisecondsCacheDuration) 670 | ); 671 | // pass expiry time with a delay 672 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 673 | var expiredResult = sut.Get(TestKey); 674 | 675 | Assert.That(validResult, Is.Not.Null); 676 | Assert.That(expiredResult, Is.Null); 677 | } 678 | 679 | [Test] 680 | public void GetOrAddWithAbsoluteOffsetExpiryInTheDelegateDoesExpireItems() 681 | { 682 | var millisecondsCacheDuration = 100; 683 | var validResult = sut.GetOrAdd( 684 | TestKey, 685 | entry => 686 | { 687 | entry.SetAbsoluteExpiration(DateTimeOffset.Now.AddMilliseconds(millisecondsCacheDuration)); 688 | return new ComplexTestObject(); 689 | } 690 | ); 691 | // pass expiry time with a delay 692 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 693 | var expiredResult = sut.Get(TestKey); 694 | 695 | Assert.That(validResult, Is.Not.Null); 696 | Assert.That(expiredResult, Is.Null); 697 | } 698 | 699 | [Test] 700 | public void GetOrAddWithAbsoluteOffsetExpiryInTheDelegateUsingTimeSpanDoesExpireItems() 701 | { 702 | var millisecondsCacheDuration = 100; 703 | var validResult = sut.GetOrAdd( 704 | TestKey, 705 | entry => 706 | { 707 | entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(millisecondsCacheDuration)); 708 | return new ComplexTestObject(); 709 | } 710 | ); 711 | // pass expiry time with a delay 712 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 713 | var expiredResult = sut.Get(TestKey); 714 | 715 | Assert.That(validResult, Is.Not.Null); 716 | Assert.That(expiredResult, Is.Null); 717 | } 718 | 719 | [Test] 720 | public async Task GetOrAddAsyncWithAbsoluteOffsetExpiryAsDateTimeOffsetDoesExpireItems() 721 | { 722 | var millisecondsCacheDuration = 100; 723 | var validResult = await sut.GetOrAddAsync( 724 | TestKey, 725 | () => Task.FromResult(new ComplexTestObject()), 726 | DateTimeOffset.Now.AddMilliseconds(millisecondsCacheDuration) 727 | ); 728 | // pass expiry time with a delay 729 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 730 | var expiredResult = sut.Get(TestKey); 731 | 732 | Assert.That(validResult, Is.Not.Null); 733 | Assert.That(expiredResult, Is.Null); 734 | } 735 | 736 | [Test] 737 | public async Task GetOrAddAsyncWithAbsoluteOffsetExpiryAsTimeSpanDoesExpireItems() 738 | { 739 | var millisecondsCacheDuration = 100; 740 | var validResult = await sut.GetOrAddAsync( 741 | TestKey, 742 | () => Task.FromResult(new ComplexTestObject()), 743 | TimeSpan.FromMilliseconds(millisecondsCacheDuration) 744 | ); 745 | // pass expiry time with a delay 746 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 747 | var expiredResult = sut.Get(TestKey); 748 | 749 | Assert.That(validResult, Is.Not.Null); 750 | Assert.That(expiredResult, Is.Null); 751 | } 752 | 753 | [Test] 754 | public async Task GetOrAddAsyncWithAbsoluteOffsetExpiryInTheDelegateDoesExpireItems() 755 | { 756 | var millisecondsCacheDuration = 100; 757 | var validResult = await sut.GetOrAddAsync( 758 | TestKey, 759 | entry => 760 | { 761 | entry.SetAbsoluteExpiration(DateTimeOffset.Now.AddMilliseconds(millisecondsCacheDuration)); 762 | return Task.FromResult(new ComplexTestObject()); 763 | } 764 | ); 765 | // pass expiry time with a delay 766 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 767 | var expiredResult = sut.Get(TestKey); 768 | 769 | Assert.That(validResult, Is.Not.Null); 770 | Assert.That(expiredResult, Is.Null); 771 | } 772 | 773 | [Test] 774 | public async Task GetOrAddAsyncWithAbsoluteOffsetExpiryInTheDelegateUsingTimeSpanDoesExpireItems() 775 | { 776 | var millisecondsCacheDuration = 100; 777 | var validResult = await sut.GetOrAddAsync( 778 | TestKey, 779 | entry => 780 | { 781 | entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(millisecondsCacheDuration)); 782 | return Task.FromResult(new ComplexTestObject()); 783 | } 784 | ); 785 | // pass expiry time with a delay 786 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 787 | var expiredResult = sut.Get(TestKey); 788 | 789 | Assert.That(validResult, Is.Not.Null); 790 | Assert.That(expiredResult, Is.Null); 791 | } 792 | 793 | [Test] 794 | public async Task GetOrAddAsyncWithAbsoluteOffsetExpiryInTheDelegateAfterLongRunningTaskDoesExpireItems() 795 | { 796 | var millisecondsCacheDuration = 100; 797 | var validResult = await sut.GetOrAddAsync( 798 | TestKey, 799 | async entry => 800 | { 801 | await Task.Delay(50); 802 | entry.SetAbsoluteExpiration(DateTimeOffset.Now.AddMilliseconds(millisecondsCacheDuration)); 803 | return new ComplexTestObject(); 804 | } 805 | ); 806 | // pass expiry time with a delay 807 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 808 | var expiredResult = sut.Get(TestKey); 809 | 810 | Assert.That(validResult, Is.Not.Null); 811 | Assert.That(expiredResult, Is.Null); 812 | } 813 | 814 | [Test] 815 | public async Task GetOrAddAsyncWithAbsoluteOffsetExpiryInTheDelegateUsingTimeSpanAfterLongRunningTaskDoesExpireItems() 816 | { 817 | var millisecondsCacheDuration = 100; 818 | var validResult = await sut.GetOrAddAsync( 819 | TestKey, 820 | async entry => 821 | { 822 | await Task.Delay(50); 823 | entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(millisecondsCacheDuration)); 824 | return new ComplexTestObject(); 825 | } 826 | ); 827 | // pass expiry time with a delay 828 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 829 | var expiredResult = sut.Get(TestKey); 830 | 831 | Assert.That(validResult, Is.Not.Null); 832 | Assert.That(expiredResult, Is.Null); 833 | } 834 | 835 | [Test] 836 | public void GetOrAddWithCancellationExpiryBasedOnTimerAndCallbackInTheDelegateDoesExpireItemsAndFireTheCallback() 837 | { 838 | var millisecondsCacheDuration = 100; 839 | var callbackHasFired = false; 840 | var tokenSource = new CancellationTokenSource(millisecondsCacheDuration); 841 | var expireToken = new CancellationChangeToken(tokenSource.Token); 842 | var validResult = sut.GetOrAdd( 843 | TestKey, 844 | entry => 845 | { 846 | return new ComplexTestObject(); 847 | }, new MemoryCacheEntryOptions() 848 | .AddExpirationToken(expireToken) 849 | .RegisterPostEvictionCallback((key, value, reason, state) => callbackHasFired = true)); 850 | // trigger expiry 851 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 852 | 853 | Assert.That(validResult, Is.Not.Null); 854 | Assert.That(callbackHasFired, Is.True); 855 | } 856 | 857 | [Test] 858 | public void GetOrAddWithImmediateExpirationAndCallbackInTheDelegateDoesExpireItemsAndFireTheCallback() 859 | { 860 | var millisecondsCacheDuration = 100; 861 | var callbackHasFired = false; 862 | var validResult = sut.GetOrAdd( 863 | TestKey, 864 | entry => 865 | { 866 | return new ComplexTestObject(); 867 | }, LazyCacheEntryOptions 868 | .WithImmediateAbsoluteExpiration(TimeSpan.FromMilliseconds(millisecondsCacheDuration)) 869 | .RegisterPostEvictionCallback((key, value, reason, state) => callbackHasFired = true)); 870 | // trigger expiry 871 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 872 | 873 | Assert.That(validResult, Is.Not.Null); 874 | Assert.That(callbackHasFired, Is.True); 875 | } 876 | 877 | [Test] 878 | public async Task GetOrAddAsyncWithImmediateExpirationAndCallbackInTheDelegateDoesExpireItemsAndFireTheCallback() 879 | { 880 | var millisecondsCacheDuration = 1000; 881 | var callbackHasFired = false; 882 | var validResult = await sut.GetOrAddAsync( 883 | TestKey, 884 | entry => 885 | { 886 | return Task.FromResult(new ComplexTestObject()); 887 | }, LazyCacheEntryOptions 888 | .WithImmediateAbsoluteExpiration(TimeSpan.FromMilliseconds(millisecondsCacheDuration)) 889 | .RegisterPostEvictionCallback((key, value, reason, state) => callbackHasFired = true)); 890 | // trigger expiry 891 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 1000)); 892 | 893 | Assert.That(validResult, Is.Not.Null); 894 | Assert.That(callbackHasFired, Is.True); 895 | } 896 | 897 | [Test] 898 | public async Task AutoRefresh() 899 | { 900 | var key = "someKey"; 901 | var refreshInterval = TimeSpan.FromSeconds(1); 902 | var timesGenerated = 0; 903 | 904 | // this is the Func what we are caching 905 | ComplexTestObject GetStuff() 906 | { 907 | timesGenerated++; 908 | return new ComplexTestObject(); 909 | } 910 | 911 | // this sets up options that will recreate the entry on eviction 912 | MemoryCacheEntryOptions GetOptions() 913 | { 914 | var options = new LazyCacheEntryOptions() 915 | .SetAbsoluteExpiration(refreshInterval, ExpirationMode.ImmediateEviction); 916 | options.RegisterPostEvictionCallback((keyEvicted, value, reason, state) => 917 | { 918 | if (reason == EvictionReason.Expired || reason == EvictionReason.TokenExpired) 919 | sut.GetOrAdd(key, _ => GetStuff(), GetOptions()); 920 | }); 921 | return options; 922 | } 923 | 924 | for (var i = 0; i < 3; i++) 925 | { 926 | var thing = sut.GetOrAdd(key, () => GetStuff(), GetOptions()); 927 | Assert.That(thing, Is.Not.Null); 928 | await Task.Delay(2 * refreshInterval); 929 | } 930 | 931 | // refreshed every second in 6 seconds so generated 6 times 932 | // even though we only fetched it every other second which would be 3 times 933 | Assert.That(timesGenerated, Is.EqualTo(6)); 934 | } 935 | 936 | [Test] 937 | public async Task GetOrAddAsyncWithImmediateExpirationDoesExpireItems() 938 | { 939 | var millisecondsCacheDuration = 100; 940 | var validResult = await sut.GetOrAddAsync( 941 | TestKey, 942 | () => 943 | { 944 | return Task.FromResult(new ComplexTestObject()); 945 | }, DateTimeOffset.UtcNow.AddMilliseconds(millisecondsCacheDuration), ExpirationMode.ImmediateEviction); 946 | // trigger expiry 947 | Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsCacheDuration + 50)); 948 | 949 | var actual = sut.Get(TestKey); 950 | 951 | Assert.That(validResult, Is.Not.Null); 952 | Assert.That(actual, Is.Null); 953 | } 954 | 955 | [Test] 956 | [Ignore("Not a real unit tests - just used for hammering the cache")] 957 | public async Task PerfTest() 958 | { 959 | var watch = new Stopwatch(); 960 | watch.Start(); 961 | var asyncThreads = 10; 962 | var syncThreads = 10; 963 | var uniqueCacheItems = 20; 964 | int cacheMiss = 0; 965 | int hits = 0; 966 | var cancel = new CancellationTokenSource(TimeSpan.FromSeconds(10)); 967 | 968 | async Task GetStuffAsync() 969 | { 970 | await Task.Delay(25); 971 | Interlocked.Increment(ref cacheMiss); 972 | return new ComplexTestObject(); 973 | } 974 | 975 | ComplexTestObject GetStuff() 976 | { 977 | Thread.Sleep(25); 978 | Interlocked.Increment(ref cacheMiss); 979 | return new ComplexTestObject(); 980 | } 981 | 982 | 983 | var asyncActions = Task.Run(() => 984 | { 985 | Parallel.For(1, asyncThreads, async i => 986 | { 987 | while (!cancel.IsCancellationRequested) 988 | { 989 | var key = $"stuff-{hits % uniqueCacheItems}"; 990 | var cached = await sut.GetOrAddAsync(key, () => GetStuffAsync(), DateTimeOffset.UtcNow.AddSeconds(1)); 991 | if (!cancel.IsCancellationRequested) Interlocked.Increment(ref hits); 992 | } 993 | }); 994 | }); 995 | 996 | var syncActions = Task.Run(() => 997 | { 998 | Parallel.For(1, syncThreads, i => 999 | { 1000 | while (!cancel.IsCancellationRequested) 1001 | { 1002 | var key = $"stuff-{hits % uniqueCacheItems}"; 1003 | var cached = sut.GetOrAdd(key, () => GetStuff(), DateTimeOffset.UtcNow.AddSeconds(1)); 1004 | if (!cancel.IsCancellationRequested) Interlocked.Increment(ref hits); 1005 | } 1006 | }); 1007 | }); 1008 | 1009 | await Task.WhenAll(asyncActions, syncActions); 1010 | 1011 | watch.Stop(); 1012 | Console.WriteLine(watch.Elapsed); 1013 | Console.WriteLine("miss " + cacheMiss); 1014 | Console.WriteLine("hit " + hits); 1015 | } 1016 | 1017 | 1018 | [Test] 1019 | public void GetOrAddWithPolicyAndThenGetObjectReturnsCorrectType() 1020 | { 1021 | sut.GetOrAdd(TestKey, () => testObject, 1022 | oneHourNonRemoveableMemoryCacheEntryOptions); 1023 | var actual = sut.Get(TestKey); 1024 | Assert.IsNotNull(actual); 1025 | } 1026 | 1027 | [Test] 1028 | public void GetOrAddWithPolicyAndThenGetValueObjectReturnsCorrectType() 1029 | { 1030 | int Fetch() 1031 | { 1032 | return 123; 1033 | } 1034 | 1035 | sut.GetOrAdd(TestKey, Fetch, oneHourNonRemoveableMemoryCacheEntryOptions); 1036 | var actual = sut.Get(TestKey); 1037 | Assert.AreEqual(123, actual); 1038 | } 1039 | 1040 | [Test] 1041 | public void GetOrAddWithPolicyWillAddOnFirstCallButReturnCachedOnSecond() 1042 | { 1043 | var times = 0; 1044 | 1045 | 1046 | var expectedFirst = sut.GetOrAdd(TestKey, () => 1047 | { 1048 | times++; 1049 | return new DateTime(2001, 01, 01); 1050 | }, oneHourNonRemoveableMemoryCacheEntryOptions); 1051 | 1052 | var expectedSecond = sut.GetOrAdd(TestKey, () => 1053 | { 1054 | times++; 1055 | return new DateTime(2002, 01, 01); 1056 | }, oneHourNonRemoveableMemoryCacheEntryOptions); 1057 | 1058 | Assert.AreEqual(2001, expectedFirst.Year); 1059 | Assert.AreEqual(2001, expectedSecond.Year); 1060 | Assert.AreEqual(1, times); 1061 | } 1062 | 1063 | [Test] 1064 | [MaxTime(20000)] 1065 | public void GetOrAddWithPostEvictionCallbackdReturnsTheOriginalCachedObjectEvenIfNotGettedBeforehand() 1066 | { 1067 | object cacheValue = null; 1068 | var cacheEntryOptions = new MemoryCacheEntryOptions 1069 | { 1070 | AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100) 1071 | }.RegisterPostEvictionCallback((key, value, reason, state) => cacheValue = value); 1072 | sut.GetOrAdd(TestKey, () => 123, 1073 | cacheEntryOptions); 1074 | 1075 | sut.Remove(TestKey); //force removed callback to fire 1076 | while (cacheValue == null) 1077 | Thread.Sleep(500); 1078 | 1079 | cacheValue.Should().BeOfType(); 1080 | cacheValue.Should().Be(123); 1081 | } 1082 | 1083 | [Test] 1084 | public void GetWithClassTypeParamReturnsType() 1085 | { 1086 | var cached = new EventArgs(); 1087 | sut.Add(TestKey, cached); 1088 | Assert.AreEqual(cached, sut.Get(TestKey)); 1089 | } 1090 | 1091 | [Test] 1092 | public void GetWithIntRetunsDefaultIfNotCached() 1093 | { 1094 | Assert.AreEqual(default(int), sut.Get(TestKey)); 1095 | } 1096 | 1097 | [Test] 1098 | public void GetWithNullableIntRetunsCachedNonNullableInt() 1099 | { 1100 | const int expected = 123; 1101 | sut.Add(TestKey, expected); 1102 | Assert.AreEqual(expected, sut.Get(TestKey)); 1103 | } 1104 | 1105 | [Test] 1106 | public void GetWithNullableStructTypeParamReturnsType() 1107 | { 1108 | var cached = new DateTime(); 1109 | sut.Add(TestKey, cached); 1110 | Assert.AreEqual(cached, sut.Get(TestKey)); 1111 | } 1112 | 1113 | [Test] 1114 | public void GetWithStructTypeParamReturnsType() 1115 | { 1116 | var cached = new DateTime(2000, 1, 1); 1117 | sut.Add(TestKey, cached); 1118 | Assert.AreEqual(cached, sut.Get(TestKey)); 1119 | } 1120 | 1121 | [Test] 1122 | public void GetWithValueTypeParamReturnsType() 1123 | { 1124 | const int cached = 3; 1125 | sut.Add(TestKey, cached); 1126 | Assert.AreEqual(3, sut.Get(TestKey)); 1127 | } 1128 | 1129 | [Test] 1130 | public void GetWithWrongClassTypeParamReturnsNull() 1131 | { 1132 | var cached = new EventArgs(); 1133 | sut.Add(TestKey, cached); 1134 | Assert.IsNull(sut.Get(TestKey)); 1135 | } 1136 | 1137 | [Test] 1138 | public void GetWithWrongStructTypeParamReturnsNull() 1139 | { 1140 | var cached = new DateTime(); 1141 | sut.Add(TestKey, cached); 1142 | Assert.AreEqual(new TimeSpan(), sut.Get(TestKey)); 1143 | } 1144 | 1145 | [Test] 1146 | public void RemovedItemCannotBeRetrievedFromCache() 1147 | { 1148 | sut.Add(TestKey, new object()); 1149 | Assert.NotNull(sut.Get(TestKey)); 1150 | sut.Remove(TestKey); 1151 | Assert.Null(sut.Get(TestKey)); 1152 | } 1153 | 1154 | [Test] 1155 | public void TryGetReturnsCachedValueAndTrue() 1156 | { 1157 | string val = "Test Value"; 1158 | string key = "testkey"; 1159 | sut.Add(key, val); 1160 | 1161 | var contains = sut.TryGetValue(key, out var value); 1162 | 1163 | Assert.IsTrue(contains); 1164 | Assert.AreEqual(value, val); 1165 | 1166 | var contains2 = sut.TryGetValue("invalidkey", out var value2); 1167 | 1168 | Assert.IsFalse(contains2); 1169 | 1170 | Assert.Throws(() => sut.TryGetValue(key, out var value3)); 1171 | } 1172 | } 1173 | } -------------------------------------------------------------------------------- /LazyCache.UnitTests/LazyCache.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LazyCache.UnitTests/MockCachingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using LazyCache.Mocks; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using NUnit.Framework; 6 | 7 | namespace LazyCache.UnitTests 8 | { 9 | public class MockCachingServiceTests 10 | { 11 | private const string CacheKey = "cacheKey"; 12 | private const string FunctionReturnValue = "someValue"; 13 | 14 | [Test] 15 | public void GetOrAddCalledWithCacheKeyAndFunction_FunctionResultReturned() 16 | { 17 | var sut = new MockCachingService(); 18 | 19 | var result = sut.GetOrAdd(CacheKey, () => FunctionReturnValue); 20 | 21 | result.Should().Be(FunctionReturnValue); 22 | } 23 | 24 | [Test] 25 | public void GetOrAddCalledWithCacheKeyAndFunctionWithCacheEntry_FunctionResultReturned() 26 | { 27 | var sut = new MockCachingService(); 28 | 29 | var result = sut.GetOrAdd(CacheKey, cacheEntry => FunctionReturnValue); 30 | 31 | result.Should().Be(FunctionReturnValue); 32 | } 33 | 34 | [Test] 35 | public void GetOrAddCalledWithCacheKeyAndFunctionWithDateTimeOffSet_FunctionResultReturned() 36 | { 37 | var sut = new MockCachingService(); 38 | 39 | var result = sut.GetOrAdd(CacheKey, () => FunctionReturnValue, DateTimeOffset.MinValue); 40 | 41 | result.Should().Be(FunctionReturnValue); 42 | } 43 | 44 | [Test] 45 | public void GetOrAddCalledWithCacheKeyAndFunctionWithMemoryCacheEntryOptions_FunctionResultReturned() 46 | { 47 | var sut = new MockCachingService(); 48 | 49 | var result = sut.GetOrAdd(CacheKey, () => FunctionReturnValue, new MemoryCacheEntryOptions()); 50 | 51 | result.Should().Be(FunctionReturnValue); 52 | } 53 | 54 | [Test] 55 | public void GetOrAddCalledWithCacheKeyAndFunctionWithTimeSpan_FunctionResultReturned() 56 | { 57 | var sut = new MockCachingService(); 58 | 59 | var result = sut.GetOrAdd(CacheKey, () => FunctionReturnValue, TimeSpan.FromMilliseconds(1000)); 60 | 61 | result.Should().Be(FunctionReturnValue); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore21/CachingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using LazyCache.Providers; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using NUnit.Framework; 4 | 5 | namespace LazyCache.UnitTestsCore21 6 | { 7 | [TestFixture] 8 | public class CachingServiceTests 9 | { 10 | private static CachingService BuildCache() 11 | { 12 | return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 13 | } 14 | 15 | private IAppCache sut; 16 | 17 | 18 | private const string TestKey = "testKey"; 19 | 20 | [SetUp] 21 | public void BeforeEachTest() 22 | { 23 | sut = BuildCache(); 24 | } 25 | 26 | 27 | [Test] 28 | public void GetOrAddOnCore21ReturnsTheCachedItem() 29 | { 30 | var cachedResult = sut.GetOrAdd(TestKey, () => new {SomeProperty = "SomeValue"}); 31 | 32 | Assert.IsNotNull(cachedResult); 33 | Assert.AreEqual("SomeValue", cachedResult.SomeProperty); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore21/LazyCache.UnitTestsCore21.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore22/CachingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using LazyCache.Providers; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using NUnit.Framework; 4 | 5 | namespace LazyCache.UnitTestsCore22 6 | { 7 | [TestFixture] 8 | public class CachingServiceTests 9 | { 10 | private static CachingService BuildCache() 11 | { 12 | return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 13 | } 14 | 15 | private IAppCache sut; 16 | 17 | 18 | private const string TestKey = "testKey"; 19 | 20 | [SetUp] 21 | public void BeforeEachTest() 22 | { 23 | sut = BuildCache(); 24 | } 25 | 26 | 27 | [Test] 28 | public void GetOrAddOnCore22ReturnsTheCachedItem() 29 | { 30 | var cachedResult = sut.GetOrAdd(TestKey, () => new {SomeProperty = "SomeValue"}); 31 | 32 | Assert.IsNotNull(cachedResult); 33 | Assert.AreEqual("SomeValue", cachedResult.SomeProperty); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore22/LazyCache.UnitTestsCore22.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore30/CachingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using LazyCache.Providers; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using NUnit.Framework; 4 | 5 | namespace LazyCache.UnitTestsCore30 6 | { 7 | [TestFixture] 8 | public class CachingServiceTests 9 | { 10 | private static CachingService BuildCache() 11 | { 12 | return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 13 | } 14 | 15 | private IAppCache sut; 16 | 17 | 18 | private const string TestKey = "testKey"; 19 | 20 | [SetUp] 21 | public void BeforeEachTest() 22 | { 23 | sut = BuildCache(); 24 | } 25 | 26 | 27 | [Test] 28 | public void GetOrAddOnCore30ReturnsTheCachedItem() 29 | { 30 | var cachedResult = sut.GetOrAdd(TestKey, () => new {SomeProperty = "SomeValue"}); 31 | 32 | Assert.IsNotNull(cachedResult); 33 | Assert.AreEqual("SomeValue", cachedResult.SomeProperty); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore30/LazyCache.UnitTestsCore30.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore31/CachingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using LazyCache.Providers; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using NUnit.Framework; 4 | 5 | namespace LazyCache.UnitTestsCore31 6 | { 7 | [TestFixture] 8 | public class CachingServiceTests 9 | { 10 | private static CachingService BuildCache() 11 | { 12 | return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 13 | } 14 | 15 | private IAppCache sut; 16 | 17 | 18 | private const string TestKey = "testKey"; 19 | 20 | [SetUp] 21 | public void BeforeEachTest() 22 | { 23 | sut = BuildCache(); 24 | } 25 | 26 | 27 | [Test] 28 | public void GetOrAddOnCore31ReturnsTheCachedItem() 29 | { 30 | var cachedResult = sut.GetOrAdd(TestKey, () => new {SomeProperty = "SomeValue"}); 31 | 32 | Assert.IsNotNull(cachedResult); 33 | Assert.AreEqual("SomeValue", cachedResult.SomeProperty); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LazyCache.UnitTestsCore31/LazyCache.UnitTestsCore31.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LazyCache.UnitTestsNet50/CachingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using LazyCache.Providers; 2 | using Microsoft.Extensions.Caching.Memory; 3 | using NUnit.Framework; 4 | 5 | namespace LazyCache.UnitTestsNet50 6 | { 7 | [TestFixture] 8 | public class CachingServiceTests 9 | { 10 | private static CachingService BuildCache() 11 | { 12 | return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); 13 | } 14 | 15 | private IAppCache sut; 16 | 17 | 18 | private const string TestKey = "testKey"; 19 | 20 | [SetUp] 21 | public void BeforeEachTest() 22 | { 23 | sut = BuildCache(); 24 | } 25 | 26 | 27 | [Test] 28 | public void GetOrAddOnNet50ReturnsTheCachedItem() 29 | { 30 | var cachedResult = sut.GetOrAdd(TestKey, () => new {SomeProperty = "SomeValue"}); 31 | 32 | Assert.IsNotNull(cachedResult); 33 | Assert.AreEqual("SomeValue", cachedResult.SomeProperty); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LazyCache.UnitTestsNet50/LazyCache.UnitTestsNet50.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LazyCache.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache", "LazyCache\LazyCache.csproj", "{E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.UnitTests", "LazyCache.UnitTests\LazyCache.UnitTests.csproj", "{7F6C8799-CA9E-43BD-AE97-72C31BB4E106}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "info", "info", "{81C0E096-59B7-4129-851B-8183FDB9B02B}" 11 | ProjectSection(SolutionItems) = preProject 12 | appveyor.yml = appveyor.yml 13 | build.ps1 = build.ps1 14 | Readme.md = Readme.md 15 | ReleaseNotes.md = ReleaseNotes.md 16 | EndProjectSection 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{335BA426-C839-4996-8476-F3EE4056C40E}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheDatabaseQueriesApiSample", "CacheDatabaseQueriesApiSample\CacheDatabaseQueriesApiSample.csproj", "{5D6A88DD-230C-4057-B8EB-A987FF4F29DB}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.AspNetCore", "LazyCache.AspNetCore\LazyCache.AspNetCore.csproj", "{A7B07002-29F5-4463-8CA7-097C337337A1}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console.Net461", "Console.Net461\Console.Net461.csproj", "{32142F20-DFCE-4DF0-A263-093111E5A3FA}" 25 | EndProject 26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.Ninject", "LazyCache.Ninject\LazyCache.Ninject.csproj", "{6FF349C3-D20C-493C-87A3-5A193538FE13}" 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.Ninject.UnitTests", "LazyCache.Ninject.UnitTests\LazyCache.Ninject.UnitTests.csproj", "{05FDA7C8-42B4-4B07-B148-EC5EFB98055B}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.UnitTestsCore21", "LazyCache.UnitTestsCore21\LazyCache.UnitTestsCore21.csproj", "{A9092A92-0EA4-42DE-9522-8F86BA1E603E}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.UnitTestsCore22", "LazyCache.UnitTestsCore22\LazyCache.UnitTestsCore22.csproj", "{69E47208-10AA-471D-AC26-AA95FF9EEA2D}" 33 | EndProject 34 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.UnitTestsCore30", "LazyCache.UnitTestsCore30\LazyCache.UnitTestsCore30.csproj", "{8E1FEC4E-BE54-48AE-8C87-8A718BE1E3E2}" 35 | EndProject 36 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.UnitTestsCore31", "LazyCache.UnitTestsCore31\LazyCache.UnitTestsCore31.csproj", "{2E025606-884D-4C48-8490-99EB1EA7B268}" 37 | EndProject 38 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.Benchmarks", "LazyCache.Benchmarks\LazyCache.Benchmarks.csproj", "{CE7DF61F-03B2-493E-8BFF-6E744015DE14}" 39 | EndProject 40 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.UnitTestsNet50", "LazyCache.UnitTestsNet50\LazyCache.UnitTestsNet50.csproj", "{735910E0-E533-4D8B-91AC-6CA7415DEE0A}" 41 | EndProject 42 | Global 43 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 44 | Debug|Any CPU = Debug|Any CPU 45 | Release|Any CPU = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 48 | {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {7F6C8799-CA9E-43BD-AE97-72C31BB4E106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {7F6C8799-CA9E-43BD-AE97-72C31BB4E106}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {7F6C8799-CA9E-43BD-AE97-72C31BB4E106}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {7F6C8799-CA9E-43BD-AE97-72C31BB4E106}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {A7B07002-29F5-4463-8CA7-097C337337A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {A7B07002-29F5-4463-8CA7-097C337337A1}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {A7B07002-29F5-4463-8CA7-097C337337A1}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {A7B07002-29F5-4463-8CA7-097C337337A1}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {6FF349C3-D20C-493C-87A3-5A193538FE13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {6FF349C3-D20C-493C-87A3-5A193538FE13}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {6FF349C3-D20C-493C-87A3-5A193538FE13}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {6FF349C3-D20C-493C-87A3-5A193538FE13}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {05FDA7C8-42B4-4B07-B148-EC5EFB98055B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {05FDA7C8-42B4-4B07-B148-EC5EFB98055B}.Debug|Any CPU.Build.0 = Debug|Any CPU 74 | {05FDA7C8-42B4-4B07-B148-EC5EFB98055B}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {05FDA7C8-42B4-4B07-B148-EC5EFB98055B}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {A9092A92-0EA4-42DE-9522-8F86BA1E603E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {A9092A92-0EA4-42DE-9522-8F86BA1E603E}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {A9092A92-0EA4-42DE-9522-8F86BA1E603E}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {A9092A92-0EA4-42DE-9522-8F86BA1E603E}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {69E47208-10AA-471D-AC26-AA95FF9EEA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {69E47208-10AA-471D-AC26-AA95FF9EEA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {69E47208-10AA-471D-AC26-AA95FF9EEA2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {69E47208-10AA-471D-AC26-AA95FF9EEA2D}.Release|Any CPU.Build.0 = Release|Any CPU 84 | {8E1FEC4E-BE54-48AE-8C87-8A718BE1E3E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 85 | {8E1FEC4E-BE54-48AE-8C87-8A718BE1E3E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 86 | {8E1FEC4E-BE54-48AE-8C87-8A718BE1E3E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 87 | {8E1FEC4E-BE54-48AE-8C87-8A718BE1E3E2}.Release|Any CPU.Build.0 = Release|Any CPU 88 | {2E025606-884D-4C48-8490-99EB1EA7B268}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 89 | {2E025606-884D-4C48-8490-99EB1EA7B268}.Debug|Any CPU.Build.0 = Debug|Any CPU 90 | {2E025606-884D-4C48-8490-99EB1EA7B268}.Release|Any CPU.ActiveCfg = Release|Any CPU 91 | {2E025606-884D-4C48-8490-99EB1EA7B268}.Release|Any CPU.Build.0 = Release|Any CPU 92 | {CE7DF61F-03B2-493E-8BFF-6E744015DE14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 93 | {CE7DF61F-03B2-493E-8BFF-6E744015DE14}.Debug|Any CPU.Build.0 = Debug|Any CPU 94 | {CE7DF61F-03B2-493E-8BFF-6E744015DE14}.Release|Any CPU.ActiveCfg = Release|Any CPU 95 | {CE7DF61F-03B2-493E-8BFF-6E744015DE14}.Release|Any CPU.Build.0 = Release|Any CPU 96 | {735910E0-E533-4D8B-91AC-6CA7415DEE0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 97 | {735910E0-E533-4D8B-91AC-6CA7415DEE0A}.Debug|Any CPU.Build.0 = Debug|Any CPU 98 | {735910E0-E533-4D8B-91AC-6CA7415DEE0A}.Release|Any CPU.ActiveCfg = Release|Any CPU 99 | {735910E0-E533-4D8B-91AC-6CA7415DEE0A}.Release|Any CPU.Build.0 = Release|Any CPU 100 | EndGlobalSection 101 | GlobalSection(SolutionProperties) = preSolution 102 | HideSolutionNode = FALSE 103 | EndGlobalSection 104 | GlobalSection(NestedProjects) = preSolution 105 | {5D6A88DD-230C-4057-B8EB-A987FF4F29DB} = {335BA426-C839-4996-8476-F3EE4056C40E} 106 | {32142F20-DFCE-4DF0-A263-093111E5A3FA} = {335BA426-C839-4996-8476-F3EE4056C40E} 107 | EndGlobalSection 108 | GlobalSection(ExtensibilityGlobals) = postSolution 109 | SolutionGuid = {5040E431-0FAA-4DC7-A678-D218CD57D542} 110 | EndGlobalSection 111 | EndGlobal 112 | -------------------------------------------------------------------------------- /LazyCache/AppCacheExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace LazyCache 8 | { 9 | public static class AppCacheExtensions 10 | { 11 | public static void Add(this IAppCache cache, string key, T item) 12 | { 13 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 14 | 15 | cache.Add(key, item, cache.DefaultCachePolicy.BuildOptions()); 16 | } 17 | 18 | public static void Add(this IAppCache cache, string key, T item, DateTimeOffset expires) 19 | { 20 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 21 | 22 | cache.Add(key, item, new MemoryCacheEntryOptions {AbsoluteExpiration = expires}); 23 | } 24 | 25 | public static void Add(this IAppCache cache, string key, T item, TimeSpan slidingExpiration) 26 | { 27 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 28 | 29 | cache.Add(key, item, new MemoryCacheEntryOptions {SlidingExpiration = slidingExpiration}); 30 | } 31 | 32 | public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory) 33 | { 34 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 35 | 36 | return cache.GetOrAdd(key, addItemFactory, cache.DefaultCachePolicy.BuildOptions()); 37 | } 38 | 39 | public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory, DateTimeOffset expires) 40 | { 41 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 42 | 43 | return cache.GetOrAdd(key, addItemFactory, new MemoryCacheEntryOptions {AbsoluteExpiration = expires}); 44 | } 45 | 46 | public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory, DateTimeOffset expires, ExpirationMode mode) 47 | { 48 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 49 | 50 | switch (mode) 51 | { 52 | case ExpirationMode.LazyExpiration: 53 | return cache.GetOrAdd(key, addItemFactory, new MemoryCacheEntryOptions { AbsoluteExpiration = expires }); 54 | default: 55 | return cache.GetOrAdd(key, addItemFactory, new LazyCacheEntryOptions().SetAbsoluteExpiration(expires, mode)); 56 | } 57 | } 58 | 59 | public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory, 60 | TimeSpan slidingExpiration) 61 | { 62 | return cache.GetOrAdd(key, addItemFactory, 63 | new MemoryCacheEntryOptions {SlidingExpiration = slidingExpiration}); 64 | } 65 | 66 | public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory, 67 | MemoryCacheEntryOptions policy) 68 | { 69 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 70 | 71 | return cache.GetOrAdd(key, _=> addItemFactory(), policy); 72 | } 73 | 74 | public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory) 75 | { 76 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 77 | 78 | return cache.GetOrAddAsync(key, addItemFactory, cache.DefaultCachePolicy.BuildOptions()); 79 | } 80 | 81 | public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory, 82 | DateTimeOffset expires) 83 | { 84 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 85 | 86 | return cache.GetOrAddAsync(key, addItemFactory, new MemoryCacheEntryOptions {AbsoluteExpiration = expires}); 87 | } 88 | 89 | public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory, 90 | DateTimeOffset expires, ExpirationMode mode) 91 | { 92 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 93 | 94 | switch (mode) 95 | { 96 | case ExpirationMode.LazyExpiration: 97 | return cache.GetOrAddAsync(key, addItemFactory, new MemoryCacheEntryOptions { AbsoluteExpiration = expires }); 98 | default: 99 | return cache.GetOrAddAsync(key, addItemFactory, new LazyCacheEntryOptions().SetAbsoluteExpiration(expires, mode)); 100 | } 101 | } 102 | 103 | public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory, 104 | TimeSpan slidingExpiration) 105 | { 106 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 107 | 108 | return cache.GetOrAddAsync(key, addItemFactory, 109 | new MemoryCacheEntryOptions {SlidingExpiration = slidingExpiration}); 110 | } 111 | 112 | public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory, 113 | MemoryCacheEntryOptions policy) 114 | { 115 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 116 | 117 | return cache.GetOrAddAsync(key, _=> addItemFactory(), policy); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /LazyCache/AsyncLazy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading.Tasks; 4 | 5 | namespace LazyCache 6 | { 7 | /// 8 | /// See https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/ 9 | /// 10 | /// 11 | public class AsyncLazy : Lazy> 12 | { 13 | public AsyncLazy(Func valueFactory) : 14 | base(() => Task.Factory.StartNew(valueFactory)) 15 | { 16 | } 17 | 18 | public AsyncLazy(Func> taskFactory) : 19 | base(() => Task.Factory.StartNew(taskFactory).Unwrap()) 20 | { 21 | } 22 | 23 | public TaskAwaiter GetAwaiter() 24 | { 25 | return Value.GetAwaiter(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /LazyCache/CacheDefaults.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Caching.Memory; 3 | 4 | namespace LazyCache 5 | { 6 | // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global 7 | public class CacheDefaults 8 | { 9 | public virtual int DefaultCacheDurationSeconds { get; set; } = 60 * 20; 10 | 11 | internal MemoryCacheEntryOptions BuildOptions() 12 | { 13 | return new MemoryCacheEntryOptions 14 | { 15 | AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(DefaultCacheDurationSeconds) 16 | }; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /LazyCache/CacheItemPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace LazyCache 6 | { 7 | [Obsolete( 8 | "CacheItemPolicy was part of System.Runtime.Caching which is no longer used by LazyCache. " + 9 | "This class is a fake used to maintain backward compatibility and will be removed in a later version." + 10 | "Change to MemoryCacheEntryOptions instead")] 11 | public class CacheItemPolicy : MemoryCacheEntryOptions 12 | { 13 | public PostEvictionCallbackRegistration RemovedCallback => PostEvictionCallbacks.FirstOrDefault(); 14 | } 15 | } -------------------------------------------------------------------------------- /LazyCache/CachingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using LazyCache.Providers; 6 | using Microsoft.Extensions.Caching.Memory; 7 | 8 | namespace LazyCache 9 | { 10 | // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global 11 | public class CachingService : IAppCache 12 | { 13 | private readonly Lazy cacheProvider; 14 | 15 | private readonly int[] keyLocks; 16 | 17 | public CachingService() : this(DefaultCacheProvider) 18 | { 19 | } 20 | 21 | public CachingService(Lazy cacheProvider) 22 | { 23 | this.cacheProvider = cacheProvider ?? throw new ArgumentNullException(nameof(cacheProvider)); 24 | var lockCount = Math.Max(Environment.ProcessorCount * 8, 32); 25 | keyLocks = new int[lockCount]; 26 | } 27 | 28 | public CachingService(Func cacheProviderFactory) 29 | { 30 | if (cacheProviderFactory == null) throw new ArgumentNullException(nameof(cacheProviderFactory)); 31 | cacheProvider = new Lazy(cacheProviderFactory); 32 | var lockCount = Math.Max(Environment.ProcessorCount * 8, 32); 33 | keyLocks = new int[lockCount]; 34 | 35 | } 36 | 37 | public CachingService(ICacheProvider cache) : this(() => cache) 38 | { 39 | if (cache == null) throw new ArgumentNullException(nameof(cache)); 40 | } 41 | 42 | public static Lazy DefaultCacheProvider { get; set; } 43 | = new Lazy(() => 44 | new MemoryCacheProvider( 45 | new MemoryCache( 46 | new MemoryCacheOptions()) 47 | )); 48 | 49 | /// 50 | /// Seconds to cache objects for by default 51 | /// 52 | [Obsolete("DefaultCacheDuration has been replaced with DefaultCacheDurationSeconds")] 53 | public virtual int DefaultCacheDuration 54 | { 55 | get => DefaultCachePolicy.DefaultCacheDurationSeconds; 56 | set => DefaultCachePolicy.DefaultCacheDurationSeconds = value; 57 | } 58 | 59 | /// 60 | /// Policy defining how long items should be cached for unless specified 61 | /// 62 | public virtual CacheDefaults DefaultCachePolicy { get; set; } = new CacheDefaults(); 63 | 64 | public virtual void Add(string key, T item, MemoryCacheEntryOptions policy) 65 | { 66 | if (item == null) 67 | throw new ArgumentNullException(nameof(item)); 68 | ValidateKey(key); 69 | 70 | CacheProvider.Set(key, item, policy); 71 | } 72 | 73 | public virtual T Get(string key) 74 | { 75 | ValidateKey(key); 76 | 77 | var item = CacheProvider.Get(key); 78 | 79 | return GetValueFromLazy(item, out _); 80 | } 81 | 82 | public virtual Task GetAsync(string key) 83 | { 84 | ValidateKey(key); 85 | 86 | var item = CacheProvider.Get(key); 87 | 88 | return GetValueFromAsyncLazy(item, out _); 89 | } 90 | 91 | public virtual bool TryGetValue(string key, out T value) 92 | { 93 | ValidateKey(key); 94 | 95 | return CacheProvider.TryGetValue(key, out value); 96 | } 97 | 98 | public virtual T GetOrAdd(string key, Func addItemFactory) 99 | { 100 | return GetOrAdd(key, addItemFactory, null); 101 | } 102 | 103 | public virtual T GetOrAdd(string key, Func addItemFactory, MemoryCacheEntryOptions policy) 104 | { 105 | ValidateKey(key); 106 | 107 | object cacheItem; 108 | 109 | object CacheFactory(ICacheEntry entry) => 110 | new Lazy(() => 111 | { 112 | var result = addItemFactory(entry); 113 | SetAbsoluteExpirationFromRelative(entry); 114 | EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy(entry.PostEvictionCallbacks); 115 | return result; 116 | }); 117 | 118 | // acquire lock per key 119 | uint hash = (uint)key.GetHashCode() % (uint)keyLocks.Length; 120 | while (Interlocked.CompareExchange(ref keyLocks[hash], 1, 0) == 1) { Thread.Yield(); } 121 | 122 | try 123 | { 124 | cacheItem = CacheProvider.GetOrCreate(key, policy, CacheFactory); 125 | } 126 | finally 127 | { 128 | keyLocks[hash] = 0; 129 | } 130 | 131 | try 132 | { 133 | var result = GetValueFromLazy(cacheItem, out var valueHasChangedType); 134 | 135 | // if we get a cache hit but for something with the wrong type we need to evict it, start again and cache the new item instead 136 | if (valueHasChangedType) 137 | { 138 | CacheProvider.Remove(key); 139 | 140 | // acquire lock again 141 | hash = (uint)key.GetHashCode() % (uint)keyLocks.Length; 142 | while (Interlocked.CompareExchange(ref keyLocks[hash], 1, 0) == 1) { Thread.Yield(); } 143 | 144 | try 145 | { 146 | cacheItem = CacheProvider.GetOrCreate(key, CacheFactory); 147 | } 148 | finally 149 | { 150 | keyLocks[hash] = 0; 151 | } 152 | result = GetValueFromLazy(cacheItem, out _ /* we just evicted so type change cannot happen this time */); 153 | } 154 | 155 | return result; 156 | } 157 | catch //addItemFactory errored so do not cache the exception 158 | { 159 | CacheProvider.Remove(key); 160 | throw; 161 | } 162 | } 163 | 164 | private static void SetAbsoluteExpirationFromRelative(ICacheEntry entry) 165 | { 166 | if (!entry.AbsoluteExpirationRelativeToNow.HasValue) return; 167 | 168 | var absoluteExpiration = DateTimeOffset.UtcNow + entry.AbsoluteExpirationRelativeToNow.Value; 169 | if (!entry.AbsoluteExpiration.HasValue || absoluteExpiration < entry.AbsoluteExpiration) 170 | entry.AbsoluteExpiration = absoluteExpiration; 171 | } 172 | 173 | public virtual void Remove(string key) 174 | { 175 | ValidateKey(key); 176 | CacheProvider.Remove(key); 177 | } 178 | 179 | public virtual ICacheProvider CacheProvider => cacheProvider.Value; 180 | 181 | public virtual Task GetOrAddAsync(string key, Func> addItemFactory) 182 | { 183 | return GetOrAddAsync(key, addItemFactory, null); 184 | } 185 | 186 | public virtual async Task GetOrAddAsync(string key, Func> addItemFactory, 187 | MemoryCacheEntryOptions policy) 188 | { 189 | ValidateKey(key); 190 | 191 | object cacheItem; 192 | 193 | // Ensure only one thread can place an item into the cache provider at a time. 194 | // We are not evaluating the addItemFactory inside here - that happens outside the lock, 195 | // below, and guarded using the async lazy. Here we just ensure only one thread can place 196 | // the AsyncLazy into the cache at one time 197 | 198 | // acquire lock 199 | uint hash = (uint)key.GetHashCode() % (uint)keyLocks.Length; 200 | while (Interlocked.CompareExchange(ref keyLocks[hash], 1, 0) == 1) { Thread.Yield(); } 201 | 202 | object CacheFactory(ICacheEntry entry) => 203 | new AsyncLazy(async () => 204 | { 205 | var result = await addItemFactory(entry).ConfigureAwait(false); 206 | SetAbsoluteExpirationFromRelative(entry); 207 | EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy(entry.PostEvictionCallbacks); 208 | return result; 209 | }); 210 | 211 | try 212 | { 213 | cacheItem = CacheProvider.GetOrCreate(key, policy, CacheFactory); 214 | } 215 | finally 216 | { 217 | keyLocks[hash] = 0; 218 | } 219 | 220 | try 221 | { 222 | var result = GetValueFromAsyncLazy(cacheItem, out var valueHasChangedType); 223 | 224 | // if we get a cache hit but for something with the wrong type we need to evict it, start again and cache the new item instead 225 | if (valueHasChangedType) 226 | { 227 | CacheProvider.Remove(key); 228 | 229 | // acquire lock 230 | hash = (uint)key.GetHashCode() % (uint)keyLocks.Length; 231 | while (Interlocked.CompareExchange(ref keyLocks[hash], 1, 0) == 1) { Thread.Yield(); } 232 | 233 | try 234 | { 235 | cacheItem = CacheProvider.GetOrCreate(key, CacheFactory); 236 | } 237 | finally 238 | { 239 | keyLocks[hash] = 0; 240 | } 241 | result = GetValueFromAsyncLazy(cacheItem, out _ /* we just evicted so type change cannot happen this time */); 242 | } 243 | 244 | if (result.IsCanceled || result.IsFaulted) 245 | CacheProvider.Remove(key); 246 | 247 | return await result.ConfigureAwait(false); 248 | } 249 | catch //addItemFactory errored so do not cache the exception 250 | { 251 | CacheProvider.Remove(key); 252 | throw; 253 | } 254 | } 255 | 256 | protected virtual T GetValueFromLazy(object item, out bool valueHasChangedType) 257 | { 258 | valueHasChangedType = false; 259 | switch (item) 260 | { 261 | case Lazy lazy: 262 | return lazy.Value; 263 | case T variable: 264 | return variable; 265 | case AsyncLazy asyncLazy: 266 | // this is async to sync - and should not really happen as long as GetOrAddAsync is used for an async 267 | // value. Only happens when you cache something async and then try and grab it again later using 268 | // the non async methods. 269 | return asyncLazy.Value.ConfigureAwait(false).GetAwaiter().GetResult(); 270 | case Task task: 271 | return task.Result; 272 | } 273 | 274 | // if they have cached something else with the same key we need to tell caller to reset the cached item 275 | // although this is probably not the fastest this should not get called on the main use case 276 | // where you just hit the first switch case above. 277 | var itemsType = item?.GetType(); 278 | if (itemsType != null && itemsType.IsGenericType && itemsType.GetGenericTypeDefinition() == typeof(Lazy<>)) 279 | { 280 | valueHasChangedType = true; 281 | } 282 | 283 | return default(T); 284 | } 285 | 286 | protected virtual Task GetValueFromAsyncLazy(object item, out bool valueHasChangedType) 287 | { 288 | valueHasChangedType = false; 289 | switch (item) 290 | { 291 | case AsyncLazy asyncLazy: 292 | return asyncLazy.Value; 293 | case Task task: 294 | return task; 295 | // this is sync to async and only happens if you cache something sync and then get it later async 296 | case Lazy lazy: 297 | return Task.FromResult(lazy.Value); 298 | case T variable: 299 | return Task.FromResult(variable); 300 | } 301 | 302 | // if they have cached something else with the same key we need to tell caller to reset the cached item 303 | // although this is probably not the fastest this should not get called on the main use case 304 | // where you just hit the first switch case above. 305 | var itemsType = item?.GetType(); 306 | if (itemsType != null && itemsType.IsGenericType && itemsType.GetGenericTypeDefinition() == typeof(AsyncLazy<>)) 307 | { 308 | valueHasChangedType = true; 309 | } 310 | 311 | return Task.FromResult(default(T)); 312 | } 313 | 314 | protected virtual void EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy( 315 | IList callbackRegistrations) 316 | { 317 | if (callbackRegistrations != null) 318 | foreach (var item in callbackRegistrations) 319 | { 320 | var originalCallback = item.EvictionCallback; 321 | item.EvictionCallback = (key, value, reason, state) => 322 | { 323 | // before the original callback we need to unwrap the Lazy that holds the cache item 324 | if (value is AsyncLazy asyncCacheItem) 325 | value = asyncCacheItem.IsValueCreated ? asyncCacheItem.Value : Task.FromResult(default(T)); 326 | else if (value is Lazy cacheItem) 327 | value = cacheItem.IsValueCreated ? cacheItem.Value : default(T); 328 | 329 | // pass the unwrapped cached value to the original callback 330 | originalCallback(key, value, reason, state); 331 | }; 332 | } 333 | } 334 | 335 | protected virtual void ValidateKey(string key) 336 | { 337 | if (key == null) 338 | throw new ArgumentNullException(nameof(key)); 339 | 340 | if (string.IsNullOrWhiteSpace(key)) 341 | throw new ArgumentOutOfRangeException(nameof(key), "Cache keys cannot be empty or whitespace"); 342 | } 343 | } 344 | } -------------------------------------------------------------------------------- /LazyCache/ExpirationMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace LazyCache 5 | { 6 | public enum ExpirationMode 7 | { 8 | /// 9 | /// This is the default for Memory cache - expired items are removed from the cache 10 | /// the next time that key is accessed. This is the most performant, and so the default, 11 | /// because no timers are required to removed expired items, but it does mean that 12 | /// PostEvictionCallbacks may fire later than expected, or not at all. 13 | /// 14 | LazyExpiration, 15 | 16 | /// 17 | /// Use a timer to force eviction of expired items from the cache as soon as they expire. 18 | /// This will then trigger PostEvictionCallbacks at the expected time. This uses more resources 19 | /// than LazyExpiration. 20 | /// 21 | [Obsolete("Use ExpirationMode.ImmediateEviction instead - this name is miss-leading")] 22 | ImmediateExpiration, 23 | 24 | /// 25 | /// Use a timer to force eviction of expired items from the cache as soon as they expire. 26 | /// This will then trigger PostEvictionCallbacks at the expected time. This uses more resources 27 | /// than LazyExpiration. 28 | /// 29 | ImmediateEviction 30 | } 31 | } -------------------------------------------------------------------------------- /LazyCache/IAppCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace LazyCache 6 | { 7 | public interface IAppCache 8 | { 9 | ICacheProvider CacheProvider { get; } 10 | 11 | /// 12 | /// Define the number of seconds to cache objects for by default 13 | /// 14 | CacheDefaults DefaultCachePolicy { get; } 15 | void Add(string key, T item, MemoryCacheEntryOptions policy); 16 | T Get(string key); 17 | Task GetAsync(string key); 18 | bool TryGetValue(string key, out T value); 19 | 20 | T GetOrAdd(string key, Func addItemFactory); 21 | T GetOrAdd(string key, Func addItemFactory, MemoryCacheEntryOptions policy); 22 | Task GetOrAddAsync(string key, Func> addItemFactory); 23 | Task GetOrAddAsync(string key, Func> addItemFactory, MemoryCacheEntryOptions policy); 24 | void Remove(string key); 25 | } 26 | } -------------------------------------------------------------------------------- /LazyCache/ICacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace LazyCache 6 | { 7 | public interface ICacheProvider : IDisposable 8 | { 9 | void Set(string key, object item, MemoryCacheEntryOptions policy); 10 | object Get(string key); 11 | object GetOrCreate(string key, Func func); 12 | object GetOrCreate(string key, MemoryCacheEntryOptions policy, Func func); 13 | void Remove(string key); 14 | Task GetOrCreateAsync(string key, Func> func); 15 | bool TryGetValue(object key, out T value); 16 | } 17 | } -------------------------------------------------------------------------------- /LazyCache/LazyCache.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | netstandard2.0 6 | true 7 | 1.0.0 8 | 9 | $(LazyCacheVersion)$(LazyCacheVersionSuffix) 10 | $(APPVEYOR_BUILD_NUMBER) 11 | 0 12 | $(LazyCacheVersion).$(AppVeyorBuildNumber) 13 | https://github.com/alastairtree 14 | https://github.com/alastairtree 15 | Lazy cache is a simple, thread safe, in-memory caching library that makes it easy to add high performance caching to your dotnet app. 16 | https://github.com/alastairtree/LazyCache 17 | Copyright 2014 - 2018 Alastair Crabtree 18 | logo-128.png 19 | https://github.com/alastairtree/LazyCache 20 | Caching Performance Speed In-memory IMemoryCache Generics ServiceCacheing Lazy Cache Lazy-Load MemoryCache CachingService AppCache ApplicationCache Memcached 21 | See https://raw.githubusercontent.com/alastairtree/LazyCache/master/ReleaseNotes.md 22 | MIT 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LazyCache/MemoryCacheEntryOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Caching.Memory; 3 | 4 | namespace LazyCache 5 | { 6 | public class LazyCacheEntryOptions : MemoryCacheEntryOptions 7 | { 8 | public ExpirationMode ExpirationMode { get; set; } 9 | public TimeSpan ImmediateAbsoluteExpirationRelativeToNow { get; set; } 10 | 11 | public static LazyCacheEntryOptions WithImmediateAbsoluteExpiration(DateTimeOffset absoluteExpiration) 12 | { 13 | var delay = absoluteExpiration.Subtract(DateTimeOffset.UtcNow); 14 | return new LazyCacheEntryOptions 15 | { 16 | AbsoluteExpiration = absoluteExpiration, 17 | ExpirationMode = ExpirationMode.ImmediateEviction, 18 | ImmediateAbsoluteExpirationRelativeToNow = delay 19 | }; 20 | } 21 | 22 | public static LazyCacheEntryOptions WithImmediateAbsoluteExpiration(TimeSpan absoluteExpiration) 23 | { 24 | return new LazyCacheEntryOptions 25 | { 26 | AbsoluteExpirationRelativeToNow = absoluteExpiration, 27 | ExpirationMode = ExpirationMode.ImmediateEviction, 28 | ImmediateAbsoluteExpirationRelativeToNow = absoluteExpiration 29 | }; 30 | } 31 | } 32 | 33 | public static class LazyCacheEntryOptionsExtension { 34 | public static LazyCacheEntryOptions SetAbsoluteExpiration(this LazyCacheEntryOptions option, DateTimeOffset absoluteExpiration, 35 | ExpirationMode mode) 36 | { 37 | if (option == null) throw new ArgumentNullException(nameof(option)); 38 | 39 | var delay = absoluteExpiration.Subtract(DateTimeOffset.UtcNow); 40 | option.AbsoluteExpiration = absoluteExpiration; 41 | option.ExpirationMode = mode; 42 | option.ImmediateAbsoluteExpirationRelativeToNow = delay; 43 | return option; 44 | } 45 | 46 | public static LazyCacheEntryOptions SetAbsoluteExpiration(this LazyCacheEntryOptions option, TimeSpan absoluteExpiration, 47 | ExpirationMode mode) 48 | { 49 | if (option == null) throw new ArgumentNullException(nameof(option)); 50 | 51 | option.AbsoluteExpirationRelativeToNow = absoluteExpiration; 52 | option.ExpirationMode = mode; 53 | option.ImmediateAbsoluteExpirationRelativeToNow = absoluteExpiration; 54 | return option; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /LazyCache/Mocks/MockCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Caching.Memory; 4 | using Microsoft.Extensions.Primitives; 5 | 6 | namespace LazyCache.Mocks 7 | { 8 | public class MockCacheEntry : ICacheEntry 9 | { 10 | public MockCacheEntry(string key) 11 | { 12 | Key = key; 13 | } 14 | 15 | public void Dispose() 16 | { 17 | } 18 | 19 | public object Key { get; } 20 | public object Value { get; set; } 21 | public DateTimeOffset? AbsoluteExpiration { get; set; } 22 | public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; } 23 | public TimeSpan? SlidingExpiration { get; set; } 24 | public IList ExpirationTokens { get; } 25 | public IList PostEvictionCallbacks { get; } 26 | public CacheItemPriority Priority { get; set; } 27 | public long? Size { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /LazyCache/Mocks/MockCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace LazyCache.Mocks 6 | { 7 | 8 | public class MockCacheProvider : ICacheProvider 9 | { 10 | public void Set(string key, object item, MemoryCacheEntryOptions policy) 11 | { 12 | } 13 | 14 | public object Get(string key) 15 | { 16 | return null; 17 | } 18 | 19 | public object GetOrCreate(string key, Func func) 20 | { 21 | return func(null); 22 | } 23 | 24 | public object GetOrCreate(string key, MemoryCacheEntryOptions policy, Func func) 25 | { 26 | return func(null); 27 | } 28 | 29 | public void Remove(string key) 30 | { 31 | } 32 | 33 | public Task GetOrCreateAsync(string key, Func> func) 34 | { 35 | return func(null); 36 | } 37 | 38 | public bool TryGetValue(object key, out T value) 39 | { 40 | throw new NotImplementedException(); 41 | } 42 | 43 | public void Dispose() 44 | { 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /LazyCache/Mocks/MockCachingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace LazyCache.Mocks 6 | { 7 | /// 8 | /// A mock implementation IAppCache that does not do any caching. 9 | /// Useful in unit tests or for feature switching to swap in a dependency to disable all caching 10 | /// 11 | public class MockCachingService : IAppCache 12 | { 13 | public ICacheProvider CacheProvider { get; } = new MockCacheProvider(); 14 | public CacheDefaults DefaultCachePolicy { get; set; } = new CacheDefaults(); 15 | 16 | public T Get(string key) 17 | { 18 | return default(T); 19 | } 20 | 21 | public T GetOrAdd(string key, Func addItemFactory) 22 | { 23 | return addItemFactory(new MockCacheEntry(key)); 24 | } 25 | 26 | public T GetOrAdd(string key, Func addItemFactory, MemoryCacheEntryOptions policy) 27 | { 28 | return addItemFactory(new MockCacheEntry(key)); 29 | } 30 | 31 | public Task GetOrAddAsync(string key, Func> addItemFactory, 32 | MemoryCacheEntryOptions policy) 33 | { 34 | return addItemFactory(new MockCacheEntry(key)); 35 | } 36 | 37 | public void Remove(string key) 38 | { 39 | } 40 | 41 | public Task GetOrAddAsync(string key, Func> addItemFactory) 42 | { 43 | return addItemFactory(new MockCacheEntry(key)); 44 | } 45 | 46 | public Task GetAsync(string key) 47 | { 48 | return Task.FromResult(default(T)); 49 | } 50 | 51 | public void Add(string key, T item, MemoryCacheEntryOptions policy) 52 | { 53 | } 54 | 55 | public bool TryGetValue(string key, out T value) 56 | { 57 | value = default(T); 58 | return true; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /LazyCache/Providers/MemoryCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Caching.Memory; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace LazyCache.Providers 8 | { 9 | public class MemoryCacheProvider : ICacheProvider 10 | { 11 | internal readonly IMemoryCache cache; 12 | 13 | public MemoryCacheProvider(IMemoryCache cache) 14 | { 15 | this.cache = cache; 16 | } 17 | 18 | public void Set(string key, object item, MemoryCacheEntryOptions policy) 19 | { 20 | cache.Set(key, item, policy); 21 | } 22 | 23 | public object Get(string key) 24 | { 25 | return cache.Get(key); 26 | } 27 | 28 | public object GetOrCreate(string key, Func factory) 29 | { 30 | return cache.GetOrCreate(key, factory); 31 | } 32 | 33 | public object GetOrCreate(string key, MemoryCacheEntryOptions policy, Func factory) 34 | { 35 | if (policy == null) 36 | return cache.GetOrCreate(key, factory); 37 | 38 | if (!cache.TryGetValue(key, out var result)) 39 | { 40 | var entry = cache.CreateEntry(key); 41 | // Set the initial options before the factory is fired so that any callbacks 42 | // that need to be wired up are still added. 43 | entry.SetOptions(policy); 44 | 45 | if (policy is LazyCacheEntryOptions lazyPolicy && lazyPolicy.ExpirationMode != ExpirationMode.LazyExpiration) 46 | { 47 | var expiryTokenSource = new CancellationTokenSource(); 48 | var expireToken = new CancellationChangeToken(expiryTokenSource.Token); 49 | entry.AddExpirationToken(expireToken); 50 | entry.RegisterPostEvictionCallback((keyPost, value, reason, state) => 51 | expiryTokenSource.Dispose()); 52 | 53 | result = factory(entry); 54 | 55 | expiryTokenSource.CancelAfter(lazyPolicy.ImmediateAbsoluteExpirationRelativeToNow); 56 | } 57 | else 58 | { 59 | result = factory(entry); 60 | } 61 | entry.SetValue(result); 62 | // need to manually call dispose instead of having a using 63 | // in case the factory passed in throws, in which case we 64 | // do not want to add the entry to the cache 65 | entry.Dispose(); 66 | } 67 | 68 | return (T)result; 69 | } 70 | 71 | public void Remove(string key) 72 | { 73 | cache.Remove(key); 74 | } 75 | 76 | public Task GetOrCreateAsync(string key, Func> factory) 77 | { 78 | return cache.GetOrCreateAsync(key, factory); 79 | } 80 | 81 | public bool TryGetValue(object key, out T value) 82 | { 83 | return cache.TryGetValue(key, out value); 84 | } 85 | 86 | 87 | public void Dispose() 88 | { 89 | cache?.Dispose(); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Lazy Cache # 4 | 5 | [![Build status](https://ci.appveyor.com/api/projects/status/oca98pp4safs4vj2/branch/master?svg=true)](https://ci.appveyor.com/project/alastairtree/lazycache/branch/master) 6 | ![AppVeyor tests](https://img.shields.io/appveyor/tests/alastairtree/lazycache.svg) 7 | [![NuGet](https://img.shields.io/nuget/v/LazyCache.svg)](https://www.nuget.org/packages/LazyCache/) 8 | ![Nuget](https://img.shields.io/nuget/dt/LazyCache.svg) 9 | 10 | 11 | Lazy cache is a simple in-memory caching service. It has a developer friendly 12 | generics based API, and provides a thread safe cache implementation that 13 | guarantees to only execute your cachable delegates once (it's lazy!). Under 14 | the hood it leverages Microsoft.Extensions.Caching and Lazy to provide performance and 15 | reliability in heavy load scenarios. 16 | 17 | ## Download ## 18 | 19 | LazyCache is available using [nuget](https://www.nuget.org/packages/LazyCache/). To install LazyCache, run the following command in the [Package Manager Console](http://docs.nuget.org/docs/start-here/using-the-package-manager-console) 20 | 21 | ```Powershell 22 | PM> Install-Package LazyCache 23 | ``` 24 | 25 | ## Quick start 26 | 27 | See the [quick start wiki](https://github.com/alastairtree/LazyCache/wiki/Quickstart) 28 | 29 | ## Sample code 30 | 31 | ```csharp 32 | // Create our cache service using the defaults (Dependency injection ready). 33 | // By default it uses a single shared cache under the hood so cache is shared out of the box (but you can configure this) 34 | IAppCache cache = new CachingService(); 35 | 36 | // Declare (but don't execute) a func/delegate whose result we want to cache 37 | Func complexObjectFactory = () => methodThatTakesTimeOrResources(); 38 | 39 | // Get our ComplexObjects from the cache, or build them in the factory func 40 | // and cache the results for next time under the given key 41 | ComplexObjects cachedResults = cache.GetOrAdd("uniqueKey", complexObjectFactory); 42 | ``` 43 | 44 | As you can see the magic happens in the `GetOrAdd()` method which gives the consumer an atomic and tidy way to add caching to your code. It leverages a factory delegate `Func` and generics to make it easy to add cached method calls to your app. 45 | 46 | It means you avoid the usual "Check the cache - execute the factory function - add results to the cache" pattern, saves you writing the double locking cache pattern and means you can be a lazy developer! 47 | 48 | ## What should I use it for? 49 | 50 | LazyCache suits the caching of database calls, complex object graph building routines and web service calls that should be cached for performance. 51 | Allows items to be cached for long or short periods, but defaults to 20 mins. 52 | 53 | ## .Net framework and dotnet core support? 54 | 55 | The latest version targets netstandard 2.0. See [.net standard implementation support](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support) 56 | 57 | For dotnet core 2, .net framwork net461 or above, netstandard 2+, use LazyCache 2 or above. 58 | 59 | For .net framework without netstandard 2 support such as net45 net451 net46 use LazyCache 0.7 - 1.x 60 | 61 | For .net framework 4.0 use LazyCache 0.6 62 | 63 | 64 | ## Features ## 65 | 66 | - Simple API with familiar sliding or absolute expiration 67 | - Guaranteed single evaluation of your factory delegate whose results you want to cache 68 | - Strongly typed generics based API. No need to cast your cached objects every time you retrieve them 69 | - Stops you inadvertently caching an exception by removing Lazys that evaluate to an exception 70 | - Thread safe, concurrency ready 71 | - Async compatible - lazy single evaluation of async delegates using `GetOrAddAsync()` 72 | - Interface based API and built in `MockCache` to support test driven development and dependency injection 73 | - Leverages a provider model on top of IMemoryCache under the hood and can be extended with your own implementation 74 | - Good test coverage 75 | 76 | ## Documentation 77 | 78 | * [The wiki](https://github.com/alastairtree/LazyCache/wiki) 79 | * [Adding caching to a .net application and make it faster](https://alastaircrabtree.com/the-easy-way-to-add-caching-to-net-application-and-make-it-faster-is-called-lazycache/) 80 | 81 | ## Sample Application 82 | 83 | See [CacheDatabaseQueriesApiSample](/CacheDatabaseQueriesApiSample) for an example of how to use LazyCache to cache the results of an Entity framework query in 84 | a web api controller. Watch how the cache saves trips to the database and results are returned to the client far quicker from the 85 | in-memory cache 86 | 87 | ## Contributing 88 | 89 | If you have an idea or want to fix an issue please open an issue on Github to discuss it and it will be considered. 90 | 91 | If you have code to share you should submit a pull request: fork the repo, then create a branch on that repo with your changes, when you are happy create a pull Request from your branch into LazyCache master for review. See https://help.github.com/en/articles/creating-a-pull-request-from-a-fork. 92 | 93 | LazyCache is narrow in focus and well established so unlikely to accept massive changes out of nowhere but come talk about on GitHub and we can all collaborate on something that works for everyone. It is also quite extensible so you may be able to extend it in your project or add a companion library if necessary. 94 | 95 | 96 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | # Release notes for LazyCache # 2 | 3 | ## Version 2.4.0 4 | - Bug fix for generic type - see #163. Thanks @gamblen 5 | - Bug fix for async wait and abs expiry. Thanks @allanrodriguez 6 | - (Very minor API change that is compiler compatible, but not binary compatible, so incrementing the minor.) 7 | 8 | ## Version 2.1.3 9 | - Rename ExpirationMode.ImmediateExpiry => ExpirationMode.ImmediateEviction 10 | - Lovely new logo! (#133) Thanks to @doolali 11 | - Performance imporvements to reduce allocations in #134 - thanks @jnyrup 12 | 13 | ## Version 2.1.2 14 | - Tweak key lock array size based on CPU count so larger for bigger machines (See PR #126 and discussion with @jjxtra) 15 | 16 | ## Version 2.1.1 17 | - PR #126 (and issue #104) optimise cache key locking for faster performance under high CPU load and lots of concurrency. Thanks to @jjxtra 18 | 19 | ## Version 2.1.0 20 | - Add options for expiration: 21 | - ExpirationMode.ImmediateExpiration which uses a timer to remove items from the cache as soon as they expire (more resource intensive) 22 | - ExpirationMode.LazyExpiration (existing default) which removes expired cache items when they are next accessed if they have expired. 23 | - Fix #96 AddExpirationToken with CancellationChangeToken is not being honored 24 | - Allow callers to pass MemoryCacheEntryOptions that is used at cache insertion time. This allows users to wire up callbacks and expiration tokens that fire at the correct time 25 | 26 | ## Version 2.0.5 27 | - Fix #85 and #100 Absolute expiration not working with TimeSpan. Thank you to @Meberem and @Sinhk. 28 | - Fix #124 casing for PackageReference - by @jnyrup 29 | 30 | ## Version 2.0.4 31 | - Fix #82 Make constructor resolution specific when using aspnet core dependency injection 32 | 33 | ## Version 2.0.3 34 | - Fix #46 GetOrAdd and GetOrAddAsync returning nulll if there is already an item with the same key but different type in the cache (PR #106) 35 | 36 | ## Version 2.0.2 37 | - Fix #71. Downgrade framework references to core framework 2.1 (not 2.2) since that is the current LTS release (PR #105) 38 | 39 | ## Version 2.0.1 40 | - Add Ninject adapter (PR #61) 41 | 42 | ## Version 2.0.0 43 | - *BREAKING CHANGE* Upgrade to netstandard2.0 44 | - *BREAKING CHANGE* Change underlying cache from System.Runtime.Caching to Microsft.Extension.Caching.Memory 45 | - *BREAKING CHANGE* Removed IAppCache.ObjectCache and changed to a cache provider model. 46 | To access the provider use IAppCache.CacheProvider. By default we use a singleton shared in-memory cache but add your own cache provider by implmenting the simple `ICacheProvider`. 47 | - *BREAKING CHANGE* changed from CacheItemPolicy to MemoryCacheEntryOptions. RemovedCallback is now PostEvictionCallbacks. 48 | - Added a new replaceable global static default cache provider 49 | 50 | `Func DefaultCacheProvider { get; }` 51 | 52 | By default we use a shared in-memory cache but each instance can have it's underlying cache provider overridden from it's constructor. 53 | - Make methods on CachingService virtual/protected to enable 54 | - Add LazyCache.AspNetCore for dependency injection registration - ServiceCollection.AddLazyCache(); 55 | - Update sample to use aspnet core and LazyCache.AspNetCore 56 | - New IAppCache.DefaultCachePolicy to replace CachingService.DefaultCacheDuration 57 | - Moved most CachingService method overloads to extension methods on IAppCache in AppCacheExtensions. API should be backwards compatible but as now extension methods this is technically an API breaking changing. 58 | - Added new methods on IAppCache to allow you to specify cache expiry options on executution of the item factory 59 | 60 | `GetOrAdd(string key, Func addItemFactory)` 61 | 62 | `Task GetOrAddAsync(string key, Func> addItemFactory)` 63 | 64 | 65 | ## Version 0.7.1 66 | - Fix async/sync interopability bug, see https://github.com/alastairtree/LazyCache/issues/12 67 | 68 | ## Version 0.7 69 | 70 | - *BREAKING CHANGE* Upgrade to .net 4.5 71 | - Added ObjectCache property to IAppCache to allow access to underlying cache for operations such as cache clearing 72 | - Support caching asynchronous tasks with GetOrAddAsync methods 73 | - Add ApiAsyncCachingSample to demonstrate the caching the results of SQL Queries in a WebApi controller 74 | - Add badges to Readme 75 | 76 | ## Version 0.6 77 | 78 | - Fixed issue with RemovedCallback not unwrapping the Lazy used to thread safe the cache item. 79 | 80 | ## Version 0.5 81 | 82 | - Initial release of CachingService and interface IAppCache. 83 | - Readme 84 | - Core unit tests. 85 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '2.0.0.{build}' 2 | image: Visual Studio 2019 3 | configuration: Release 4 | environment: 5 | LazyCacheVersion: 2.0.0 6 | LazyCacheVersionSuffix: 7 | LazyCacheAspNetCoreVersion: 2.0.0 8 | LazyCacheAspNetCoreVersionSuffix: 9 | LazyCacheNinjectVersion: 2.0.0 10 | LazyCacheNinjectVersionSuffix: 11 | build_script: 12 | - ps: '& .\build.ps1' 13 | deploy: off -------------------------------------------------------------------------------- /artwork/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alastairtree/LazyCache/633b5702ddbc3e013aa9a3893d7b4dc0c807943f/artwork/favicon.ico -------------------------------------------------------------------------------- /artwork/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alastairtree/LazyCache/633b5702ddbc3e013aa9a3893d7b4dc0c807943f/artwork/logo-128.png -------------------------------------------------------------------------------- /artwork/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alastairtree/LazyCache/633b5702ddbc3e013aa9a3893d7b4dc0c807943f/artwork/logo-16.png -------------------------------------------------------------------------------- /artwork/logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alastairtree/LazyCache/633b5702ddbc3e013aa9a3893d7b4dc0c807943f/artwork/logo-256.png -------------------------------------------------------------------------------- /artwork/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alastairtree/LazyCache/633b5702ddbc3e013aa9a3893d7b4dc0c807943f/artwork/logo-32.png -------------------------------------------------------------------------------- /artwork/logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alastairtree/LazyCache/633b5702ddbc3e013aa9a3893d7b4dc0c807943f/artwork/logo-64.png -------------------------------------------------------------------------------- /artwork/logo-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /artwork/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | Set-StrictMode -Version latest 2 | $ErrorActionPreference = "Stop" 3 | 4 | 5 | # Taken from psake https://github.com/psake/psake 6 | 7 | <# 8 | .SYNOPSIS 9 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode 10 | to see if an error occcured. If an error is detected then an exception is thrown. 11 | This function allows you to run command-line programs without having to 12 | explicitly check the $lastexitcode variable. 13 | .EXAMPLE 14 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" 15 | #> 16 | function Exec 17 | { 18 | [CmdletBinding()] 19 | param( 20 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, 21 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd) 22 | ) 23 | & $cmd 24 | if ($lastexitcode -ne 0) { 25 | throw ("Exec: " + $errorMessage) 26 | } 27 | } 28 | 29 | $config = "release" 30 | 31 | Try { 32 | 33 | # Get dependencies from nuget and compile 34 | Exec { dotnet restore } 35 | Exec { nuget restore Console.Net461 -SolutionDirectory . } 36 | Exec { dotnet build --configuration $config --no-restore } 37 | 38 | # Find each test project and run tests. upload results to AppVeyor 39 | Get-ChildItem .\**\*.csproj -Recurse | 40 | Where-Object { $_.Name -match ".*Test.*\.csproj$"} | 41 | ForEach-Object { 42 | 43 | Exec { dotnet test $_.FullName --configuration $config --no-build --no-restore --logger:"trx;LogFileName=..\..\test-result.trx" } 44 | 45 | $testResults = (Resolve-Path .\test-result*.trx) 46 | # if on build server upload results to AppVeyor 47 | if ("${ENV:APPVEYOR_JOB_ID}" -ne "") { 48 | $wc = New-Object 'System.Net.WebClient' 49 | $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", $testResults) 50 | } 51 | 52 | Remove-Item $testResults -ErrorAction SilentlyContinue 53 | } 54 | 55 | # Publish the nupkg artifacts 56 | if (Get-Command "Push-AppveyorArtifact" -errorAction SilentlyContinue) 57 | { 58 | Get-ChildItem .\*\bin\$config\*.nupkg -Recurse | ForEach-Object { Push-AppveyorArtifact $_.FullName -FileName $_.Name } 59 | } 60 | 61 | } Catch { 62 | $host.SetShouldExit(-1) 63 | throw 64 | } --------------------------------------------------------------------------------