Skip to content

Bump ZiggyCreatures.FusionCache from 0.26.0 to 2.9.0 - #23

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/beta-censoring/src/BetaCensor.Caching/ZiggyCreatures.FusionCache-2.9.0
Closed

dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/nuget/beta-censoring/src/BetaCensor.Caching/ZiggyCreatures.FusionCache-2.9.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Oct 2, 2026

Copy link
Copy Markdown
Contributor

Updated ZiggyCreatures.FusionCache from 0.26.0 to 2.9.0.

Release notes

Sourced from ZiggyCreatures.FusionCache's releases.

2.9.0

🦅 Eager Refresh now also checks L2

Community member @​sfhb24 noticed that during an Eager Refresh the L2 was not being checked.

Now, this is admittedly not a huge thing per se, because of 2 reasons:

  • backplane: when using an L1+L2 setup, a backplane is usually also used, and in that case the factory would not run because an update on L2 would be immediately visible to the other nodes
  • distributed locker: by using a distributed locker a factory would not run because nodes would coordinate so that only 1 factory runs concurrently, even on multiple nodes

In both these cases, the extra L2 check during an eager refresh would not be necessary.

Having said that, an L1+L2 setup without a backplane or a distributed locker may also be common, and in that case the new L2 check in the eager refresh window can in fact be helpful in reducing the amount of factory executions even more.

Long story short: I just implemented it!

See here for the issue.

🔒 Fix for memory locker + Eager Refresh edge case

If a factory fails when executed in the background during an eager refresh, FusionCache already takes care of everything and correctly releases the potentially acquired locks (memory and/or distributed), and this is good.

But community member @​joaopbnogueira noticed a peculiar edge case: if the factory is not one marked with the async keyword AND it throws an exception, the memory lock is not being released properly.

Or, to better say, "was not". Because now this has been fixed.

Thanks João for spotting this.

See here for the issue.

🔒 Fix for distributed locker + skip L1 edge case

Community member @​joaopbnogueira also noticed another peculiar scenario, specifically when a MemoryCache write fails (yep, it can happen, true story).

