dotnet/aspnetcore · error · InvalidOperationException
Either absolute or sliding expiration needs to be provided.
Error message
Either absolute or sliding expiration needs to be provided.
What it means
Thrown by DatabaseOperations.ValidateOptions (line 395) when neither SlidingExpiration nor an absolute expiration is configured on the DistributedCacheEntryOptions passed to Set/SetAsync. The SqlServer cache requires at least one expiration policy per item. The exception type is InvalidOperationException. Note: SqlServerCache.GetOptions (line 283) fills in a DefaultSlidingExpiration when all three option properties are absent, so this only fires if GetOptions is bypassed or DefaultSlidingExpiration is itself unset.
Source
Thrown at src/Caching/SqlServer/src/DatabaseOperations.cs:395
absoluteExpiration = utcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);
}
else if (options.AbsoluteExpiration.HasValue)
{
if (options.AbsoluteExpiration.Value <= utcNow)
{
throw new InvalidOperationException("The absolute expiration value must be in the future.");
}
absoluteExpiration = options.AbsoluteExpiration.Value;
}
return absoluteExpiration;
}
private static void ValidateOptions(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)
{
if (!slidingExpiration.HasValue && !absoluteExpiration.HasValue)
{
throw new InvalidOperationException("Either absolute or sliding expiration needs " +
"to be provided.");
}
}
}
View on GitHub (pinned to 294cab2f9b)
Solutions
- Always set at least one expiration: options.SlidingExpiration = TimeSpan.FromMinutes(20), or AbsoluteExpirationRelativeToNow, or AbsoluteExpiration.
- If using SqlServerCache, ensure SqlServerCacheOptions.DefaultSlidingExpiration is configured (defaults to 20 minutes) so GetOptions fills the gap.
- Call the public IDistributedCache.SetAsync API (which routes through GetOptions) rather than internal DatabaseOperations.
Example fix
// before — no expiration set (triggers if GetOptions bypassed)
var opts = new DistributedCacheEntryOptions();
await cache.SetAsync("key", value, opts);
// after — always set an expiration
var opts = new DistributedCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromMinutes(20)
};
await cache.SetAsync("key", value, opts); Defensive patterns
Strategy: validation
Validate before calling
// Ensure at least one expiration is set
if (!options.AbsoluteExpiration.HasValue
&& !options.AbsoluteExpirationRelativeToNow.HasValue
&& !options.SlidingExpiration.HasValue)
{
options.SlidingExpiration = TimeSpan.FromMinutes(20);
}
await cache.SetAsync(key, value, options); Type guard
static bool HasExpiration(DistributedCacheEntryOptions opts)
=> opts.AbsoluteExpiration.HasValue
|| opts.AbsoluteExpirationRelativeToNow.HasValue
|| opts.SlidingExpiration.HasValue; Try / catch
try
{
await cache.SetAsync(key, value, options);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("absolute or sliding"))
{
options.SlidingExpiration = TimeSpan.FromMinutes(20);
await cache.SetAsync(key, value, options);
} Prevention
- Configure SqlServerCacheOptions.DefaultSlidingExpiration so GetOptions fills the gap.
- Always set at least one expiration property per cache entry.
When it happens
Trigger: SetCacheItem/SetCacheItemAsync calls DatabaseOperations.ValidateOptions (line 136/176) with both slidingExpiration and absoluteExpiration null. In normal SqlServerCache usage, GetOptions substitutes _defaultSlidingExpiration, so this is primarily a concern when calling DatabaseOperations directly or if DefaultSlidingExpiration was somehow zero/nulled.
Common situations: Calling DatabaseOperations directly instead of via SqlServerCache; passing a new DistributedCacheEntryOptions() with no properties set while bypassing SqlServerCache.GetOptions; a custom IDistributedCache wrapper that strips options before delegating.
Related errors
- The absolute expiration value must be in the future.
- ExpiredItemsDeletionInterval cannot be less than the minimum
- The sliding expiration value must be positive.
- The absolute expiration value must be in the future.
- Circuit options have already been configured.
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/3da2af16ae708981.
Report an issue: GitHub.