Skip to content

Commit b0c5a5e

Browse files
committed
Update AttributeDescriptors cache on Save(), Delete(), and Move()
While many updates to the configuration metadata are made available in real time, some—such as adding or deleting `AttributeDescriptor`s—are not. This is because the `ContentTypeDecriptors.AttributeDescriptors` property caches the list of attributes so that it doesn't need to recalculate them each time its called. Specifically, the property not only pulls in `AttributeDescriptor` values from the local `Attributes` collection, but also from all ascendent `Attributes` collections of parent `ContentTypeDescriptor` object. This reflects the fact that attributes are inherited from parent content types. To mitigate this, I've introduced a new `internal` `ResetAttributeDescriptors()` method on `ContentTypeDescriptor`. This simply sets the local backing field for the `AttributeDescriptors` collection to `null`, and then recursively does the same for any `ContentTypeDescriptor` objects underneath it. Critically, it does not repopulate the cache at this point; instead, that will be done the first time the `AttributeDescriptors` collection is called. This is important, as otherwise that process would be repeated multiple times when doing a recursive `Save()`—as is common when using the **OnTopic Data Exchange** library. By simply nullifying the field, we can safely call this method multiple times with minimal overhead; the caches will then be repopulated later, each time the respective collection is referenced. Similar to how we update the `GetContentTypeDescriptors()` cache (a9ea634), this his new `ResetAttributeDescriptors()` method is called from `Save()`, `Delete()`, and `Move()` any time they are called with an `AttributeDescriptor`. More specifically, this occurs on `Save()` if the topic is new, and on `Move()` if the parent topic is different than the target topic. This satisfies the requirements of the newly proposed feature enhancement: Update attribute descriptors when adding or removing an attribute (#17).
1 parent a9ea634 commit b0c5a5e

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

OnTopic.Tests/TopicRepositoryBaseTest.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,5 +240,74 @@ public void Delete_ContentTypeDescriptor_UpdatesContentTypeCache() {
240240

241241
}
242242

243+
/*==========================================================================================================================
244+
| TEST: MOVE: CONTENT TYPE DESCRIPTOR: UPDATES CONTENT TYPE CACHE
245+
\-------------------------------------------------------------------------------------------------------------------------*/
246+
/// <summary>
247+
/// Loads the <see cref="TopicRepositoryBase.GetAttributes(Topic, Boolean?, Boolean?)"/>, then moves one of the <see
248+
/// cref="ContentTypeDescriptor"/>s via <see cref="TopicRepositoryBase.Move(Topic, Topic)"/>, and ensures that it is
249+
/// immediately reflected in the <see cref="TopicRepositoryBase"/> cache of <see cref="ContentTypeDescriptor"/>s.
250+
/// </summary>
251+
[TestMethod]
252+
public void Move_ContentTypeDescriptor_UpdatesContentTypeCache() {
253+
254+
var contentTypes = _topicRepository.GetContentTypeDescriptors();
255+
var pageContentType = contentTypes.Contains("Page")? contentTypes["Page"] : null;
256+
var contactContentType = contentTypes.Contains("Contact")? contentTypes["Contact"] : null;
257+
var contactAttributeCount = contactContentType?.AttributeDescriptors.Count;
258+
259+
_topicRepository.Move(contactContentType, pageContentType);
260+
261+
Assert.IsFalse(contactContentType?.AttributeDescriptors.Count > contactAttributeCount);
262+
263+
}
264+
265+
/*==========================================================================================================================
266+
| TEST: SAVE: ATTRIBUTE DESCRIPTOR: UPDATES CONTENT TYPE
267+
\-------------------------------------------------------------------------------------------------------------------------*/
268+
/// <summary>
269+
/// Creates a <see cref="ContentTypeDescriptor"/> with a child <see cref="ContentTypeDescriptor"/> . Adds a new <see
270+
/// cref="AttributeDescriptor"/> to the parent. Ensures that the <see cref="ContentTypeDescriptor.AttributeDescriptors"/>
271+
/// of the child reflects the change.
272+
/// </summary>
273+
[TestMethod]
274+
public void Save_AttributeDescriptor_UpdatesContentType() {
275+
276+
var contentType = TopicFactory.Create("Parent", "ContentTypeDescriptor") as ContentTypeDescriptor;
277+
var attributeList = TopicFactory.Create("Attributes", "List", contentType);
278+
var childContentType = TopicFactory.Create("Child", "ContentTypeDescriptor", contentType) as ContentTypeDescriptor;
279+
var attributeCount = childContentType.AttributeDescriptors.Count;
280+
281+
var newAttribute = TopicFactory.Create("NewAttribute", "BooleanAttribute", attributeList) as BooleanAttribute;
282+
283+
_topicRepository.Save(newAttribute);
284+
285+
Assert.IsTrue(childContentType.AttributeDescriptors.Count > attributeCount);
286+
287+
}
288+
289+
/*==========================================================================================================================
290+
| TEST: DELETE: ATTRIBUTE DESCRIPTOR: UPDATES CONTENT TYPE
291+
\-------------------------------------------------------------------------------------------------------------------------*/
292+
/// <summary>
293+
/// Creates a <see cref="ContentTypeDescriptor"/> with a child <see cref="ContentTypeDescriptor"/> . Adds a new <see
294+
/// cref="AttributeDescriptor"/> to the parent, then immediately deletes it. Ensures that the <see
295+
/// cref="ContentTypeDescriptor.AttributeDescriptors"/> of the child reflects the change.
296+
/// </summary>
297+
[TestMethod]
298+
public void Delete_AttributeDescriptor_UpdatesContentTypeCache() {
299+
300+
var contentType = TopicFactory.Create("Parent", "ContentTypeDescriptor") as ContentTypeDescriptor;
301+
var attributeList = TopicFactory.Create("Attributes", "List", contentType);
302+
var newAttribute = TopicFactory.Create("NewAttribute", "BooleanAttribute", attributeList) as BooleanAttribute;
303+
var childContentType = TopicFactory.Create("Child", "ContentTypeDescriptor", contentType) as ContentTypeDescriptor;
304+
var attributeCount = childContentType.AttributeDescriptors.Count;
305+
306+
_topicRepository.Delete(newAttribute);
307+
308+
Assert.IsTrue(childContentType.AttributeDescriptors.Count < attributeCount);
309+
310+
}
311+
243312
} //Class
244313
} //Namespace