Here's an example of it:

  • a custom MemroyCache is being used as the L1
  • that instance has been configured with a SizeLimit
  • there's a cache miss
  • the factory executes successfully
  • the new entry does not have a Size specified
  • L1 write fails (because with a SizeLimit it's mandatory for every entry to specify a Size)

In this scenario a distributed lock may not have been released.

But fear no more: this does not hapen anymore!

See here for the issue.

🧼 Fix for Clear(false) + Fail-Safe

... (truncated)

2.8.0

🔒 Fix for distributed lock release when skipping L2 write

Community member @​joaopbnogueira spotted a problem with an edge case: when using SkipDistributedCacheWrite the distributed locker was not being properly disposed, which was unfortunate.
Now this has been fixed.

See here for the issue.

🏷️ Fix for tag marker re-materialization

Community member @​igor-henriques noticed an issues because of which when the tag marker (the special cache entry containing the RemoveByTag() timestamp) expired, it was being re-materialized with a newer timestamp: this could have led to the same results as a new RemoveByTag() call.
That was unfortunate, but it has now been fixed.

See here for the issue.

🏷️ Better handling of RemoveByTagBehavior.Remove

Community member @​gpetrou asked for some help regarding a certain scenario, and during the explanation/investigation it emerged that FusionCache could have handled RemoveByTagBehavior.Remove in a slightly better way.
It was mostly an edge case, but still: now the way it is internally handled is even better than before.

See here for the issue.

🔭 Better observability for user-initiated cancellations

Community member @​dzmitry-tsarevich highlighted that user-initiated cancellations were being handled in a little-too-aggressive way from the oint of view of observability: too much background noise was being generated, which could lead to bloat.
Now this has been made better, slimmer.

See here for the issue.

👷 New builder ext methods

Community member @​Stepami noticed the lack of a specific ext method on the builder to register the distributed locker based on Redis, basically WithRedisDistributedLocker().

After accepting his PR, I noticed a couple of extra ones were also missing, and so I added them.

See here for the issue.

Also, community member @​petriceko asked for a new overload of the WithOptions() ext method with better DI support: promptly, community member @​vrbyjimmy made a PR to add that, which I merged.
Talk about community collaboration 🙂

See here for the issue.

2.7.2

🔃 Better DI registrations checks

Community member @​erikatsg noticed what seemed like a strange behavior, but after a preliminary check discovered that the problem was on their side, because they were adding the same FusionCache registration to the DI container more than once.

Something like this:

services.AddFusionCache()
  .WithDefaultEntryOptions(options =>
  {
    options.Duration = TimeSpan.FromSeconds(111);
  });

// LATER...

services.AddFusionCache()
  .WithDefaultEntryOptions(options =>
  {
    options.Duration = TimeSpan.FromSeconds(222);
  });

This is generally not suggested nor supported.

Luckily, FusionCache already had a series of checks for that and more strange situations, and those checks emit log records in those situations.

Unluckily though, those checks were previously performed only when asking to the DI container for an IFusionCacheProvider (to work with named caches).

Well, not anymore: now it works in any setup/scenarion, all automatically.

The current list of checks performed is:

  • multiple direct IFusionCache registrations (e.g.: multiple services.AddFusionCache() calls)
  • multiple named IFusionCache registrations (e.g.: multiple services.AddFusionCache(name) calls with the same name)
  • multiple keyed IFusionCache registrations (e.g.: multiple services.AddFusionCache().AsKeyedService(key) calls with the same key, for either named caches or the default cache)

See here for the issue.

🏅 Better Advisor checks

A new check has been added in the Advisor that detecs a potentially incorrect configuration for a System.Text.Json based serializer related to value tuples.

This is not about a FusionCache bug, but about the System.Text.Json serializer's default configuration deviating from the commonly expected one (like, historically, Json.NET for example), see here for more.

Instead of forcing a specific json configuration for FusionCache, the Advisor now checks if the serializer in use is not supporting value tuples correctly and, if so, emits a warning (also configurable) in the logs to warn users about that.

This allows them to make the best decision for their specific use case.

Thanks to @​sychare and @​bassepeder for the hints.

Also, the check for a missing CacheKeyPrefix is now better, more limited in scope.
... (truncated)

2.7.1

[!NOTE]
You should update to v2.7.2, see here.

🏅 Better Advisor checks

A new check has been added in the Advisor that detecs a potentially incorrect configuration for a System.Text.Json based serializer related to value tuples.

This is not about a FusionCache bug, but about the System.Text.Json serializer's default configuration deviating from the commonly expected one (like, historically, Json.NET for example), see here for more.

Instead of forcing a specific json configuration for FusionCache, the Advisor now checks if the serializer in use is not supporting value tuples correctly and, if so, emits a warning (also configurable) in the logs to warn users about that.

This allows them to make the best decision for their specific use case.

Thanks to @​sychare and @​bassepeder for the hints.

Also, the check for a missing CacheKeyPrefix is now better, more limited in scope.

See here and here for the issues.

🆕 New SerializationConfigIssuesLogLevel option

See the previous item: with this new option is possible to change the desired log level, or suppress it.

🆕 New DistributedLockerErrorsLogLevel option

It's now possible to granularly configure the log level to use for distributed lockers errors, which is nice.

🔭 Better handling of OTEL parent traces

Sometimes the handling of OTEL parent traces was not the best, particularly around highly multithreaded scenarios where a native Activity is not being passed around correctly in the context.

This has now been fixed.

🏷️ OTEL traces for a RemoveByTag(tag) operation now include the tag (duh)

Normally, every operation in the cache has a cache key as the main argument.

In the case of a RemoveByTag(tag) operation though, that is not the case since the main argument is not the cache key, but the tag: strangely enough, FusionCache previously was not logging it front and center, but now it does.

This should help with tagging-related investigations and troubleshootings.

Thanks to community member @​tvardero for spotting this.

See here for the issue.

🐞 Better temporal checks during an L2 to L1 copy

Thanks to community member @​DanielStout5 for spotting this: it's quite rare, but sometimes an entry in L2 may be older than the corresponding entry in L1, and before this fix that assumption did not hold sometimes.
Now an extra check is performed, to make sure that this scenario doesnot produce unwanted results.

... (truncated)

2.7.0

[!NOTE]
You should update to v2.7.2, see here: there's a small issue in v2.7.0, nothing critical really, but better to use the new version.

🏅 Better Advisor checks

A new check has been added in the Advisor that detecs a potentially incorrect configuration for a System.Text.Json based serializer related to value tuples.

This is not about a FusionCache bug, but about the System.Text.Json serializer's default behavior deviating from the commonly expected one (like, historically, Json.NET for example), see here for more.

Instead of forcing different json options specifically for FusionCache, the Advisor now checks if the serializer in use is not supporting value tuples correctly and, if so, emits a warning (also configurable) in the logs to warn users about that.

This allows them to make the best decision for their specific use case.

Thanks to @​sychare and @​bassepeder for the hints.

Also, the check for a missing CacheKeyPrefix is now better, more limited in scope.

See here and here for the issues.

🆕 New SerializationIssuesLogLevel option

See the previous item: with this new option is possible to change the desired log level, or suppress it.

🆕 New DistributedLockerErrorsLogLevel option

It's now possible to granularly configure the log level to use for distributed lockers errors, which is nice.

🔭 Better handling of OTEL parent traces

Sometimes the handling of OTEL parent traces was not the best, particularly around highly multithreaded scenarios where a native Activity is not being passed around correctly in the context.

This has now been fixed.

🏷️ OTEL traces for a RemoveByTag(tag) operation now include the tag (duh)

Normally, every operation in the cache has a cache key as the main argument.

In the case of a RemoveByTag(tag) operation though, that is not the case since the main argument is not the cache key, but the tag: strangely enough, FusionCache previously was not logging it front and center, but now it does.

This should help with tagging-related investigations and troubleshootings.

Thanks to community member @​tvardero for spotting this.

See here for the issue.

🐞 Better temporal checks during an L2 to L1 copy

Thanks to community member @​DanielStout5 for spotting this: it's quite rare, but sometimes an entry in L2 may be older than the corresponding entry in L1, and before this fix that assumption did not hold sometimes.
Now an extra check is performed, to make sure that this scenario doesnot produce unwanted results.

... (truncated)

2.6.0

🏷️ Configurable cleanup behavior for RemoveByTag()

Normally, when calling RemoveByTag("my-tag"), the entries with such a tag will be gradually expired on a subsequent access.

Community member @​charlesvigneault asked for the ability to instead properly remove them.

So I added a new option to allow configuring this behavior:

services.AddFusionCache()
	.WithOptions(options =>
	{
		options.RemoveByTagBehavior = RemoveByTagBehavior.Remove;
	});

See here for the original issue.

Ⓜ️ Add support for RemoveByTag("*") in HybridCache adapter

After the initial release of HybridCache in 2025, the team added support for a special case: using RemoveByTag("*") to clear the entire cache.

I didn't notice untile recently, and thanks to community user @​vrbyjimmy I did that.
Or, to better say it, he did that!
He acted so quickly that a PR immediately landed with the implementation, so thanks Jakub for that!

What happens underneath is that a RemoveByTag("*") call on the adapter is detected and re-routed to a Clear() call on the underlying FusionCache instance: very simple and elegant, and I like that a lot.

See here for the original issue.

🔒 Better Distributed Locker + Eager Refresh

Community user @​jgshowpad noticed that when using the new distributed stampede protection introduced in v2.5.0 with Eager Refresh some errors were being logged.

That was caused by the Redis-based distributed locker not handling correctly a timeout of zero (which btw is a pretty common approach to basically check for a lock already being acquired by someone else, without having to wait).

This has now been fixed.

See here for the original issue.

⚡ Perf boost for GenerateOperationId()

Community user @​Inok contributed with a nice set of low-level perf optimizations for the GenerateOperationId() internal method, which may be called quite a lot when doing observability (logging, OTEL, etc).

That's a very nice and welcome contribution, thanks Pavel!

See here for the original issue.
... (truncated)

2.5.0

🛡️ Distributed Cache Stampede Protection

Since the very beginning FusionCache offered a solid Cache Stampede protection, as explained in the docs where it is clearly illustrated:

Cache Stampede Request Coalescing

Such protection worked not just in the normal flow (miss -> factory -> return) but also with other more advanced features like:

  • Eager Refresh: hit (after the eager threshold) -> return + background factory
  • Factory Timeouts: miss -> factory + timeout -> return + background complete

With time the stampede protection got even better, and even extensible: this allowed 3rd party implementations of the core mechanism, called memory locker (IFusionCacheMemoryLocker).

All of this without removing the normal "it just works" experience since, by default, a StandardMemoryLocker is used without needing any user setup or intervention.

Cool.

But here's the thing: this protection had always been a local thing, meaning it did not span multiple nodes, in a distributed way: this meant that, if we were "unlucky", multiple factories could have run at the same time for the same cache key on different nodes.

Meaning, this:

Distributed Cache Stampede, Before

But that was true until now: enter Distributed Cache Stampede Protection 🎉

Thanks to the introduction of the new IFusionCacheDistributedLocker (see the next point) it's now possible to coordinate factory execution accross multiple nodes, so that only one factory would run at the same time for the same cache key even on different nodes.

Meaning, this:

Distributed Cache Stampede, After

By providing an IFusionCacheDistributedLocker implementation during setup, FusionCache will take care of everything, we don't have to do anything else.

The setup looks like this:

services.AddFusionCache()
	// SERIALIZER
	.WithSerializer(
		new FusionCacheSystemTextJsonSerializer()
	)
	// DISTRIBUTED CACHE
	.WithDistributedCache(
		new RedisCache(new RedisCacheOptions
		{
			Configuration = "localhost:6379",
		})
	)
	// BACKPLANE
	.WithBackplane(
		new RedisBackplane(new RedisBackplaneOptions
 ... (truncated)

## 2.4.0

## 🏷️ Add `StaleTags` to factory execution context

Community user @​ted-mundy noticed a tricky behavior when using Tagging with stale entries (see next point).

To solve it, I added a new `StaleTags` property to the factory execution context, so that now it's possible to access both the tags that are being passed to the `GetOrSet()` call and the existing tags of the stale entry in the cache (if any), like this:

```c#
cache.GetOrSet<string>(
  "foo",
  (ctx, token) => {
    // THE TAGS PASSED BELOW ("tag1", "tag2" and "tag3")
    ctx.Tags;
    // THE TAGS OF THE STALE ENTRY ALREADY IN THE CACHE, IF ANY
    ctx.StaleTags;
    
    return "Combo";
  },
  tags: ["tag1", "tag2", "tag3"]
);

This can be useful even in other scenarios, like applying some custom logic about what to do based on the tags already in the cache.

Nice.

🐞 Fix for tags with stale entries

As mentioned above, community user @​ted-mundy noticed a tricky behavior when using Tagging with stale entries.

Thanks to the addition of the new StaleTags property, this is now solved for good.

Thanks @​ted-mundy !

See here for the original issue.

Ⓜ️ Better entry options mapping with HybridCache adapter

Community user @​TheSpookyElectric noticed that, when working with the HybridCache adapter, the LocalCacheExpiration was not being handled correctly in all cases.

The mapping logic has been updated to account for that, and it now works as expected.

Thanks @​TheSpookyElectric !

See here for the original issue.

🐞 Fix for WithRegisteredSerializer()

... (truncated)

2.3.0

🔑 Access the cache key in the factory context

Community user @​posledam and others asked for the ability to access the cache key in the factory context, to be able to work with it while going to the database or similar, without creating a closure with the lambda.

So that's what I added, but there's a catch here: FusionCache provides automatic cache key manipulation with things like CacheKeyPrefix usually in conjunction with things like Named Caches, so it would be nice to access both the original one and the processed one.

Therefore I added both of them in the factory context, and it can be used like this:

cache.GetOrSet<string>(
  "foo",
  (ctx, token) => {
    ctx.Key; // THE (PROCESSED) CACHE KEY
    ctx.OriginalKey; // THE (ORIGINAL) CACHE KEY
  }
);

See here for the original issue, and here for the design issue.

⚙️ New InternalStrings options

FusionCache automatically handles a lot of things for us, and to do that it may need to manipulate some strings used internally like the cache key or the backplane channel name.

For example to use the CacheName to automatically separate data in a shared cache when used in conjunction with other FusionCache instances, or the set of special cache keys used with Tagging or the way wire format versioning is handled to automatically avoid errors when evolving the internal data structures.

In these cases some special characters are used as separators.

This is all good and well, but recently community user @​stebet started working on a NATS version of the backplane (and that's awesome!) and he noticed that some of these special characters create issues on NATS, which has some reserved characters that have special meaning or cannot be used anyway.

Because of this I'm adding a new set of options specifically for changing the set of internal strings used by FusionCache, so that it's possible to work with systems like NATS and avoid issues.

So now we have a new InternalStrings option inside of FusionCacheOptions where we can set strings like:

  • TagCacheKeyPrefix
  • ClearRemoveTag
  • DistributedCacheWireFormatSeparator
  • BackplaneWireFormatSeparator
  • and more

It can be used like this:

var options = new FusionCacheOptions()
{
  // ...
  InternalStrings = {
    TagCacheKeyPrefix = "tag__",
    ClearRemoveTag = "clear_remove"
    // ...
  }
 ... (truncated)

## 2.2.0

## 🎯 Changes in multi-targeting

Some time ago I started enabling multi-targeting on FusionCache: I didn't do that because I needed to special case some parts of the code, but just because I wanted to reduce the amount of explicit dependencies for certain TFMs after a request from the community.

But recently community user @​nick-randal noticed in #​416 some potential issues: long story short, from now on FusionCache will have explicit targeting only for currently supported TFMs (which today means no more .NET 3.1, .NET 6 or .NET 7) and for them it will have the minimum set of explicit dependencies.

But wait: does this mean that those older versions of .NET wil not be able to use FusionCache anymore?

Absolutely not: since FusionCache targets .NET Standard 2.0, this means that ANY version of .NET compatible with .NET Standard 2.0 (meaning: all versions) will still be able to use FusionCache, just without an explicit "support statement", since those versions are anyway not supported anymore, not even by Microsoft itself.

See [here](https://github.com/ZiggyCreatures/FusionCache/issues/416) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/425) for the original issues.

## 🚀 Make the AOT support official

FusionCache has been AOT compatible for a long time, which is already good.

I just need to make that more "official" by declaring it in the csproj, enabling analyzers, create a test console app and, in general, do everything that's needed. And that's what I did.

Thanks to community user @​digital88 for pointing that out.

See [here](https://github.com/ZiggyCreatures/FusionCache/discussions/439) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/449) for the original issues.

## 🔀 Expose the current distributed cache, if any

Currently, given a `FusionCache` instance, it's only possible to know if there is a distributed cache level (L2) via the `bool HasDistributedCache { get; }` property, not which one it is.

Community user @​angularsen asked to expose it in #​443 .

Historically I've been hesitant to expose internals, but at this point I think I can let this one go.

So now there's a new `IDistributedCache? DistributedCache { get; }` property that expose the `IDistributedCache` instance being used, if any.

See [here](https://github.com/ZiggyCreatures/FusionCache/issues/443) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/446) for the original issues.

> [!WARNING]
> This is _technically_ a breaking change, but since nobody has custom `IFusionCache` implementations and I updated both (`FusionCache` and `NullFusionCache`), I think this is fine.

## 📢 Expose the current backplane, if any

Same as above, but for the backplane.

So now there's a new a new `IFusionCacheBackplane? Backplane { get; }` property that expose the `IFusionCacheBackplane` instance being used, if any.

See [here](https://github.com/ZiggyCreatures/FusionCache/issues/443) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/445) for the original issues.

> [!WARNING]
> This is _technically_ a breaking change, but since nobody has custom `IFusionCache` implementations and I updated both (`FusionCache` and `NullFusionCache`), I think this is fine.

## 😶 Add DI support for `NullFusionCache`

 ... (truncated)

## 2.2.0-preview-1

## 🎯 Changes in multi-targeting

Some time ago I started enabling multi-targeting on FusionCache: I didn't do that because I needed to special case some parts of the code, but just because I wanted to reduce the amount of explicit dependencies for certain TFMs fater after a request from the community.

But recently community user @​nick-randal noticed in #​416 some potential issues: long story short, from now on FusionCache will have explicit targeting only for currently supported TFMs (which today means no more .NET 3.1, .NET 6 or .NET 7) and for them it will have the minimum set of explicit dependencies.

But wait: does this mean that those older versions of .NET wil not be able to use FusionCache anymore?

Absolutely not: since FusionCache targets .NET Standard 2.0, this means that ANY version of .NET compatible with .NET Standard 2.0 (meaning: all versions) will still be able to use FusionCache, just without an explicit "support statement", since those versions are anyway not supported anymore, not even by Microsoft itself.

See [here](https://github.com/ZiggyCreatures/FusionCache/issues/416) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/425) for the original issues.

## 🚀 Make the AOT support official

FusionCache has been AOT compatible for a long time, which is already good.

I just need to make that more "official" by declaring it in the csproj, enabling analyzers, create a test console app and, in general, do everything that's needed. And that's what I did.

Thanks to community user @​digital88 for pointing that out.

See [here](https://github.com/ZiggyCreatures/FusionCache/discussions/439) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/449) for the original issues.

## 🔀 Expose the current distributed cache, if any

Currently, given a `FusionCache` instance, it's only possible to know if there is a distributed cache level (L2) via the `bool HasDistributedCache { get; }` property, not which one it is.

Community user @​angularsen asked to expose it in #​443 .

Historically I've been hesitant to expose internals, but at this point I think I can let this one go.

So now there's a new `IDistributedCache? DistributedCache { get; }` property that expose the `IDistributedCache` instance being used, if any.

See [here](https://github.com/ZiggyCreatures/FusionCache/issues/443) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/446) for the original issues.

> [!WARNING]
> This is _technically_ a breaking change, but since nobody has custom `IFusionCache` implementations and I updated both (`FusionCache` and `NullFusionCache`), I think this is fine.

## 📢 Expose the current backplane, if any

Same as above, but for the backplane.

So now there's a new a new `IFusionCacheBackplane? Backplane { get; }` property that expose the `IFusionCacheBackplane` instance being used, if any.

See [here](https://github.com/ZiggyCreatures/FusionCache/issues/443) and [here](https://github.com/ZiggyCreatures/FusionCache/issues/445) for the original issues.

> [!WARNING]
> This is _technically_ a breaking change, but since nobody has custom `IFusionCache` implementations and I updated both (`FusionCache` and `NullFusionCache`), I think this is fine.

See here for the original issue.

 ... (truncated)

## 2.1.0

| 🙋‍♂️ Updating to `v2` ? Please [read here](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Update_v2_0_0.md). |
|:-------|

## 🔌 Integrate All The Things!

Now that v2 isfinally here and with full [Tagging](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Tagging.md) support, it's time to integrate all the things 🥳

The first 2 can be found below:
- OutputCache
- EF 2nd Level Cache

## 🚀 Output Cache, FusionCache style

The first one on my list is [Output Cache](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/output), and the nice thing about the way it has been designed in ASP.NET is that the only thing that is needed to make a custom version is an implementation of [`IOutputCacheStore`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.outputcaching.ioutputcachestore).

And so I did, and thanks to native Tagging in FusionCache the whole implementation is a thing of beauty with just 1 line per method: [behold](https://github.com/ZiggyCreatures/FusionCache/blob/9104e12b925517142d95bab47027f1e886e56484/src/ZiggyCreatures.FusionCache.AspNetCore.OutputCaching/FusionOutputCacheStore.cs).

Btw while I was working on this, community user @​Fabman08 [asked](https://github.com/ZiggyCreatures/FusionCache/discussions/366) for the same thing, talk about good timing!

Anyway, why is all of this useful?

Because now, when using OutputCache, we'll not be limited by a simple memory cache anymore, and can instead have the power of all the features of FusionCache like [fail-safe](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/FailSafe.md), [L1+L2](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/CacheLevels.md), [backplane](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Backplane.md) support and more: imagine having the performance of a memory cache (L1) but with the availability and database savings of a distributed cache (L2) including instant synchronization of the backplane.

If you ask me, it's awesome.

Ok so, how can we set it up?

Easy:

```c#
// FUSION CACHE
services.AddFusionCache();

// FUSION OUTPUT CACHE
services.AddFusionOutputCache();

// OUTPUT CACHE (STANDARD SETUP)
services.AddOutputCache(options =>
{
  options.AddPolicy("Expire2", builder =>
    builder.Expire(TimeSpan.FromSeconds(2))
  );
  options.AddPolicy("Expire5", builder =>
    builder.Expire(TimeSpan.FromSeconds(5))
  );
});

When using the normal OutputCache (with a memory-only cache store) we need to:

  • setup OutputCache (settings, profiles, etc)
    ... (truncated)

2.0.2

📣 LTS-ONLY RELEASE
This version references only .NET 8 core packages, so it can be used in scenarios where only LTS packages can be referenced.

A Special Version

This version is exactly the same as v2.2.0, except for 2 things:

  • it ONLY references v8 core packages (LTS)
  • it does NOT contain the HybridCache integration, since the needed HybridCache abstractions are part of v9 core packages

This has been done to support users that cannot reference STS core packages.

See here for the original issue and more details, like, a lot more details.

2.0.1

📣 LTS-ONLY RELEASE
This version references only .NET 8 core packages, so it can be used in scenarios where only LTS packages can be referenced.

A Special Version

This version is exactly the same as v2.1.0, except for 2 things:

  • it ONLY references v8 core packages (LTS)
  • it does NOT contain the HybridCache integration, since the needed HybridCache abstractions are part of v9 core packages

This has been done to support users that cannot reference STS core packages.

See here for the original issue and more details, like, a lot more details.

2.0.0

[!IMPORTANT]
This is a world's first!

FusionCache is the first production-ready implementation of Microsoft HybridCache: not just the first 3rd party implementation, which it is, but the very first implementation at all, including Microsoft's own implementation which is not out yet.

Read below for more.

🙋‍♂️ Updating to v2 ? Please read here.

❤️ FusionCache V2: a small personal note

For me (Jody), this feels like a monumental personal achievement. The amount of work poured into it, the sheer size of the release, all the new features like Tagging, Clear(), Microsoft HybridCache support and everything else: I honestly couldn't be prouder of it.

I hope you will all like using it, as much as I liked creating it.

Ok, end of the personal note: let's get the features rolling!

🏷️ Tagging (docs)

FusionCache now has full support for tagging!

This means we can now associate one or more tags to any cache entry and, later on, simply call RemoveByTag("my-tag") to evict all the entries that have the "my-tag" associated to them.

And yes, it works with all the other features of FusionCache like L1+L2, backplane, fail-safe, soft timeouts, eager refresh, adaptive caching and everything else.

Honestly, the end result is a thing of beauty.

Here's an example:

cache.Set("risotto_milanese", 123, tags: ["food", "yellow"]);
cache.Set("kimchi", 123, tags: ["food", "red"]);
cache.Set("trippa", 123, tags: ["food", "red"]);
cache.Set("sunflowers", 123, tags: ["painting", "yellow"]);

// REMOVE ENTRIES WITH TAG "red"
cache.RemoveByTag("red");

// NOW ONLY "risotto_milanese" and "sunflowers" ARE IN THE CACHE

// REMOVE ENTRIES WITH TAG "food"
cache.RemoveByTag("food");

// NOW ONLY "sunflowers" IS IN THE CACHE

It's really that simple.

... (truncated)

2.0.0-preview-4

[!IMPORTANT]
This is most probably the LAST PREVIEW of FusionCache V2, before going GA.
All help is more than welcome since the main feature, Tagging, is an uber complex beast.

[!WARNING]
Because of the MAJOR version change, for now I decided to bump the wire format identifier: read more here and here.

Ⓜ️ Support for Microsoft's new HybridCache, without the extra package

Thanks to a suggestion by community member @​pwelter34 the ZiggyCreatures.FusionCache.MicrosoftHybridCache extra package was not actually needed.

Because of this, 2 things happened:

  1. the adapter class is now directly in the main FusionCache package
  2. the extra package has been updated, made empty, and will be marked as deprecated

Less code, less packages and less dependencies to deal with, yeah 🥳

See here for the issue.

🆕 New AllowStaleOnReadOnly entry option

Historically fail-safe has been used even with read-only methods like TryGet and GetOrDefault, where a "fail" cannot actually happen.

This sometimes created some confusion, and lead to people enabling fail-safe globally and sometimes getting back stale values even with read-only operations.

To allow for better and more granular control over this aspect, a new AllowStaleOnReadOnly entry option has been added, which controls the return of stale values in read-only operations.

ℹ️ Better metadata

Community user @​jarodriguez-itsoft noticed that sometimes the payload in the distributed cache was not as small as it could've been.
Because of this the internal shape of the metadata class has been changed, to better reflect the common usage patterns and save memory whenever possible.
This set of changes made it possible to get a metadata-less scenario, which in a lot of scenario will lead to smaller payloads in the distributed cache, less network usage and so on.

On top of this, some fields has been changed to a different type to allocate less and consume less cpu, while at the same time better support has been added for cross-nodes entry priority when scaling horizontally.

See here for the original issue.

⚡ Better serializers

Thanks to a big effort by community user @​stebet , serializers are now natively better and allocate less, thanks to some array pools/buffers magic.
Because of this, support for the external RecyclableMemoryStreamManager has been removed, since it's not necessary anymore.
Less dependencies here, too!

See here for the original PR.


... (truncated)

2.0.0-preview-3

[!IMPORTANT]
This is a PREVIEW of a very big and important milestone for FusionCache.
Although all is already in a very good shape, this is still a PREVIEW version.
All help is more than welcome since the main feature, Tagging, is an uber complex beast.

[!WARNING]
Because of the MAJOR version change, for now I decided to bump the wire format identifier: read more here and here.

Ⓜ️ Native support for Microsoft's new HybridCache

As already announced when I shared my thoughts on the new Microsoft HybridCache some time ago, I wanted to allow FusionCache to be also usable as a 3rd party HybridCache implementation.

To be clear, this does NOT mean that FusionCache will now be based on HybridCache from Microsoft, but that it will ALSO be available AS an implementation of it, via an adapter class included in a new Nuget package.

So, how can we use it?

Easy peasy, we just add the new package:

dotnet add package ZiggyCreatures.FusionCache.MicrosoftHybridCache --version "2.0.0-preview-3"

and, when setting up FusionCache in our Startup.cs file, we simply add .AsHybridCache():

services.AddFusionCache()
  .WithDefaultEntryOptions(options =>
  {
    options.Duration = TimeSpan.FromSeconds(10);
    options.IsFailSafeEnabled = true;
  })
  .AsHybridCache(); // MAGIC

Now, every time we'll ask for HybridCache via DI (taken as-is from the official docs):

public class SomeService(HybridCache cache)
{
    private HybridCache _cache = cache;

    public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
    {
        return await _cache.GetOrCreateAsync(
            $"{name}-{id}", // Unique key to the cache entry
            async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
            cancellationToken: token
        );
    }

 ... (truncated)

## 2.0.0-preview-2

> [!IMPORTANT]
> This is a PREVIEW of a very big and important milestone for FusionCache.
> All help is more than welcome since the main feature, Tagging, is an uber complex beast.

> [!WARNING]  
> Although all is already in a very good shape, this is still a PREVIEW version.

> [!WARNING]  
> Because of the MAJOR version change, for now I decided to bump the wire format identifier: read more [here](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/CacheLevels.md#-wire-format-versioning) and [here](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Backplane.md#-wire-format-versioning).

## 🔀 New entry options to precisely skip read/write on memory/distributed levels

New entry options have been added to precisely skip reading and/or writing for both the memory (L1) and the distributed (L2) levels.

Now we have 4 options with more granular control:
- `SkipMemoryCacheRead`
- `SkipMemoryCacheWrite`
- `SkipDistributedCacheRead`
- `SkipDistributedCacheWrite`

Previously we had 2 options:
- `SkipMemoryCache`
- `SkipDistributedCache`

Now these 2 old ones now act in this way:
- the setter changes both the corresponding read/write ones
- the getter returns `true` if both the read and the write options are set to `true` (eg: `return SkipMemoryCacheRead && SkipMemoryCacheWrite`)

Even if they work, to avoid future confusion the 2 old ones have been marked as `[Obsolete]`.

Of course the handy ext methods like `SetSkipMemoryCache()` still work.

## 📜 New `IncludeTagsInLogs` option

FusionCache now allows you to include tags when logging a cache entry, via the new `IncludeTagsInLogs` option.

It is disabled by default since tags can be considered as part of the "content" of the cached entry itself, an so they may contain sensitive informations.

We can just enable it in the `FusionCacheOptions` and be good to go.

----------------------
### ⬇️ Other release notes from preview-1 ⬇️
----------------------

## 🏷️ Tagging ([docs](https://github.com/ZiggyCreatures/FusionCache/issues/319))

Yep, it's true: FusionCache now has full support for tagging!
This means we can now associate one or more tags to any cache entry and, later on, simply call `RemoveByTag("my-tag")` to evict all the entries that have the `"my-tag"` associated to them.

And yes, it works with all the other features of FusionCache like [L1+L2](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/CacheLevels.md), [backplane](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Backplane.md), [fail-safe](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/FailSafe.md), [soft timeouts](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Timeouts.md), [eager refresh](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/EagerRefresh.md), [adaptive caching](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/AdaptiveCaching.md) and everything else.
 ... (truncated)

## 2.0.0-preview-1

> [!IMPORTANT]
> This is the first PREVIEW of a very big and important milestone for FusionCache.
> All help is more than welcome since the main feature, Tagging, is an uber complex beast.

> [!WARNING]  
> Although all is already in a very good shape, this is still a PREVIEW version.

> [!WARNING]  
> Because of the MAJOR version change, for now I decided to bump the wire format identifier: read more [here](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/CacheLevels.md#-wire-format-versioning) and [here](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Backplane.md#-wire-format-versioning).

## 🏷️ Tagging ([docs](https://github.com/ZiggyCreatures/FusionCache/issues/319))

Yep, it's true: FusionCache now has full support for tagging!
This means we can now associate one or more tags to any cache entry and, later on, simply call `RemoveByTag("my-tag")` to evict all the entries that have the `"my-tag"` associated to them.

And yes, it works with all the other features of FusionCache like [L1+L2](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/CacheLevels.md), [backplane](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Backplane.md), [fail-safe](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/FailSafe.md), [soft timeouts](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/Timeouts.md), [eager refresh](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/EagerRefresh.md), [adaptive caching](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/AdaptiveCaching.md) and everything else.

Honestly, the end result is a thing of beauty.

Here's an example:

```csharp
cache.Set("risotto_milanese", 123, tags: ["food", "yellow"]);
cache.Set("kimchi", 123, tags: ["food", "red"]);
cache.Set("trippa", 123, tags: ["food", "red"]);
cache.Set("sunflowers", 123, tags: ["painting", "yellow"]);

// [...]

// REMOVE ENTRIES WITH TAG "red"
cache.RemoveByTag("red");

// NOW ONLY "risotto_milanese" and "sunflowers" ARE IN THE CACHE

// [...]

// REMOVE ENTRIES WITH TAG "food"
cache.RemoveByTag("food");

// NOW ONLY "sunflowers" IS IN THE CACHE

It's really that simple.

How to make it work, to make it work well, and to make it work in a scalable and flexible way including support for all the resiliency features of FusionCache (eg: fail-safe, auto-recovery, etc) that is a completely different thing.

If you want to know more read here for the proposal, including a complete overview of the design I decided to use for the feature, which I think strikes a delicate balance of all considerations.

And, if you like, let me know your thoughts!

... (truncated)

1.4.1

This is a small version, just to update some transitive packages with security vulnerabilities.

⚠️ Update vulnerable dependencies

The affected transitive packages are:

  • System.Text.Json
  • MessagePack
  • Microsoft.Extensions.Caching.Memory

To that to update Microsoft.Extensions.Caching.Memory, an update for System.Diagnostics.DiagnosticSource was also needed.

This is all.

PS: if you have some time, please read the Tagging proposal (eg: evict by tag + clear), which is planned for v2.0. This will be one of the biggest and most important features of FusionCache ever. Any help is welcome!

1.4.0

[!IMPORTANT]
Although when updating to a new version it's the norm to update all referenced packages at once, this time is even more important. Because of a small change in the IFusionCacheSerializer interface, it is highly suggested to update all packages, including in transitive dependencies.

🚀 Add support for RecyclableMemoryStream to serializers (docs)

It is now possible to use RecyclableMemoryStreams with some of the supported serializers.
It's opt-in, meaning totally optional, and it's possible to use the default configuration or to specify one in the constructor, for maximum control and to fine-tune it however we want.

Thanks @​viniciusvarzea for the suggestion!

See here for the original issue.

🔭 Better observability for GET operations (docs)

Community user @​dotTrench noticed that when using OpenTelemetry, GET activities (TryGet, GetOrDefault, etc) were not including a useful bit of information: the hit/miss status.
While adding support for it via an extra tag, it was also noticed that it was not including another useful bit of information: the stale/fresh status.
Now this is solved, thanks to 2 new tags.

See here for the original issue.

💣 Throw specific exception when factory fails without fail-safe (docs)

Since v1.3.0 it's possible to trigger a factory fail without throwing an exception, so that fail-safe can kick in and do its thing.

But what happens if fail-safe is not enabled or if there's no stale value to fall back to?

Previously a plain Exception was being thrown, but that is hardly a best practice: now a more specific exception type has been created and will be thrown instead, namely FusionCacheFactoryException.

See here for the original issue.

🛑 Add cancellation support to serializers (docs)

Previously serializers did not support cancellation: now this is supported, via the standard CancellationTokens.

Thanks @​b-c-lucas for noticing it!

See here for the original issue.

🚀 Better perf for FusionCacheProvider (docs)

Community user @​MarkCiliaVincenti contributed with a PR that improved the performance of FusionCacheProvider, used with Named Caches, thanks to the use of FrozenDictionary.

See here for the original PR.

... (truncated)

1.3.0

♊ Auto-Clone (docs)

In general is never a good idea to mutate data retrieved from the cache: it should always be considered immutable/readonly.
To see why, read more in the docs.

Not all the scenarios where mutating a piece of data we got from the cache are necessarily wrong though, as users may have a particular use case where that may be needed, and ideally they should be abe to do that in an easy (and optimized!) way, by following the tried and true "it just works" mindset.

With Auto-Clone this is now possible.

A couple of details:

  • it just works, out of the box
  • is easy to use
  • doesn't require extra coding/setup (it's just a new EnableAutoClone option)
  • uses existing code infrastructure (eg: IFusionCacheSerializer)
  • has granular control on a per-entry basis
  • is performant (as much as possible)

Thanks to community users @​JarrodOsborne and @​kzkzg !

See here for the original issue.

💣 Fail-Safe Without Exceptions (docs)

Currently the way to activate fail-safe is for a factory to throw an exception.

This makes sense, since the whole point of fail-safe is to protect us when an error occurs while executing a factory.

But there may be other ways to do it, for example by using a variation of the Result Pattern or similar approaches, in which throwing an exception is not necessary.

This is now possible thanks to the new Fail(...) method on the FusionCacheFactoryExecutionContext<TValue> type, which we can access when executing a factory.

A quick example:

var productResult = await cache.GetOrSetAsync<Result<Product>>(
	$"product:{id}",
	async (ctx, ct) =>
	{
		var productResult = GetProductFromDb(id);

		if (productResult.IsSuccess == false)
		{
			return ctx.Fail(productResult.Error);
		}

		return productResult;
	},
	opt => opt.SetDuration(duration).SetFailSafe(true)
);
 ... (truncated)

## 1.2.0

## 🔑 Added DI Keyed Services support ([docs](https://github.com/ZiggyCreatures/FusionCache/blob/main/docs/DependencyInjection.md#-keyed-services))

Since .NET 8 we now have native support for multiple services of the same type, identified by different names, thanks to the addition of so called [keyed services](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0#keyed-services).

The idea is basically that we can now register services not only by type but also by specifying the name, like this:

```csharp
services.AddKeyedSingleton<MyService>("foo");
services.AddKeyedSingleton<MyService>("bar");

and later is possible to resolve it by both the type and a name.

Another way is to simply mark a constructor parameter or web action with the [FromKeyedServices] attribute, like this:

app.MapGet("/foo", ([FromKeyedServices("foo")] MyService myService) => myService.Whatever(123));
app.MapGet("/bar", ([FromKeyedServices("bar")] MyService myService) => myService.Whatever(123));

From now on, when registering a named cache, we can simply add AsKeyedServiceByCacheName() like this:

services.AddFusionCache("MyCache")
  .AsKeyedServiceByCacheName();

and later we'll be able to have the named cache both as usual:

app.MapGet("/foo", (IFusionCacheProvider cacheProvider) => {
  var cache = cacheProvider.GetCache("MyCache");
  cache.Set("key", 123);
});

and as a keyed service, like this:

app.MapGet("/foo", ([FromKeyedServices("MyCache")] IFusionCache cache) => {
  cache.Set("key", 123);
});

We can even use AsKeyedService(object? serviceKey) and specify a custom service key like for any other keyed service in .NET.

On top of being able to register FusionCache as a keyed service, we can even consume keyed services as FusionCache components, like memory cache, distributed cache, serializer, backplane, etc.

For more read at the official docs.

... (truncated)

1.2.0-preview1

🔑 Added DI Keyed Services support

Since .NET 8 we now have native support for multiple services of the same type, identified by different names, thanks to the addition of so called keyed services.

The idea is basically that we can now register services not only by type but also by specifying the name, like this:

services.AddKeyedSingleton<MyService>("foo");
services.AddKeyedSingleton<MyService>("bar");

and later is possible to resolve it by both the type and a name.

Another way is to simply mark a constructor parameter or web action with the [FromKeyedServices] attribute, like this:

app.MapGet("/foo", ([FromKeyedServices("foo")] MyService myService) => myService.Whatever(123));
app.MapGet("/bar", ([FromKeyedServices("bar")] MyService myService) => myService.Whatever(123));

From now on, when registering a named cache, we can simply add AsKeyedService() like this:

services.AddFusionCache("MyCache")
  .AsKeyedService();

and later we'll be able to have the named cache with something like this:

app.MapGet("/foo", ([FromKeyedServices("MyCache")] IFusionCache cache) => {
  cache.Set("key", 123);
});

Of course the named cache provider way is still available, like this:

app.MapGet("/foo", (IFusionCacheProvider cacheProvider) => {
  var cache = cacheProvider.GetCache("foo");
  cache.Set("key", 123);
});

See here for the original issue.

⚡ Add PreferSyncSerialization option

It has been observed that in some situations async serialization and deserialization can be slower than the sync counterpart: this has nothing to do with FusionCache itself, but how serialization works in general.

... (truncated)

1.1.0

The theme for this release is some bug fixes, general quality of life improvements and some minor perf optimizations.

📞 Added a couple of missing OnMiss events

Community user @​ConMur noticed that sometimes, in a couple of code paths related to distributed cache operations, FusionCache was missing some OnMiss events (no pun intended): now this has been fixed.

See here for the original issue.

💣 Better FailSafeMaxDuration handling

User @​F2 and user @​sabbadino both noticed that fail-safe max duration was not being respected all the times, particularly when multiple fail-safe activations actually occurred in sequence there was in fact the risk of extending the physical duration of the cache more than what should've been correct.
This has been fixed (while also introducing some nice memory and cpu savings!).

See here and here for the original issues.

💀 A rare case of deadlock

While doing some extensive testing community user @​martindisch discovered a rare case of deadlock that was happening only when all of these conditions were met simultaneously:

  • Eager Refresh enabled
  • call GetOrSet[Async] while passing a CancellationToken
  • the call that actually triggered the eager refresh is cancelled, after the eager refresh kicked in but before it finished
  • not all the times, but only when the execution flow passed in a certain spot at a certain time

This issue kicked off an experimentation about a reworking of FusionCache internals regarding the general theme of cancellations of background factory executions in general (eager refresh, background factory completion with soft/hard timeouts, etc): I am happy to say that now the deadlock is gone for good.
To do that well I slightly changed the behaviour of FusionCache regarding background factory executions: now they cannot be cancelled anymore by cancelling the original request that generated them, since it doesn't make that much sense to begin with, since a cancellation is used to cancel the current operation, but a background execution (particularly with eager refresh) is basically a side effect, which does have a life of its own, so it doesn't make a lot of sense to cancel that, too.

All in all, there should be realistically no discernible externally observable difference in behaviour (and no more deadlocks!).

Finally, I've added some tests to detect these scenario to avoid future regressions.

See here for the original issue.

📢 Better AutoRecoveryDelay default value

The default value for AutoRecoveryDelay has been changed from 2s to 5s, to better align with the standard reconnect timing of StackExchange.Redis, which is the most commonly used implementation for the distributed cache and the backplane.
The idea is about "sensible defaults" and the overarching theme of "it just works": if the default distributed cache and backplane are Redis, let's just make sure that the defualt experience is better aligned with that (and also, when bad things happen in production, automatically recovering from it with a slightly longer delay is, pragmatically, really not a big deal).

🧽 Some code cleanup

Thanks to @​SimonCropp the code has been cleaned up a little bit here, updated to the latest C# features there, plus some other minor tweaks. Thanks Simon!

🚀 Performance

In this release I've been able to squeeze in some minor but nice memory/cpu optimizations.

✅ Better tests

I added some more tests to have a higher code coverage.

📕 Docs

... (truncated)

1.0.0

FusionCache is now v1.0 🥳

Yes, it finally happened.

Let's see what this release includes.

🚀 Performance, performance everywhere

FusionCache always tried to be as optimized as possible, but sometimes useful new features took some precedence over micro-optimizing this or that.

Now that all the major features (and then some) are there, it was time to do a deep dive and optimize a cpu cycle here, remove an allocation there and tweak some hot path to achieve the best use of resources.

So here's a non-comprehensive list of nice performance improvements in this release:

  • zero allocations/minimal cpu usage in Get happy path
  • reduced allocations/cpu usage in Set happy path
  • less allocations/cpu usage when not using distributed components
  • less allocations/cpu usage (via closures) when using events
  • zero allocations at all when not using logging (no support structures init for operationId generation)
  • reduced overhead in some async code paths

Oh, and thanks to community member @​neon-sunset for the issue highlighting some shortcomings, that now have been solved!

See here for the issue.

🦅 Better Eager Refresh (docs)

When executing an Eager Refresh, the initial check for an updated cache entry on the distributed cache is now totally non-blocking, for even better performance.

🆕 Added IgnoreIncomingBackplaneNotifications option (docs)

FusionCache always allowed to optionally skip sending backplane notifications granularly, for each operation (or globally thanks to DefaultEntryOptions): it was not possible though to ignore receiving them.

Now we may be thinking "why would I want to use a backplane, but not receive its notifications?" and the answer to that can be found in the feature request made by community member @​celluj34 .

See here for the issue.

⚠️ Better nullability annotations for generic types

This is linked to the evolution of nullable reference types, nullables with generics and the related static analysis with each new version of c# and its compiler.

Along the years I tried to adjust the annotations to better handle generic types + nullables with each new version, because what the compiler allowed me to do and was able to infer changed at every release (the first version had problems with generics without where T : class/struct constraints, for example).

I've now updated them to reflect the latest behaviour, so that it's now more strict in the generic signatures, mostly for GetOrSet<T> and GetOrSetAsync<T>: in previous versions the return type was always nullable, so when calling GetOrSet<Person> we would have a return value of Person? (nullable) even if the call was not GetOrSet<Person?>.

Now this is better.

Thanks for community member @​angularsen for highlighting this.
... (truncated)

1.0.0-preview2

[!IMPORTANT]
Yep, it's almost v1.0 time!

Please try this preview2 release and let me know if you find any issue, so the v1.0 can be as good as possible: from now until v1.0 is out I will no longer consider requests for new features.

Thanks 🙏

🚀 Performance, performance everywhere

FusionCache always tried to be as optimized as possible, but sometimes useful new features took some precedence over micro-optimizing this or that.

Now that all the major features (and then some) are there, i...

Description has been truncated

[![Dependabot compatibility score](ht...

Description has been truncated

---
updated-dependencies:
- dependency-name: ZiggyCreatures.FusionCache
  dependency-version: 2.9.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Oct 2, 2026
@dependabot @github

dependabot Bot commented on behalf of github Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Looks like ZiggyCreatures.FusionCache is no longer being updated by Dependabot, so this is no longer needed.

@dependabot dependabot Bot closed this Oct 2, 2026
@dependabot
dependabot Bot deleted the dependabot/nuget/beta-censoring/src/BetaCensor.Caching/ZiggyCreatures.FusionCache-2.9.0 branch October 2, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants