Skip to content

Commit

Permalink
Introduced Preregister() method on MappedTopicCache
Browse files Browse the repository at this point in the history
The `Preregister()` method allows a `MappedTopicCacheEntry` to be created before the `MappedTopic` has even completed construction.

This allows a placeholder for the `MappedTopic` to be created, which in turn allows us to detect and prevent circular references during construction. (Circular references are allows when mapping properties, just not constructor parameters.)

If a circular reference is detected—i.e., `Preregister()` is called on an existing topic—then a `TopicMappingException` is thrown.
  • Loading branch information
JeremyCaney committed Mar 22, 2021
1 parent 8f86392 commit cb41728
Showing 1 changed file with 38 additions and 0 deletions.
38 changes: 38 additions & 0 deletions OnTopic/Mapping/Internal/MappedTopicCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,44 @@ internal MappedTopicCacheEntry GetOrAdd(int topicId, AssociationTypes associatio
\-----------------------------------------------------------------------------------------------------------------------*/
if (topicId > 0 && !type.Equals(typeof(object))) {
return GetOrAdd(cacheKey, cacheEntry);

/*==========================================================================================================================
| METHOD: PREREGISTER
\-------------------------------------------------------------------------------------------------------------------------*/
/// <summary>
/// Attempts to preregister a <see cref="MappedTopicCacheEntry"/> for a <see cref="Topic"/> that is in the process of
/// being mapped to <paramref name="type"/>.
/// </summary>
/// <param name="topicId">The <see cref="Topic.Id"/> associated with the cache entry.</param>
/// <param name="type">The <see cref="Type"/> that the <see cref="Topic"/> is being mapped to.</param>
internal MappedTopicCacheEntry Preregister(int topicId, Type type) {

/*------------------------------------------------------------------------------------------------------------------------
| Validate input
\-----------------------------------------------------------------------------------------------------------------------*/
Contract.Requires(topicId, nameof(topicId));
Contract.Requires(type, nameof(type));

/*------------------------------------------------------------------------------------------------------------------------
| Construct cache entry
\-----------------------------------------------------------------------------------------------------------------------*/
var cacheKey = GetCacheKey(topicId, type);
var cacheEntry = new MappedTopicCacheEntry() {
IsInitializing = true
};

/*------------------------------------------------------------------------------------------------------------------------
| Get or add entry
\-----------------------------------------------------------------------------------------------------------------------*/
if (topicId > 0 && !type.Equals(typeof(object))) {
var existingCacheEntry = GetOrAdd(cacheKey, cacheEntry);
if (existingCacheEntry != cacheEntry) {
throw new TopicMappingException(
$"An attempt has been made to map '{topicId}' to a {type.Name} has resulted in a circular reference during the " +
$"construction of the {type.Name} instance. This is not allowed. Circular must be be mapped as properties, not " +
$"as constructor parameters, so that cached entries can be returned."
);
}
}
return cacheEntry;

Expand Down

0 comments on commit cb41728

Please # to comment.