OnTopic/Metadata/ContentTypeDescriptor.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
| Project Topics Library
55
\=============================================================================================================================*/
66
using System;
7+
using System.Linq;
78
using OnTopic.Attributes;
89
using OnTopic.Collections;
910
using OnTopic.Internal.Diagnostics;
11+
using OnTopic.Querying;
1012

1113
namespace OnTopic.Metadata {
1214

@@ -210,6 +212,30 @@ public AttributeDescriptorCollection AttributeDescriptors {
210212
return _attributeDescriptors;
211213

212214
}
215+
216+
}
217+
218+
/*==========================================================================================================================
219+
| METHOD: RESET ATTRIBUTE DESCRIPTORS
220+
\-------------------------------------------------------------------------------------------------------------------------*/
221+
/// <summary>
222+
/// Resets the list of <see cref="AttributeDescriptor"/>s stored in the <see cref="AttributeDescriptors"/> collection on
223+
/// not only this <see cref="ContentTypeDescriptor"/>, but also any descendent <see cref="ContentTypeDescriptor"/>s.
224+
/// </summary>
225+
/// <remarks>
226+
/// Each <see cref="ContentTypeDescriptor"/> has an <see cref="AttributeDescriptors"/> collection which includes not only
227+
/// the <see cref="AttributeDescriptor"/>s associated with that <see cref="ContentTypeDescriptor"/>, but <i>also</i> any
228+
/// <see cref="AttributeDescriptor"/>s from any parent <see cref="ContentTypeDescriptor"/>s in the topic graph. This
229+
/// reflects the fact that attributes are inherited from parent content types. As a result, however, when an <see
230+
/// cref="AttributeDescriptor"/> is added or removed, or a <see cref="ContentTypeDescriptor"/> is moved to a new parent,
231+
/// this cache should be reset on the associated <see cref="ContentTypeDescriptor"/> and all descendent <see
232+
/// cref="ContentTypeDescriptor"/>s to ensure the change is reflected.
233+
/// </remarks>
234+
internal void ResetAttributeDescriptors() {
235+
_attributeDescriptors = null!;
236+
foreach (var topic in Children.Where(t => t is ContentTypeDescriptor).Cast<ContentTypeDescriptor>()) {
237+
topic.ResetAttributeDescriptors();
238+
}
213239
}
214240

