Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
343 commits
Select commit Hold shift + click to select a range
d081413
Added `Attributes` to `ITopicBackingAccessor`
JeremyCaney Jul 7, 2026
2c4e23d
Ensured access through `ITopicBackingAccessor`
JeremyCaney Jul 7, 2026
5cdf644
Clear associations after successful database read
JeremyCaney Jul 7, 2026
dc975bf
Account for associations in `SqlTopicRepository`
JeremyCaney Jul 7, 2026
2e4b125
Always load ancestors unless loading the root
JeremyCaney Jul 7, 2026
257545e
Dynamically load associations in `EnsureLoad()`
JeremyCaney Jul 7, 2026
f7a86d8
Trigger lazy-loading associations from `Topic`
JeremyCaney Jul 7, 2026
6e9cde8
Ensure `Deferred` are cleared in prep for new test
JeremyCaney Jul 8, 2026
aff6491
Prefer collection expressions over explicit `new`
JeremyCaney Jul 8, 2026
2e536be
Use `IsTopic()` to bypass lazy-loading
JeremyCaney Jul 8, 2026
e8f455f
Don't use null-forgiving operator after null check
JeremyCaney Jul 8, 2026
e39b605
Remove implicit nulls for `parentId`
JeremyCaney Jul 8, 2026
56379ac
Prefer implicit constructors when type is known
JeremyCaney Jul 8, 2026
45feabe
Fixed syntax errors with XML docblocks
JeremyCaney Jul 8, 2026
7b56929
Avoid multiple enumeration via `ToList()`
JeremyCaney Jul 8, 2026
a3b9a21
Prefer `Count` to `Any()`
JeremyCaney Jul 8, 2026
3100cf6
Minor grammatical clean-up in XML docblocks
JeremyCaney Jul 8, 2026
e2b8f75
Unit tests for dynamic loading associations
JeremyCaney Jul 8, 2026
a3b7360
Move `Load()`, `EnsureLoaded()` to be `async`
JeremyCaney Jul 8, 2026
caab55a
Move `Save()`, `Move()`, `Delete()`, &c to `async`
JeremyCaney Jul 8, 2026
eb5c6a3
Move xunit to async tests
JeremyCaney Jul 9, 2026
688c69b
Prefer collection expressions (cont.)
JeremyCaney Jul 9, 2026
5c6bd85
Prefer implicit constructors if type known (cont.)
JeremyCaney Jul 9, 2026
7bf98af
Ensure assignments are aligned at Character 33
JeremyCaney Jul 9, 2026
6682c49
Ensured all files end on the last line
JeremyCaney Jul 9, 2026
6e8fb7d
Corrected XML docblock `cref` references
JeremyCaney Jul 9, 2026
b280e43
Reintroduced types for constructors
JeremyCaney Jul 9, 2026
e05427f
Prefer implicit constructors if type known (cont.)
JeremyCaney Jul 9, 2026
0afc984
Accept implicit base/default constructor
JeremyCaney Jul 9, 2026
691f5b9
Prefer implicit constructors if type known (cont.)
JeremyCaney Jul 9, 2026
f6bfb68
Remove unused `using` statements
JeremyCaney Jul 9, 2026
13264ca
Removed implicit, default arguments
JeremyCaney Jul 9, 2026
5b293fd
Fixed gap in "stamping" of `ITopicLoadResolver`
JeremyCaney Jul 9, 2026
27265c6
Remove unneceasary, implicit overloads, parameters
JeremyCaney Jul 9, 2026
f93a982
Established new `LazyLoadingTopicRepository`
JeremyCaney Jul 10, 2026
70005a1
Move `StampResolver()` to `LazyLoading` repository
JeremyCaney Jul 10, 2026
b787626
Centralized `ResolveDeferredAssociations()`
JeremyCaney Jul 10, 2026
1935995
Clear deferred in `ResolveDeferredAssociations()`
JeremyCaney Jul 10, 2026
cf376a7
Implemented `ResolveDeferredAssociations()` in SQL
JeremyCaney Jul 10, 2026
4caa4b4
FIre `TopicLoaded` event for children in SQL
JeremyCaney Jul 10, 2026
cd21597
Move stamping w/in `LazyLoadingTopicRepository`
JeremyCaney Jul 11, 2026
7ad38df
Manage index via `OnTopicLoaded()`
JeremyCaney Jul 11, 2026
1853602
Ensure cache misses called with same arguments
JeremyCaney Jul 11, 2026
43ba647
Reformat `Load()` calls
JeremyCaney Jul 11, 2026
29969d1
Introduce and wire-up `StampAscendants()`
JeremyCaney Jul 11, 2026
60276b8
Added test for the new `StampAscendants()`
JeremyCaney Jul 11, 2026
b815da7
Tidied up indentation, formatting
JeremyCaney Jul 11, 2026
7251476
Established new `VersionHistoryCollection`
JeremyCaney Jul 11, 2026
609b5aa
Use `VersionHistoryCollection` on `VersionHistory`
JeremyCaney Jul 11, 2026
10959ed
Added `VersionHistory` to `ITopicBackingAccessor`
JeremyCaney Jul 11, 2026
8fa4a93
Added `VersionHistory` to `TopicPayload`
JeremyCaney Jul 11, 2026
d9723f0
`VersionHistory` in `IsLoaded()`, `SetLoadState()`
JeremyCaney Jul 11, 2026
155a605
Set `LoadState` in `SetVersionHistory()`
JeremyCaney Jul 11, 2026
b28a31a
Lazy-load `VersionHistory` in `EnsureLoaded()`
JeremyCaney Jul 11, 2026
dea4bc9
Lazy-load `VersionHistory` in `Load()`
JeremyCaney Jul 11, 2026
09f4d0b
Wire-up dynamic loading of `VersionHistory`
JeremyCaney Jul 11, 2026
d70f751
Reviewed and tidied up XML docblock remarks
JeremyCaney Jul 11, 2026
ec9f740
Name `LoadTopicGraphAsync()` as `LoadTopicGraph()`
JeremyCaney Jul 11, 2026
f4a3e53
Merged `FillChildrenAsync()` into `FillChildren()`
JeremyCaney Jul 11, 2026
c5c1267
Added test for deferred `VersionHistory` state
JeremyCaney Jul 11, 2026
ba648d7
Added `CancellationToken` to all async test calls
JeremyCaney Jul 11, 2026
cf92ed3
Default `Load()` to a sparse, lazy-loaded graph
JeremyCaney Jul 11, 2026
864d712
Introduced `TopicRecord` for testing
JeremyCaney Jul 14, 2026
198ae09
Introduced `StubLazyLoadingTopicRepositoryBuilder`
JeremyCaney Jul 14, 2026
aa2c476
Introduced `StubLazyLoadingTopicRepository`
JeremyCaney Jul 14, 2026
a159b61
Renamed `ITopicLoadResolver` to `ITopicLazyLoader`
JeremyCaney Jul 14, 2026
9c88454
Rename to `Resolve(r)` to `Load(er)`
JeremyCaney Jul 14, 2026
1007d6a
Introduce `ITopicLazyLoadable`
JeremyCaney Jul 14, 2026
248411e
Make `ITopicLazyLoadable` an explicit interface
JeremyCaney Jul 14, 2026
a2d63a7
Ensured calls to `EnsureLoaded()` are `await`ed
JeremyCaney Jul 14, 2026
e66cea7
Move `ITopicLazyLoadable` methods inline
JeremyCaney Jul 14, 2026
2fe5427
Remove unused `IsDirty()`, `MarkClean()` overloads
JeremyCaney Jul 14, 2026
687d1ef
Introduced new `DeferredAssociationCollection`
JeremyCaney Jul 14, 2026
f2add3a
Implemented new `DeferredAssociationCollection`
JeremyCaney Jul 14, 2026
50f4376
Rely on constructor `isIncoming` over method param
JeremyCaney Jul 14, 2026
482a4ff
Mark `Source` as `private protected`
JeremyCaney Jul 14, 2026
1b602d0
Ensure reset of `Attributes.LoadState`
JeremyCaney Jul 14, 2026
47769ad
Introduced the new `ConvergeLoadState()` method
JeremyCaney Jul 15, 2026
c998de1
Applied `ConvergeLoadState()` to `AddChildTopic()`
JeremyCaney Jul 15, 2026
9a7594a
Apply `ConvergeLoadState()` to `LoadTopicGraph()`
JeremyCaney Jul 15, 2026
86aaf98
Introduced unit tests for `LoadTopicGraph()`
JeremyCaney Jul 15, 2026
5da1190
Support `Load(TopicPayload.Children)`
JeremyCaney Jul 15, 2026
f247c23
Add unit test for `Load(TopicPayload.Children)`
JeremyCaney Jul 15, 2026
b339d61
Support `Topic.IsLoaded(..., isRecursive)`
JeremyCaney Jul 15, 2026
8893273
Established tests for `IsLoaded(..., isRecursive)`
JeremyCaney Jul 15, 2026
d578cbf
Relocated tests to `ITopicLazyLoadableTest`
JeremyCaney Jul 15, 2026
167067f
Introduced `ResolveAssociations()`
JeremyCaney Jul 16, 2026
4037de9
Filter out `IsNew` in `TopicIndex`
JeremyCaney Jul 16, 2026
4f11cf8
Fixed offset to avoid collisions
JeremyCaney Jul 16, 2026
012c37c
Introduce `EnsureLoaded(…, isRecursive)` overload
JeremyCaney Jul 16, 2026
e0959e8
Added `isRecursive` support to lazy-loading stub
JeremyCaney Jul 17, 2026
b5da944
Fix `LoadTopicGraph()` issue with `Refresh()`
JeremyCaney Jul 17, 2026
3ff79e3
Introduce unit test to confirm `Refresh()` fix
JeremyCaney Jul 17, 2026
d347e3c
Introduce unit test for `HasChildren` loadstate
JeremyCaney Jul 17, 2026
1eb817d
Introduce new `GetSitemap` sproc
JeremyCaney Jul 17, 2026
f9aa14d
Established an `ISitemapTopicRepository` interface
JeremyCaney Jul 17, 2026
bfd6fe2
Introduced the `SqlSitemapTopicRepository`
JeremyCaney Jul 17, 2026
c199203
Removed the legacy extended sitemap
JeremyCaney Jul 17, 2026
c0a919e
Integrate `ISitemapTopicRepository ` in controller
JeremyCaney Jul 17, 2026
755c060
Introduced `StubSitemapTopicRepository`
JeremyCaney Jul 17, 2026
3b81cf1
Wire-up `SqlSitemapTopicRepository` in `Host`
JeremyCaney Jul 17, 2026
79ec7a6
Wire-up `StubSitemapTopicRepository` in `Host`
JeremyCaney Jul 17, 2026
966d5c7
Integrate `StubSitemapTopicRepository` with tests
JeremyCaney Jul 17, 2026
6ae375e
Fixed offset to avoid collisions (cont.)
JeremyCaney Jul 17, 2026
cbf8aab
Added default output caching policy for sitemap
JeremyCaney Jul 17, 2026
a188249
Established output caching for sitemap in `Host`
JeremyCaney Jul 17, 2026
db8fbe8
Established unit tests for lazy-loading repository
JeremyCaney Jul 17, 2026
92a9995
Allow `FindFirst()`, `FindAll()` past `NotLoaded`
JeremyCaney Jul 18, 2026
a8a95bb
Fixed tests after `FindAll()`, `FindFirst()` fixes
JeremyCaney Jul 18, 2026
665e7f5
Improved lazy-load awareness of `Delete()`
JeremyCaney Jul 18, 2026
0202356
Centralized `ITopicLazyLoadable` variable
JeremyCaney Jul 18, 2026
a304e90
Replace `ConnectResidentAssociations()`
JeremyCaney Jul 18, 2026
ffdf518
Prefer `#region` over flowerboxes for grouping
JeremyCaney Jul 18, 2026
aecee85
Introduce `autoLoad` overload to `GetValue()`
JeremyCaney Jul 19, 2026
42495b9
Presume bool, date, number attributes are indexed
JeremyCaney Jul 19, 2026
f94932d
Introduced unit tests for index auto-load bypass
JeremyCaney Jul 19, 2026
74b5e42
Ensure `cancellationToken` is documented correctly
JeremyCaney Jul 19, 2026
a54bebd
Fallback to cache if `referenceTopic` is null
JeremyCaney Jul 20, 2026
de1784e
Introduced new `FakeSqlTopicRepository`
JeremyCaney Jul 20, 2026
14eae80
Implemented unit tests w/ `FakeSqlTopicRepository`
JeremyCaney Jul 20, 2026
10c5144
Remove `includeExternalReferences` parameter
JeremyCaney Jul 20, 2026
eb15c3d
Added `IsDirty` concept to `DeferredAssociation`
JeremyCaney Jul 21, 2026
41be0d8
Introduce `ReplaceAll()` for deferred associations
JeremyCaney Jul 22, 2026
03445b3
Implement `IsDirty` in `ResolveAssociations()`
JeremyCaney Jul 22, 2026
7bfd09e
Bug fix: Ensure reciprocal relationships removed
JeremyCaney Jul 22, 2026
2589d79
Introduce `Clear()` for `Topic.Relationships`
JeremyCaney Jul 22, 2026
142b966
Moved version merging into `Rollback()`
JeremyCaney Jul 22, 2026
f22adb7
Removed legacy `referenceTopic` parameter
JeremyCaney Jul 22, 2026
778fe9a
Ensure `Deferred` associations are persisted
JeremyCaney Jul 22, 2026
a709500
Updated documents regarding enumeration
JeremyCaney Jul 22, 2026
3cd78b3
Account for `Deferred` in `IsDirty()`
JeremyCaney Jul 22, 2026
2986685
Always persist associations w/ `@DeleteUnmatched`
JeremyCaney Jul 22, 2026
cceae9e
Filter `PersistRelationships()` by `IsDirty`
JeremyCaney Jul 22, 2026
ff134c0
Centralize `LoadTopicGraph()` with delegate
JeremyCaney Jul 24, 2026
3e4e019
Allow for registering historical relationships
JeremyCaney Jul 24, 2026
30deeb3
Improved `Load(topicId, version)` fake
JeremyCaney Jul 24, 2026
a0b2c3c
Added unit test for dirty deferred associations
JeremyCaney Jul 24, 2026
60e0cf9
Added unit test for reciprocal `Clear()` fix
JeremyCaney Jul 24, 2026
bc98eb3
Added unit test for `Rollback()` implementation
JeremyCaney Jul 24, 2026
1c84037
Eagerly load immediate children of `root`
JeremyCaney Jul 24, 2026
d2e31a2
Allow `OnTopicLoaded()` to index ancestors
JeremyCaney Jul 24, 2026
fa416f3
Remove now-redundant `MergeIntoCache()`
JeremyCaney Jul 24, 2026
f310294
Introduced unit tests for indexed ancestors
JeremyCaney Jul 24, 2026
8d0af7d
Update unit test to match updated seed
JeremyCaney Jul 24, 2026
b511349
Support convergence in lazy-loading stub
JeremyCaney Jul 24, 2026
5ab6d72
Introduced `ChildTopicCollection`
JeremyCaney Jul 26, 2026
ca5ffe6
Implement `ChildTopicCollection`
JeremyCaney Jul 26, 2026
b30ab9e
Moved `LoadState` to `ChildTopicCollection`
JeremyCaney Jul 26, 2026
0a30ecd
Ensured proper sort order for `GetTopicUpdates`
JeremyCaney Jul 26, 2026
94b763c
Fixed minor spelling errors in comments
JeremyCaney Jul 26, 2026
ffb77d9
Convert `TopicIndex` to `ConcurrentDictionary<>`
JeremyCaney Jul 27, 2026
a15ae85
Introduced the `TopicIndexRegistry`
JeremyCaney Jul 27, 2026
273beeb
Wire-up `TopicIndexRegistry` "event handlers"
JeremyCaney Jul 27, 2026
a18d8d5
Introduced `GetLiveTopicIndex()` method to `Topic`
JeremyCaney Jul 27, 2026
4757d89
Use `GetLiveTopicIndex()` over `GetTopicIndex()`
JeremyCaney Jul 27, 2026
9ef4189
Skip orphaned topics in `AddTopic()`
JeremyCaney Jul 27, 2026
e37be61
Added unit test for orphaned topics in `Refresh()`
JeremyCaney Jul 27, 2026
21e5277
Added unit test for `Refresh()` ordering
JeremyCaney Jul 27, 2026
0587bbc
Added unit tests for new `TopicIndexRegistry`
JeremyCaney Jul 27, 2026
ae279e9
Removed `_topicIdIndex` from cached repository
JeremyCaney Jul 27, 2026
b7284fe
Added unit tests for ID index in cached test
JeremyCaney Jul 28, 2026
ed34ca1
Merge feature/ChildTopicCollection (#119)
JeremyCaney Jul 28, 2026
cb8e2ca
Merge branch 'feature/lazy-loading/TopicIndex-live-cache' (#116)
JeremyCaney Jul 28, 2026
fa60597
Remove duplicated unit tests
JeremyCaney Jul 28, 2026
3338fed
Migrate and improve unit tests tests
JeremyCaney Jul 28, 2026
951eba0
Fixed `BaseTopic` lazy-loading bug
JeremyCaney Jul 28, 2026
2875725
Establish stub to test concurrency issues
JeremyCaney Jul 28, 2026
14fb36e
Introduced concurrency unit test
JeremyCaney Jul 28, 2026
5779261
Added `EnsureLoaded()` concurrency gate per topic
JeremyCaney Jul 28, 2026
362b9fb
Merge `improvement/lazy-loading/concurrency`
JeremyCaney Jul 28, 2026
f09a43a
Updated `Load()` to take `depth` not `isRecursive`
JeremyCaney Jul 28, 2026
28a9431
Added `Depth` to `TopicLoadEventArgs`
JeremyCaney Jul 28, 2026
d370604
Updated `IsLoaded()` to take `depth`
JeremyCaney Jul 28, 2026
5f1a815
Update the test stub to support `Load(…, depth)`
JeremyCaney Jul 28, 2026
33e36c4
Update remaining stubs to support `Load(…, depth)`
JeremyCaney Jul 28, 2026
88160a4
Added unit tests for testing `depth` > 1
JeremyCaney Jul 28, 2026
d2c405e
Add `@Depth` parameter to `GetTopics` sproc
JeremyCaney Jul 28, 2026
ce39fa1
Added a new index for `ParentID`
JeremyCaney Jul 28, 2026
24aba97
Added unit tests for testing `depth` > 1 (cont.)
JeremyCaney Jul 28, 2026
ca8bb8f
Pre-normalize key in calls to `Load(uniqueKey, …)`
JeremyCaney Jul 28, 2026
1879ce8
Removed task queue for hierarchical mapping
JeremyCaney Jul 28, 2026
c223699
Preload the hierarchy before mapping
JeremyCaney Jul 28, 2026
8fe46dc
Added unit tests for tiered hierarchical load
JeremyCaney Jul 28, 2026
36f6b5f
Add unit tests for loading boolean values
JeremyCaney Jul 28, 2026
a13cf8a
Add unit test for auto-loading `BaseTopic`
JeremyCaney Jul 28, 2026
6caac08
Remove legacy earmark
JeremyCaney Jul 28, 2026
c1e80a5
Merge branch 'improvement/lazy-loading/GetTopics-depth-limit' (#120)
JeremyCaney Jul 29, 2026
29304b8
Remove `_reclaimPayload` in concurrency check
JeremyCaney Jul 29, 2026
338619e
Extend stub gate to support `Load()`
JeremyCaney Jul 29, 2026
8b56f3c
Renamed original gates to avoid ambiguity
JeremyCaney Jul 29, 2026
5840f01
Added unit test to validate concurrency bug
JeremyCaney Jul 29, 2026
843016d
Introduced `WithLoadGate<>()` helpers
JeremyCaney Jul 29, 2026
5197006
Added private `EnsureLoaded()` concurrency gate per topic
JeremyCaney Jul 29, 2026
a617b24
Added `Load(topicId)` concurrency gate per topic
JeremyCaney Jul 29, 2026
c3df4a8
Added unit test for `Load(topicId)` concurrency
JeremyCaney Jul 29, 2026
4c82c4a
Added `Load(uniqueKey)` concurrency gate per topic
JeremyCaney Jul 29, 2026
fe76592
Added unit test for `Load(uniqueKey)` concurrency
JeremyCaney Jul 29, 2026
78585b4
Document concurrency limitations
JeremyCaney Jul 29, 2026
9351513
Merge `improvement/lazy-loading/depth-concurrency` (#117, #120)
JeremyCaney Jul 29, 2026
7777ed1
Use lazy-loading in `GetSourceCollectionAsync()`
JeremyCaney Jul 29, 2026
6e064a1
Added unit tests to confirm load order
JeremyCaney Jul 29, 2026
d4b4020
Ensure `EnsureLoaded()` is awaited
JeremyCaney Jul 29, 2026
03ed27c
`EnsureLoaded()` for `AsAttributeDictionary()`
JeremyCaney Jul 31, 2026
51d0f4e
Added unit test for `AsAttributeDictionary()` fix
JeremyCaney Jul 31, 2026
faf3639
Ensure view model can't be mapped by reflection
JeremyCaney Jul 31, 2026
b88d436
Ensure metadata lookup items are loaded on mapping
JeremyCaney Jul 31, 2026
f287c1d
Prevent duplicate mapping of properties
JeremyCaney Jul 31, 2026
ec102ab
Introduced view models to test expanded maps
JeremyCaney Aug 1, 2026
49a6048
Added unit tests to confirm properties mapped once
JeremyCaney Aug 1, 2026
0f51264
Made `AddMissingAssociations()` concurrent
JeremyCaney Aug 2, 2026
a971aeb
Applied `AddMissingAssociations()` to caller
JeremyCaney Aug 2, 2026
bc73f87
Update unit tests for `AddMissingAssociations()`
JeremyCaney Aug 2, 2026
b42673a
Introduce new `MapPath` class
JeremyCaney Aug 3, 2026
7a5bd2c
Added completion semantics to mapped cache entries
JeremyCaney Aug 4, 2026
61b1487
Added `MapPath` to `MapAsync()` chain
JeremyCaney Aug 4, 2026
5a6b8a9
Added `TryGetValue(…, includeInitializing)` param
JeremyCaney Aug 4, 2026
ee9a9a5
Wire-up `Complete()` in `Register()`
JeremyCaney Aug 4, 2026
b197889
Wire-up `Fault()` in `MapAsync()`
JeremyCaney Aug 4, 2026
1334a2b
Introduce `resolveCachedEntry()`
JeremyCaney Aug 4, 2026
28f0b07
Added unit tests for completion semantics
JeremyCaney Aug 4, 2026
a64b1f8
Added unit test for `MapPath.Contains()`
JeremyCaney Aug 4, 2026
dbb4798
Introduced `CircularConstructorTopicViewModel`
JeremyCaney Aug 4, 2026
dc88d41
Added unit test for positional constructor mapping
JeremyCaney Aug 4, 2026
4a67bab
Added unit test for circular constructor reference
JeremyCaney Aug 4, 2026
73e2f3a
Introduced models for concurrent mapping tests
JeremyCaney Aug 4, 2026
91d0a21
Added `CreateGatedMappingService()` helper
JeremyCaney Aug 4, 2026
678bf64
Added unit test for concurrent model construction
JeremyCaney Aug 4, 2026
6d59060
Added unit test for faulty model construction
JeremyCaney Aug 4, 2026
86c6f7e
Lock creation of new view model collections
JeremyCaney Aug 4, 2026
9dfc24c
Lock the addition of view models to collections
JeremyCaney Aug 4, 2026
a101412
Allow the lazy concurrency repository fault
JeremyCaney Aug 4, 2026
96eae01
Introduced models for collection concurrency tests
JeremyCaney Aug 4, 2026
f6a85a2
Introduced new `RendezvousTopicLazyLoader`
JeremyCaney Aug 4, 2026
3a50d6b
Added unit tests for concurrent collection mapping
JeremyCaney Aug 4, 2026
c5f191b
Rely on `WhenAll()` over `WhenAny()`
JeremyCaney Aug 4, 2026
3359853
Introduced `StaggeredTopicLazyLoader`
JeremyCaney Aug 4, 2026
1a631ab
Added unit test for collection mapping order
JeremyCaney Aug 4, 2026
487ecf2
Prefer direct `WhenAll(IEnumerable)`
JeremyCaney Aug 4, 2026
3a16836
Cover `Deferred` in `Topic.Relationships.Clear()`
JeremyCaney Aug 4, 2026
e308bc5
Added unit test to confirm `Clear()` fix
JeremyCaney Aug 4, 2026
7d5cad0
Added unit test to test context of `Clear()` fix
JeremyCaney Aug 4, 2026
c6e644d
Serialize `PopulateTargetCollectionAsync()`
JeremyCaney Aug 5, 2026
277edab
Introduced `StaggeredStubTopicRepository`
JeremyCaney Aug 5, 2026
909121d
Introduced `NestedReferenceAttribute` model
JeremyCaney Aug 5, 2026
23f4adf
Added unit test for topic mapping serialization
JeremyCaney Aug 5, 2026
f0c65ea
Ensure target topic is fully loaded
JeremyCaney Aug 5, 2026
59a8b85
Optionally allow tracking load counting
JeremyCaney Aug 5, 2026
f235508
Added unit test for topic mapping warmup
JeremyCaney Aug 5, 2026
4857676
Defer `GetContentTypeDescriptors()` to first use
JeremyCaney Aug 5, 2026
3acee3b
Merge pull request #144 from `improvement/lazy-loading/mapping`
JeremyCaney Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,4 +199,7 @@ ModelManifest.xml
.vs

# Custom
ConnectionStrings.config
ConnectionStrings.config
docs/
.idea/
*.DS_Store
17 changes: 13 additions & 4 deletions OnTopic.AspNetCore.Mvc.Host/Program.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -14,22 +14,30 @@
/*==============================================================================================================================
| CONFIGURE SERVICES
\-----------------------------------------------------------------------------------------------------------------------------*/
var builder = WebApplication.CreateBuilder(args);
var builder = WebApplication.CreateBuilder(args);

/*------------------------------------------------------------------------------------------------------------------------------
| Configure: Cookie Policy
\-----------------------------------------------------------------------------------------------------------------------------*/
builder.Services.Configure<CookiePolicyOptions>(options => {
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});

/*------------------------------------------------------------------------------------------------------------------------------
| Configure: Output Caching
| Configure: Response Caching
\-----------------------------------------------------------------------------------------------------------------------------*/
builder.Services.AddResponseCaching();

/*------------------------------------------------------------------------------------------------------------------------------
| Configure: Output Caching
>-------------------------------------------------------------------------------------------------------------------------------
| Required for MapTopicSitemap()'s default caching policy to take effect; without this, the sitemap still renders correctly on
| every request via the dedicated SqlSitemapTopicRepository, but it isn't cached.
\-----------------------------------------------------------------------------------------------------------------------------*/
builder.Services.AddOutputCache();

/*------------------------------------------------------------------------------------------------------------------------------
| Configure: MVC
\-----------------------------------------------------------------------------------------------------------------------------*/
Expand All@@ -53,7 +61,7 @@
/*==============================================================================================================================
| CONFIGURE APPLICATION
\-----------------------------------------------------------------------------------------------------------------------------*/
var app = builder.Build();
var app = builder.Build();

/*------------------------------------------------------------------------------------------------------------------------------
| Configure: Error Pages
Expand All@@ -74,6 +82,7 @@
app.UseRouting();
app.UseCors("default");
app.UseResponseCaching();
app.UseOutputCache();

/*------------------------------------------------------------------------------------------------------------------------------
| Configure: MVC
Expand Down
31 changes: 16 additions & 15 deletions OnTopic.AspNetCore.Mvc.Host/SampleActivator.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,6 +36,7 @@ public class SampleActivator : IControllerActivator, IViewComponentActivator {
private readonly ITypeLookupService _typeLookupService;
private readonly ITopicMappingService _topicMappingService;
private readonly ITopicRepository _topicRepository;
private readonly ISitemapTopicRepository _sitemapTopicRepository;
private DateTime _cacheLastUpdated = DateTime.UtcNow;

/*============================================================================================================================
Expand DownExpand Up@@ -64,17 +65,17 @@ public SampleActivator(string connectionString) {
/*--------------------------------------------------------------------------------------------------------------------------
| Initialize Topic Repository
\-------------------------------------------------------------------------------------------------------------------------*/
var sqlTopicRepository = new SqlTopicRepository(connectionString);
var cachedTopicRepository = new CachedTopicRepository(sqlTopicRepository);
_ = new PageTopicViewModel();
var sqlTopicRepository = new SqlTopicRepository(connectionString);
var cachedTopicRepository = new CachedTopicRepository(sqlTopicRepository);
_ = new PageTopicViewModel();

/*--------------------------------------------------------------------------------------------------------------------------
| Preload repository
\-------------------------------------------------------------------------------------------------------------------------*/
_topicRepository = cachedTopicRepository;
_typeLookupService= new DynamicTopicViewModelLookupService();
_topicMappingService= new TopicMappingService(_topicRepository, _typeLookupService);
_ = _topicRepository.Load();
_topicRepository = cachedTopicRepository;
_sitemapTopicRepository = new SqlSitemapTopicRepository(connectionString);
_typeLookupService = new DynamicTopicViewModelLookupService();
_topicMappingService= new TopicMappingService(_topicRepository, _typeLookupService);

/*--------------------------------------------------------------------------------------------------------------------------
| Establish hierarchical topic mapping service
Expand All@@ -100,20 +101,20 @@ public object Create(ControllerContext context) {
/*--------------------------------------------------------------------------------------------------------------------------
| Validate parameters
\-------------------------------------------------------------------------------------------------------------------------*/
Contract.Requires(context, nameof(context));
Contract.Requires(context, nameof(context));

/*--------------------------------------------------------------------------------------------------------------------------
| Determine controller type
\-------------------------------------------------------------------------------------------------------------------------*/
var type = context.ActionDescriptor.ControllerTypeInfo.AsType();
var type = context.ActionDescriptor.ControllerTypeInfo.AsType();

/*--------------------------------------------------------------------------------------------------------------------------
| Periodically update cache
\-------------------------------------------------------------------------------------------------------------------------*/
if (DateTime.UtcNow > _cacheLastUpdated.AddMinutes(1)) {
var currentUpdate = DateTime.UtcNow;
_topicRepository.Refresh(_topicRepository.Load()!, _cacheLastUpdated);
_cacheLastUpdated = currentUpdate;
var currentUpdate = DateTime.UtcNow;
_topicRepository.Refresh(_topicRepository.Load().GetAwaiter().GetResult()!, _cacheLastUpdated).GetAwaiter().GetResult();
_cacheLastUpdated = currentUpdate;
}

/*--------------------------------------------------------------------------------------------------------------------------
Expand All@@ -125,7 +126,7 @@ public object Create(ControllerContext context) {
nameof(ErrorController) =>
new ErrorController(_topicRepository, _topicMappingService),
nameof(SitemapController) =>
new SitemapController(_topicRepository),
new SitemapController(_sitemapTopicRepository),
nameof(RedirectController) =>
new RedirectController(_topicRepository),
_ => throw new InvalidOperationException($"Unknown controller {type.Name}")
Expand All@@ -142,12 +143,12 @@ public object Create(ViewComponentContext context) {
/*--------------------------------------------------------------------------------------------------------------------------
| Validate parameters
\-------------------------------------------------------------------------------------------------------------------------*/
Contract.Requires(context, nameof(context));
Contract.Requires(context, nameof(context));

/*--------------------------------------------------------------------------------------------------------------------------
| Determine view component type
\-------------------------------------------------------------------------------------------------------------------------*/
var type = context.ViewComponentDescriptor.TypeInfo.AsType();
var type = context.ViewComponentDescriptor.TypeInfo.AsType();

/*--------------------------------------------------------------------------------------------------------------------------
| Configure and return appropriate view component
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@

<ItemGroup>
<ProjectReference Include="..\OnTopic.All\OnTopic.All.csproj" />
<ProjectReference Include="..\OnTopic.TestDoubles\OnTopic.TestDoubles.csproj" />
<ProjectReference Include="..\OnTopic.ViewModels\OnTopic.ViewModels.csproj" />
</ItemGroup>

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,99 +33,109 @@ public class StubTopicRepository : TopicRepository, ITopicRepository {
/// Instantiates a new instance of the StubTopicRepository.
/// </summary>
/// <returns>A new instance of the StubTopicRepository.</returns>
public StubTopicRepository() : base() {
_cache = CreateFakeData();
public StubTopicRepository() {
_cache = CreateFakeData();
Contract.Assume(_cache);
}

/*============================================================================================================================
| METHOD: LOAD
\---------------------------------------------------------------------------------------------------------------------------*/
/// <inheritdoc />
public override Topic? Load(int topicId, Topic? referenceTopic = null, bool isRecursive = true) {
public override Task<Topic?> Load(
int topicId,
Topic? referenceTopic = null,
TopicPayload payload = TopicPayload.None,
int depth = 0
) {

/*--------------------------------------------------------------------------------------------------------------------------
| Lookup by TopicId
\-------------------------------------------------------------------------------------------------------------------------*/
var topic = _cache;
var topic = (Topic?)_cache;

if (topicId > 0) {
topic = _cache.FindFirst(t => t.Id.Equals(topicId));
topic = _cache.FindFirst(t => t.Id.Equals(topicId));
}

/*--------------------------------------------------------------------------------------------------------------------------
| Raise event
\-------------------------------------------------------------------------------------------------------------------------*/
if (topic != null) {
OnTopicLoaded(new(topic, isRecursive));
OnTopicLoaded(new(topic, depth));
}

/*--------------------------------------------------------------------------------------------------------------------------
| Return value
\-------------------------------------------------------------------------------------------------------------------------*/
return topic;
return Task.FromResult(topic);

}

/// <inheritdoc />
public override Topic? Load(string uniqueKey, Topic? referenceTopic = null, bool isRecursive = true) {
public override Task<Topic?> Load(
string uniqueKey,
Topic? referenceTopic = null,
TopicPayload payload = TopicPayload.None,
int depth = 0
) {

/*--------------------------------------------------------------------------------------------------------------------------
| Validate parameters
\-------------------------------------------------------------------------------------------------------------------------*/
if (String.IsNullOrEmpty(uniqueKey)) {
return null;
return Task.FromResult<Topic?>(null);
}

/*--------------------------------------------------------------------------------------------------------------------------
| Lookup by TopicKey
\-------------------------------------------------------------------------------------------------------------------------*/
var topic = _cache.GetByUniqueKey(uniqueKey);
var topic = _cache.GetByUniqueKey(uniqueKey);

/*--------------------------------------------------------------------------------------------------------------------------
| Raise event
\-------------------------------------------------------------------------------------------------------------------------*/
if (topic != null) {
OnTopicLoaded(new(topic, isRecursive));
OnTopicLoaded(new(topic, depth));
}

/*--------------------------------------------------------------------------------------------------------------------------
| Return topic
\-------------------------------------------------------------------------------------------------------------------------*/
return topic;
return Task.FromResult(topic);

}

/// <inheritdoc />
public override Topic? Load(int topicId, DateTime version, Topic? referenceTopic = null) =>
public override Task<Topic?> Load(int topicId, DateTime version) =>
throw new NotImplementedException();

/*============================================================================================================================
| METHOD: REFRESH
\---------------------------------------------------------------------------------------------------------------------------*/
/// <inheritdoc/>
public override void Refresh(Topic referenceTopic, DateTime since) =>
public override Task Refresh(Topic referenceTopic, DateTime since) =>
throw new NotImplementedException();

/*============================================================================================================================
| METHOD: SAVE
\---------------------------------------------------------------------------------------------------------------------------*/
/// <inheritdoc />
protected override void SaveTopic([NotNull]Topic topic, DateTime version, bool persistRelationships) =>
protected override Task SaveTopic([NotNull]Topic topic, DateTime version, bool persistRelationships) =>
throw new NotImplementedException();

/*============================================================================================================================
| METHOD: MOVE
\---------------------------------------------------------------------------------------------------------------------------*/
/// <inheritdoc />
protected override void MoveTopic(Topic topic, Topic target, Topic? sibling = null) =>
protected override Task MoveTopic(Topic topic, Topic target, Topic? sibling = null) =>
throw new NotImplementedException();

/*============================================================================================================================
| METHOD: DELETE
\---------------------------------------------------------------------------------------------------------------------------*/
/// <inheritdoc />
protected override void DeleteTopic(Topic topic) =>
protected override Task DeleteTopic(Topic topic) =>
throw new NotImplementedException();

/*============================================================================================================================
Expand Down
Loading