L1CacheSystem is the application’s internal cache. It was created to solve two problems that IMemoryCache couldn’t address:
- The ability to filter by all cache entities
- Blocking a specific resource by key
This is a niche, simple solution adapted to the specific tasks I’ve encountered. When all entities fit into RAM, and there’s no point in using Redis or something more complex. It doesn’t claim compatibility with hybrid caching or other approaches.
The first thing you need to do is to import the packages:
- L1CacheSystem
- L1CacheSystem.Attributes;
- L1CacheSystem.Generator
Then, in the code, you need to declare your cache. Let’s say that, for example, we will cache the MyValue class.
public class MyValue
{
public long Id { get; set; }
public double Value { get; set; }
// other properties
public MyValue Copy()
{
return new MyValue
{
Id = this.Id,
Value = this.Value
};
}
}public partial interface IServiceCacheSystem
{
}
// You can have as many EntityCacheType as you like, the number of different types is not limited.
[EntityCacheType(
cacheType: typeof(MyValue),
interfaceType: typeof(IServiceCacheSystem),
cacheName: "YouCacheName",
propAsKey: nameof(MyValue.Id),
metricCollectFlags: MetricCollectBits.AllBits, // what metrics for this specific cache need to collect
copyMethodName: nameof(MyValue.Copy),
copyMethodType: CopyMethodType.CopyMethodOnType)] // we also support: static method and copy-constructor
public partial class ServiceCacheSystem : L1CacheSystemBase, IServiceCacheSystem
{
public ServiceCacheSystem(
ILogger<ServiceCacheSystem> logger,
TimeProvider timeProvider,
IMeterFactory meterFactory)
: base(
logger,
timeProvider,
meterFactory)
{
}
}Now you just need create and run system. For example in asp.net we can do this with that way:
public class CacheSystemHostedService : IHostedService
{
private readonly IL1CacheSystemBase _cacheSystem;
public CacheSystemHostedService(IL1CacheSystemBase cacheSystem)
{
_cacheSystem = cacheSystem;
}
public Task StartAsync(CancellationToken cancellationToken)
{
return _cacheSystem.StartAsync(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _cacheSystem.StopAsync(cancellationToken);
}
}services.AddSingleton<ServiceCacheSystem>();
services.AddSingleton<IL1CacheSystemBase>((provider) => provider.GetRequiredService<ServiceCacheSystem>()); // IL1CacheSystemBase interface providet by L1CacheSystem nuget
services.AddSingleton<IServiceCacheSystem>((provider) => provider.GetRequiredService<ServiceCacheSystem>());Settings is done. Now usage case:
public class MyValueService
{
private readonly ILogger<MyValueService> _logger;
private readonly IServiceCacheSystem _casheSystem;
public MyValueService(ILogger<MyValueService> logger, IServiceCacheSystem casheSystem)
{
_logger = logger;
_casheSystem = casheSystem;
}
public CreateValue(long id)
{
await using var lockGuard = new LockGuard(logger: _logger, parent: null);
// Since we are at the root of the call (parent == null), we will either gain ownership of the resource immediately,
// or at the moment when other calls stop owning it. If the waiting should not be infinite, pass a CancellationToken.
await _cacheSystem.GetYouCacheNameLockAsync(id, lockGuard, default);
// do whatever you want: your custom logic
// YouCacheName with Id = id nobody (except you) can change until lockGuard.Dispose not called
var value = new MyValue() { Id = id, Value = 7114 };
// If you need the value and plan to change it after this call, then pass it as `NotTransferred`
// Then CacheSystem will save a copy of it during the call (by copying it using the method you specified earlier, at the configuration stage).
_cacheSystem.SetYouCacheName(value.Id, value, valueOwnership: ValueOwnership.Transferred, expiry: null);
// if "expiry" is provided then casheSystem remove cache value from cache when key is expire
// The Set method is thread‑safe on its own; the lockGuard is only needed if you’re using something more complex than a simple set/get.
}
public DoRelatedWork(long id, ILockGuard? lockGuard = null)
{
// in this method we not shure what poinc its called
// maybe this is root call or maybe recudsive
await using var lockGuard = new LockGuard(_logger, lockGuard);
// If this is not the root (lockGuard != null) and the parent already has a lock on this key,
// then the lock counter inside lockGuard will simply be increased.
await _cacheSystem.GetYouCacheNameLockAsync(id, lockGuard, default);
// your custom logic
// On dispose lockGuard release all locks: decrease lock counter if this lockGuard not the owner of lock or release if is the owner
}
public MyValue GetMyValue(long id)
{
return _cacheSystem.GetYouCacheName(id, ReturnType.Reference); // or ReturnType.Copy
}
public MyValue[] GetAllMyValue(long id, Func<MyValue, bool>? predicate = null)
{
return _cacheSystem.GetAllYouCacheName(predicate, ReturnType.Reference); // or ReturnType.Copy
}
}What metrics look like, using Grafana visualization as an example:
