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 the SQL Server distributed cache's ValidateOptions when neither an absolute nor a sliding expiration is supplied for a cache entry. The backing stored procedure requires at least one expiration type to schedule deletion, so the cache refuses entries with no expiration policy.

Solutions

  1. Set at least one of AbsoluteExpiration, AbsoluteExpirationRelativeToNow, or SlidingExpiration on the options instance.
  2. Prefer SlidingExpiration for session-like data or AbsoluteExpirationRelativeToNow for fixed TTL.
  3. If you want a global default, wrap the cache and apply a default expiration before delegating to SetAsync.

Example fix

// before: no expiration
var opts = new DistributedCacheEntryOptions();
await cache.SetStringAsync(key, value, opts);

// after: provide a sliding expiration
var opts = new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(30) };
await cache.SetStringAsync(key, value, opts);
Defensive patterns

Strategy: validation

Validate before calling

if (!options.AbsoluteExpiration.HasValue && !options.AbsoluteExpirationRelativeToNow.HasValue && !options.SlidingExpiration.HasValue) { throw new InvalidOperationException("Set at least one expiration."); }

Type guard

static bool HasExpiration(DistributedCacheEntryOptions o) => o.AbsoluteExpiration.HasValue || o.AbsoluteExpirationRelativeToNow.HasValue || o.SlidingExpiration.HasValue;

Prevention

When it happens

Trigger: Calling SetAsync with a DistributedCacheEntryOptions that has neither AbsoluteExpiration, AbsoluteExpirationRelativeToNow, nor SlidingExpiration set — i.e. all three are null.

Common situations: Default-constructing DistributedCacheEntryOptions and forgetting to set any expiration; refactoring that removed the expiration line; assuming the cache provides a default lifetime (it does not at this code path).

Related errors


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

Appendix: 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 3600ca084e)