Prepare v2.0.0 branch for master#3
Merged
Merged
Conversation
…FM, stampede protection - Replace MemoryCache-specific API with pluggable ICleverCacheStore abstraction - MemoryCacheStore (default), DistributedCacheStore (IDistributedCache/JSON) - CleverCacheEntryOptions replaces MemoryCacheEntryOptions - Fluent DI: UseMemoryCache(), UseDistributedCache(), UseCustomStore<T>() - Extract MediatR integration into separate CleverCache.MediatR NuGet package - Main package no longer has any MediatR dependency - Add CleverCache.Redis NuGet package - UseRedisCache(string) and UseRedisCache(Action<RedisCacheOptions>) convenience methods - Central Package Management (Directory.Build.props + Directory.Packages.props) - Shared metadata (authors, repo URL, license) in one place - Per-TFM conditional package versions - Multi-target net9.0 and net10.0 with stable packages only - net9.0: EF Core 9.0.9, Extensions.Caching 9.0.9, Redis 9.0.9 - net10.0: EF Core 10.0.7, Extensions.Caching 10.0.7, Redis 10.0.7 - Cache stampede protection via AsyncKeyedLock 8.0.2 - Per-key locking (not global) prevents nested cached call deadlocks - Double-checked locking pattern on both sync and async paths - Updated ReadMe with corrected usage examples, cache options, custom store guide Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add CleverCache.Tests project with 33 tests covering:
- CleverCacheService: cache hit/miss, remove, stampede protection,
and deadlock regression test for nested cached calls
- CacheEntryManager: type-to-key tracking, dependencies, cyclic guard
- MemoryCacheStore and DistributedCacheStore: get/set/remove basics
- FakeCache: always-calls-factory behaviour
- AutoCacheBehaviour: MediatR pipeline attribute handling
- Fix DistributedCacheStore key encoding: remove type-name prefix so
Remove(key) correctly matches keys stored via Set/SetAsync
- Add InternalsVisibleTo(CleverCache.Tests) in CleverCache.MediatR.csproj
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ICacheEntryManager was an early draft of the cache provider abstraction, now superseded by ICleverCacheStore. Its two members (AddDependentCache, AddKeyToTypes) are moved directly onto ICleverCache where they always appeared via inheritance. - Delete ICacheKeyManager.cs - ICleverCache: remove : ICacheEntryManager, add members directly - CacheEntryManager: make internal (implementation detail) - CleverCacheService: make internal (users always inject ICleverCache) - CacheEntryManagerExtensions: extend ICleverCache instead of ICacheEntryManager - CleverCache.csproj: add InternalsVisibleTo(CleverCache.Tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delete CacheTypeMap.cs - unused record, dead code - Rename DependantCachesAttribute to DependentCachesAttribute (typo fix) - Remove commented-out Attribute value from DependentCacheNavigationScanMode - MissingInterceptorException: inherit from Exception not ApplicationException Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FakeCache was in CleverCache.Implementations alongside internal classes, making it awkward for users to discover and import. - FakeCache.cs: change namespace to CleverCache - FakeCacheTests.cs: remove now-unnecessary using CleverCache.Implementations - ReadMe.md: rewrite testing section - fix typos, clarify MediatR note, add guidance on using Mock<ICleverCache> for interaction verification Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Automatic cache invalidation requires EF Core (SaveChangesInterceptor). Add prominent callout explaining: - Invalidation fires on SaveChanges/SaveChangesAsync via change tracker - Raw SQL / stored procs / external writes won't auto-invalidate; use RemoveByType<T>() manually - Without EF Core at all, CleverCache still adds value - RemoveByType<T>() handles the full dependency tree so callers don't need to track cascades Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move MediatR from buried one-liner to a dedicated callout above the EF Core requirements note - it's a key selling point and should be seen immediately after the intro, not lost after a large blockquote. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tensions Part 1 - [InvalidatesCache] for MediatR commands (CleverCache.MediatR): - InvalidatesCacheAttribute: decorate commands with types to invalidate - InvalidateCacheBehaviour: clears declared types after handler succeeds; skips invalidation if handler throws - Both behaviours auto-registered by cfg.AddCleverCache() - NuGetReadMe updated with command invalidation quick start Part 2 - Fluent InvalidateCaches() for EF bulk ops (main CleverCache): - BulkOperationExtensions: InvalidateCaches(this int, ...) and InvalidateCaches(this Task<int>, ...) — no Async suffix needed since receiver types differ; generic overloads avoid typeof() - Returns row count passthrough for drop-in compatibility Tests: 11 new tests, 44 total (up from 33) Docs: README bulk operations section + MediatR [InvalidatesCache] section Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Trim README to quick-start + wiki links - Add 8 wiki pages (Getting-Started, Cache-Providers, Caching-Data, Dependent-Caches, MediatR-Integration, Bulk-Operations, Unit-Testing, Home) - Improve XML docs on ICleverCache, CacheEntryManagerExtensions, AutoCacheAttribute, InvalidatesCacheAttribute, DependentCachesAttribute - Add PackageProjectUrl to all 3 production csproj files - Fix duplicate namespace bug in DependentCachesAttribute.cs - Add ScanAssemblyContaining<T>() and ScanAssemblies() to CleverCacheOptions for attribute-based dependency registration at AddCleverCache() time - Update CleverCacheService constructor to initialise dependency tree from CleverCacheOptions.DependentCaches - Fix UseCleverCache<TContext>() resolving scoped DbContext from root provider - Add AssemblyScanningTests (5 tests); 49/49 passing on net9 and net10 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…kage - New CleverCache.EntityFrameworkCore project with interceptor, AddCleverCacheEntityFramework(), ScanDbSetsForCacheDependencies<TContext>(), navigation scanning helpers, and DbContext/DbOptionsBuilder extensions - Core package now has zero EF Core or AspNetCore.Http dependencies - CleverCacheOptions.Scanning / DisableAllScanning removed (breaking) - UseCleverCache replaced by ScanDbSetsForCacheDependencies (breaking) - DependentCachesAttribute XML doc updated for new API - wiki/Getting-Started.md updated throughout for new package split Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CleverCacheInterceptorTests: 7 tests covering Add/Modify/Delete, deduplication of same-type entities, multiple types, no-changes, async - DbContextExtensionsTests: 6 tests covering EnsureCleverCacheInterceptor (with/without), direct and recursive navigation scanning, None mode, and DependentCaches attribute discovery - Added CleverCache.EntityFrameworkCore project reference to test project - Added Microsoft.EntityFrameworkCore.InMemory package for both targets - Added InternalsVisibleTo(CleverCache.Tests) to EF package 62/62 tests passing on net9.0 and net10.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Covers all breaking changes with before/after examples: - New CleverCache.EntityFrameworkCore package requirement - AddCleverCacheEntityFramework() registration - UseCleverCache -> ScanDbSetsForCacheDependencies - Scan options moved off CleverCacheOptions - GetOrCreate factory signature (ICacheEntry removed, CleverCacheEntryOptions) - [DependantCaches] -> [DependentCaches] spelling fix - FakeCache namespace change - New V2 features section Also updated Home.md to link migration page and add EF Core package row Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Single registration call for EF Core users. AddCleverCacheEntityFramework now accepts Action<CleverCacheOptions>? and calls AddCleverCache internally, so users do not need to call both. Updated Getting-Started and Migrating-to-V2 wiki docs accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sForCacheDependencies Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove NavigationScanMode from DependentCachesAttribute - it was only used by DbContextExtensions.ProcessAttribute, which no longer exists. Attributes and navigation scanning are now fully independent concepts. - Remove ProcessAttribute from DbContextExtensions; DiscoverDependentCaches is now navigation-scanning-only (None guard returns early) - Change CleverCacheScanOptions default NavigationScanMode to Direct so a no-arg ScanDbSetsForCacheDependencies() call does something useful - Rewrite wiki/Dependent-Caches.md: remove stale Scenario 3 (per-entity attribute navigation), remove Scenario 3/4 interaction table, rename Scenario 4 to Scenario 3, update all API references to V2 names - Update wiki/Migrating-to-V2.md step 5: note navigationScanMode removal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…on guide Reorder steps so attribute rename (4) and attribute registration (5) immediately follow the UseCleverCache rename (3) they are related to. Factory signature change moves to step 6. Trims the now-redundant callout in step 3 to a brief forward reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add CleverCacheDiagnostics record with Dependants and KeysByType - Add GetDiagnostics() to ICleverCache, CleverCacheService, FakeCache - Add SnapshotDiagnostics() protected method to CacheEntryManager - Add RenderDependencyTree() extension method on ICleverCache producing a readable tree of cascade rules and tracked keys per type - 4 new tests (65 total, all passing) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…cyTree Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- GetOrCreateAsync now accepts CancellationToken and passes it to store - Add RemoveByTypeAsync(Type, CancellationToken) to ICleverCache, CleverCacheService, FakeCache - Add RemoveByTypeAsync<T> generic extension in CacheEntryManagerExtensions - CleverCacheInterceptor.SavedChangesAsync now awaits RemoveByTypeAsync (sync SavedChanges path unchanged) - InvalidateCacheBehaviour switches to await RemoveByTypeAsync - AutoCacheBehaviour passes cancellationToken to GetOrCreateAsync - All tests updated; 2 new tests (67 total) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- publish-wiki.yml now also calls 'gh repo edit' to keep the repo description, homepage URL, and topics in sync after wiki publishes - Add Publish Wiki status badge to README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- New update-about.yml workflow extracts <Description> and <PackageTags> from CleverCache.csproj and syncs them to the GitHub repo About section - Remove the hardcoded gh repo edit step from publish-wiki.yml Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ules The Any(x => x.Type == dependentType) guard checked whether the dependent type already appeared as a *source* in the set — this wrongly skipped rules when a bidirectional navigation caused the same type to appear on both sides (e.g. OrderLine -> Order was dropped because Order was already a source in Order -> OrderLine). Fix: remove the incorrect guard (HashSet<DependentCache> record equality already deduplicates exact rules for free) and replace with a proper visited-set cycle guard that only applies in Recursive mode. Add regression tests: - BidirectionalNavigation_RegistersBothDirections (the exact bug) - CircularNavigation_DoesNotStackOverflow (cycle guard) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The enum is an EF-specific concept and was only ever used by CleverCache.EntityFrameworkCore. Moving it removes an EF concern from the core package. Breaking change: namespace changes from CleverCache.Models to CleverCache.EntityFrameworkCore.Models. Noted in migration guide. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the handler legitimately returned null, the old code removed the cache entry and called next() a second time. GetOrCreateAsync already invoked the factory (and thus the handler) on a cache miss, so the second call was always a duplicate. Fix: propagate null via 'result ?? default!' rather than re-executing the handler. Comment explains why default! is used to satisfy the non-nullable TResponse return type. Add regression test: NullResult_DoesNotCallNextTwice (70 total) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Enables navigation scanning in worker services and console apps where IApplicationBuilder is not available. The existing IApplicationBuilder overload now delegates to the new IServiceProvider implementation. Update Getting-Started wiki to document both overloads. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keys tracked in _keysByType were never removed when cache entries were explicitly evicted, causing unbounded growth in long-running apps. Fix: after removing a key from the store, remove it from all type sets via RemoveKeyFromAllTypes. This covers: - Remove(key): cleans up the specific key across all type sets - RemoveByType(type): cleans up each invalidated key across all type sets (including dependent types that shared the key via transitive expansion) Natural expiry (TTL) still leaks — that requires eviction callbacks which will be addressed separately. Add regression tests: Remove_CleansUpKeyFromAllTypeSets and RemoveByType_CleansUpKeysFromAllTypeSets (72 total) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a cache entry expires naturally (TTL) it was never removed from _keysByType, causing unbounded memory growth. Add IEvictionNotifyingStore — an optional interface that cache stores implement to signal evictions back to the service layer: - MemoryCacheStore implements it via IMemoryCache PostEvictionCallback - DistributedCacheStore does not (distributed caches have no eviction API) - CleverCacheService wires RemoveKeyFromAllTypes as the callback if the store implements the interface Custom store authors can opt in by implementing IEvictionNotifyingStore. Add tests: RegisterEvictionCallback_CalledWhenEntryEvicted and Eviction_WhenStoreSupportsNotification_CleansUpKeyFromTypeSets (74 total) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cache-Providers.md: add 'Eviction notifications' section under Custom provider explaining how to implement IEvictionNotifyingStore and when to use it - Diagnostics.md: update stale note — keys are now removed on explicit Remove/RemoveByType and on natural expiry (for stores that implement IEvictionNotifyingStore); clarify distributed cache behaviour Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Microsoft.EntityFrameworkCore 9.0.9 -> 9.0.15 - Microsoft.EntityFrameworkCore.InMemory 9.0.9 -> 9.0.15 - Microsoft.Extensions.Caching.Abstractions 9.0.9 -> 9.0.15 - Microsoft.Extensions.Caching.Memory 9.0.9 -> 9.0.15 - Microsoft.Extensions.Caching.StackExchangeRedis 9.0.9 -> 9.0.15 - Microsoft.Extensions.Hosting.Abstractions 9.0.9 -> 9.0.15 - xunit.runner.visualstudio 2.8.2 -> 3.1.5 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous workflow had two bugs:
- Used GITHUB_TOKEN which lacks administration permission to update
repo description and topics (was silently failing)
- Used --add-topic which only adds, never removes stale topics
Fix:
- Switch to GH_PAT secret (Fine-grained PAT, Administration R/W)
- Use 'gh api PUT /repos/{repo}/topics' to atomically replace all topics
from <PackageTags> in CleverCache.csproj (lowercase, spaces to hyphens)
- Split into two steps for clarity
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Runs on pull_request (targeting main) and push to main - Installs .NET 9 and 10, restores, builds, and tests - Add CI badge to README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Target net8.0;net9.0;net10.0 in Directory.Build.props
- Add net8.0 condition block in Directory.Packages.props
- EF Core 8.0.15 (has 8.x patch releases)
- Microsoft.Extensions.* pinned to 9.0.15 (8.0.0 has known
vulnerability GHSA-qj66-m88j-hmgj; 9.x supports net8.0 TFM)
- StackExchangeRedis 8.0.15 (has 8.x patch releases)
- Install .NET 8 in ci.yml workflow
- 74/74 tests passing on net8.0, net9.0, and net10.0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace xunit 2.9.3 with xunit.v3 3.2.2 - Add OutputType=Exe to test project (v3 produces stand-alone executables) - Fix xUnit1031: convert eviction callback test to async Task - Fix xUnit1051: pass TestContext.Current.CancellationToken to all async method calls that accept a CancellationToken across all test files 74/74 tests passing on net8.0, net9.0, and net10.0 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
copilot/v2-refactor) intomasterNotes