215241
/*==========================================================================================================================

OnTopic/Repositories/TopicRepositoryBase.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,13 @@ topic is ContentTypeDescriptor &&
327327
_contentTypeDescriptors.Add((ContentTypeDescriptor)topic);
328328
}
329329

330+
/*------------------------------------------------------------------------------------------------------------------------
331+
| If new attribute, refresh cache
332+
\-----------------------------------------------------------------------------------------------------------------------*/
333+
if (topic.Id < 0 && IsAttributeDescriptor(topic)) {
334+
ResetAttributeDescriptors(topic);
335+
}
336+
330337
/*------------------------------------------------------------------------------------------------------------------------
331338
| Reset original key
332339
\-----------------------------------------------------------------------------------------------------------------------*/
@@ -389,6 +396,13 @@ public virtual void Move([ValidatedNotNull, NotNull]Topic topic, [ValidatedNotNu
389396
MoveEvent?.Invoke(this, new MoveEventArgs(topic, target));
390397
topic.SetParent(target, sibling);
391398

399+
/*------------------------------------------------------------------------------------------------------------------------
400+
| If a content type descriptor is being moved to a new parent, refresh cache
401+
\-----------------------------------------------------------------------------------------------------------------------*/
402+
if (topic.Parent != target && topic is ContentTypeDescriptor) {
403+
ResetAttributeDescriptors(topic);
404+
}
405+
392406
}
393407

394408
/*==========================================================================================================================
@@ -426,6 +440,13 @@ public virtual void Delete([ValidatedNotNull, NotNull]Topic topic, bool isRecurs
426440
}
427441
}
428442

443+
/*------------------------------------------------------------------------------------------------------------------------
444+
| If attribute type, refresh cache
445+
\-----------------------------------------------------------------------------------------------------------------------*/
446+
if (IsAttributeDescriptor(topic)) {
447+
ResetAttributeDescriptors(topic);
448+
}
449+
429450
}
430451

431452
/*==========================================================================================================================
@@ -551,6 +572,47 @@ protected IEnumerable<AttributeDescriptor> GetUnmatchedAttributes(Topic topic) {
551572

552573
}
553574

575+
/*==========================================================================================================================
576+
| METHOD: IS ATTRIBUTE DESCRIPTOR?
577+
\-------------------------------------------------------------------------------------------------------------------------*/
578+
/// <summary>
579+
/// Given a <see cref="Topic"/>, determines if it derives from <see cref="AttributeDescriptor"/> and is associated with
580+
/// a <see cref="ContentTypeDescriptor"/>.
581+
/// </summary>
582+
/// <param name="topic">The <see cref="Topic"/> to evaluate as an <see cref="AttributeDescriptor"/>.</param>
583+
private static bool IsAttributeDescriptor(Topic topic) =>
584+
topic is AttributeDescriptor &&
585+
topic.Parent?.Key == "Attributes" &&
586+
topic.Parent.Parent is ContentTypeDescriptor;
587+
588+
/*==========================================================================================================================
589+
| METHOD: RESET ATTRIBUTE DESCRIPTORS
590+
\-------------------------------------------------------------------------------------------------------------------------*/
591+
/// <summary>
592+
/// Assuming a topic is either a <see cref="ContentTypeDescriptor"/> or an <see cref="AttributeDescriptor"/>, will
593+
/// reset the cached <see cref="AttributeDescriptor"/>s on the associated <see cref="ContentTypeDescriptor"/> and all
594+
/// children.
595+
/// </summary>
596+
/// <remarks>
597+
/// Each <see cref="ContentTypeDescriptor"/> has a <see cref="ContentTypeDescriptor.AttributeDescriptors"/> collection
598+
/// which includes not only the <see cref="AttributeDescriptor"/>s associated with that <see
599+
/// cref="ContentTypeDescriptor"/>, but <i>also</i> any <see cref="AttributeDescriptor"/>s from any parent <see
600+
/// cref="ContentTypeDescriptor"/>s in the topic graph. This reflects the fact that attributes are inherited from parent
601+
/// content types. As a result, however, when an <see cref="AttributeDescriptor"/> is added or removed, or a <see
602+
/// cref="ContentTypeDescriptor"/> is moved to a new parent, this cache should be reset on the associated <see
603+
/// cref="ContentTypeDescriptor"/> and all descendent <see cref="ContentTypeDescriptor"/>s to ensure the change is
604+
/// reflected.
605+
/// </remarks>
606+
/// <param name="topic">The <see cref="Topic"/> to evaluate as an <see cref="AttributeDescriptor"/>.</param>
607+
private void ResetAttributeDescriptors(Topic topic) {
608+
if (IsAttributeDescriptor(topic)) {
609+
((ContentTypeDescriptor)topic.Parent!.Parent!).ResetAttributeDescriptors();
610+
}
611+
else if (topic is ContentTypeDescriptor) {
612+
((ContentTypeDescriptor)topic).ResetAttributeDescriptors();
613+
}
614+
}
615+
554616
} //Class
555617
} //Namespace
556618

0 commit comments

Comments
 (0)