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

  1. Always set at least one expiration: options.SlidingExpiration = TimeSpan.FromMinutes(20), or AbsoluteExpirationRelativeToNow, or AbsoluteExpiration.
  2. If using SqlServerCache, ensure SqlServerCacheOptions.DefaultSlidingExpiration is configured (defaults to 20 minutes) so GetOptions fills the gap.
  3. 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

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


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/3da2af16ae708981. Report an issue: GitHub.