{"record":{"id":"3da2af16ae708981","repo":"dotnet/aspnetcore","slug":"either-absolute-or-sliding-expiration-needs-to-be","errorCode":null,"errorMessage":"Either absolute or sliding expiration needs to be provided.","messagePattern":"Either absolute or sliding expiration needs to be provided\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Caching/SqlServer/src/DatabaseOperations.cs","lineNumber":395,"sourceCode":"            absoluteExpiration = utcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);\n        }\n        else if (options.AbsoluteExpiration.HasValue)\n        {\n            if (options.AbsoluteExpiration.Value <= utcNow)\n            {\n                throw new InvalidOperationException(\"The absolute expiration value must be in the future.\");\n            }\n\n            absoluteExpiration = options.AbsoluteExpiration.Value;\n        }\n        return absoluteExpiration;\n    }\n\n    private static void ValidateOptions(TimeSpan? slidingExpiration, DateTimeOffset? absoluteExpiration)\n    {\n        if (!slidingExpiration.HasValue && !absoluteExpiration.HasValue)\n        {\n            throw new InvalidOperationException(\"Either absolute or sliding expiration needs \" +\n                \"to be provided.\");\n        }\n    }\n}\n","sourceCodeStart":377,"sourceCodeEnd":400,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/Caching/SqlServer/src/DatabaseOperations.cs#L377-L400","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — no expiration set (triggers if GetOptions bypassed)\nvar opts = new DistributedCacheEntryOptions();\nawait cache.SetAsync(\"key\", value, opts);\n\n// after — always set an expiration\nvar opts = new DistributedCacheEntryOptions\n{\n    SlidingExpiration = TimeSpan.FromMinutes(20)\n};\nawait cache.SetAsync(\"key\", value, opts);","handlingStrategy":"validation","validationCode":"// Ensure at least one expiration is set\nif (!options.AbsoluteExpiration.HasValue\n    && !options.AbsoluteExpirationRelativeToNow.HasValue\n    && !options.SlidingExpiration.HasValue)\n{\n    options.SlidingExpiration = TimeSpan.FromMinutes(20);\n}\nawait cache.SetAsync(key, value, options);","typeGuard":"static bool HasExpiration(DistributedCacheEntryOptions opts)\n    => opts.AbsoluteExpiration.HasValue\n       || opts.AbsoluteExpirationRelativeToNow.HasValue\n       || opts.SlidingExpiration.HasValue;","tryCatchPattern":"try\n{\n    await cache.SetAsync(key, value, options);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"absolute or sliding\"))\n{\n    options.SlidingExpiration = TimeSpan.FromMinutes(20);\n    await cache.SetAsync(key, value, options);\n}","preventionTips":["Configure SqlServerCacheOptions.DefaultSlidingExpiration so GetOptions fills the gap.","Always set at least one expiration property per cache entry."],"tags":["caching","sqlserver","distributed-cache","configuration","expiration"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}