Skip to content

Commit d8d41b7

Browse files
committed
Merge branch 'feature/Skip-Orphaned-LastModified' into develop
Remove byline and dateline if no other attributes are updated (#20) Introduces centralized logic—via a new `AttributeValueCollection.IsDirty(excludeLastModified)` overload—for detecting if any attributes on a topic have actually been modified outside of the automatically-generated `LastModified` attributes (e.g., `LastModified`, `LastModifiedBy`). If not, this can be used by e.g. `ITopicRepository.Save()` to skip saving attribute values, and thus preventing extraneous bylines and datelines from being persisted when no other changes have been committed. Includes: - Skip indexed attributes if only the dateline, byline have changed (dedf9fc) - Introduced new `IsDirty()` overloads for `AttributeValueCollection` (d3b49ff)
2 parents 8b7dffe + cdfe2c5 commit d8d41b7

3 files changed

Lines changed: 100 additions & 8 deletions

File tree

OnTopic.Data.Sql/SqlTopicRepository.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,22 @@ public override int Save([NotNull]Topic topic, bool isRecursive = false, bool is
289289
/*------------------------------------------------------------------------------------------------------------------------
290290
| Add indexed attributes that are dirty
291291
\-----------------------------------------------------------------------------------------------------------------------*/
292-
foreach (var attributeValue in GetAttributes(topic, false, true)) {
292+
//Exclude indexed attributes if the only dirty attributes are `LastModified` and/or `LastModifiedBy`, as these are
293+
//automatically generated and don't represent genuine changes to the topic.
294+
if (topic.Attributes.IsDirty(true)) {
293295

294-
var record = attributes.NewRow();
295-
record["AttributeKey"] = attributeValue.Key;
296-
record["AttributeValue"]= attributeValue.Value;
297-
attributeValue.IsDirty = false;
296+
var indexedAttributes = GetAttributes(topic, false, true);
298297

299-
attributes.Rows.Add(record);
298+
foreach (var attributeValue in indexedAttributes) {
299+
300+
var record = attributes.NewRow();
301+
record["AttributeKey"] = attributeValue.Key;
302+
record["AttributeValue"]= attributeValue.Value;
303+
attributeValue.IsDirty = false;
304+
305+
attributes.Rows.Add(record);
306+
307+
}
300308

301309
}
302310

OnTopic.Tests/AttributeValueCollectionTest.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using OnTopic.Attributes;
99
using OnTopic.Collections;
1010
using Microsoft.VisualStudio.TestTools.UnitTesting;
11+
using System.Globalization;
1112

1213
namespace OnTopic.Tests {
1314

@@ -251,6 +252,65 @@ public void SetValue_ValueUnchanged_IsNotDirty() {
251252

252253
}
253254

255+
/*==========================================================================================================================
256+
| TEST: IS DIRTY: DIRTY VALUES: RETURNS TRUE
257+
\-------------------------------------------------------------------------------------------------------------------------*/
258+
/// <summary>
259+
/// Populates the <see cref="AttributeValueCollection"/> with a <see cref="AttributeValue"/> that is marked as <see
260+
/// cref="AttributeValue.IsDirty"/>. Confirms that <see cref="AttributeValueCollection.IsDirty(Boolean)"/> returns
261+
/// <c>true</c>/
262+
/// </summary>
263+
[TestMethod]
264+
public void IsDirty_DirtyValues_ReturnsTrue() {
265+
266+
var topic = TopicFactory.Create("Test", "Container");
267+
268+
topic.Attributes.SetValue("Foo", "Bar");
269+
270+
Assert.IsTrue(topic.Attributes.IsDirty());
271+
272+
}
273+
274+
/*==========================================================================================================================
275+
| TEST: IS DIRTY: NO DIRTY VALUES: RETURNS FALSE
276+
\-------------------------------------------------------------------------------------------------------------------------*/
277+
/// <summary>
278+
/// Populates the <see cref="AttributeValueCollection"/> with a <see cref="AttributeValue"/> that is <i>not</i> marked as
279+
/// <see cref="AttributeValue.IsDirty"/>. Confirms that <see cref="AttributeValueCollection.IsDirty(Boolean)"/> returns
280+
/// <c>false</c>/
281+
/// </summary>
282+
[TestMethod]
283+
public void IsDirty_NoDirtyValues_ReturnsFalse() {
284+
285+
var topic = TopicFactory.Create("Test", "Container", 1);
286+
287+
topic.Attributes.SetValue("Foo", "Bar", false);
288+
289+
Assert.IsFalse(topic.Attributes.IsDirty());
290+
291+
}
292+
293+
/*==========================================================================================================================
294+
| TEST: IS DIRTY: EXCLUDE LAST MODIFIED: RETURNS FALSE
295+
\-------------------------------------------------------------------------------------------------------------------------*/
296+
/// <summary>
297+
/// Populates the <see cref="AttributeValueCollection"/> with a <see cref="AttributeValue"/> that is <i>not</i> marked as
298+
/// <see cref="AttributeValue.IsDirty"/> as well as a <c>LastModified</c> <see cref="AttributeValue"/> that is. Confirms
299+
/// that <see cref="AttributeValueCollection.IsDirty(Boolean)"/> returns <c>false</c>.
300+
/// </summary>
301+
[TestMethod]
302+
public void IsDirty_ExcludeLastModified_ReturnsFalse() {
303+
304+
var topic = TopicFactory.Create("Test", "Container", 1);
305+
306+
topic.Attributes.SetValue("Foo", "Bar", false);
307+
topic.Attributes.SetValue("LastModified", DateTime.Now.ToString(CultureInfo.InvariantCulture));
308+
topic.Attributes.SetValue("LastModifiedBy", "System");
309+
310+
Assert.IsFalse(topic.Attributes.IsDirty(true));
311+
312+
}
313+
254314
/*==========================================================================================================================
255315
| TEST: SET VALUE: INVALID VALUE: THROWS EXCEPTION
256316
\-------------------------------------------------------------------------------------------------------------------------*/

OnTopic/Collections/AttributeValueCollection.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Collections.ObjectModel;
88
using System.Diagnostics.CodeAnalysis;
9+
using System.Linq;
910
using OnTopic.Attributes;
1011
using OnTopic.Internal.Diagnostics;
1112
using OnTopic.Internal.Reflection;
@@ -54,14 +55,37 @@ internal AttributeValueCollection(Topic parentTopic) : base(StringComparer.Invar
5455
/*==========================================================================================================================
5556
| METHOD: IS DIRTY
5657
\-------------------------------------------------------------------------------------------------------------------------*/
58+
/// <summary>
59+
/// Determine if <i>any</i> attributes in the <see cref="AttributeValueCollection"/> are dirty.
60+
/// </summary>
61+
/// <remarks>
62+
/// This method is intended primarily for data storage providers, such as
63+
/// <see cref="Repositories.ITopicRepository"/>, which may need to determine if any attributes are dirty prior to saving
64+
/// them to the data storage medium. Be aware that this does <i>not</i> track any <see cref="AttributeValue"/>s that may
65+
/// have been <i>deleted</i>, nor whether any <see cref="Topic.Relationships"/> have been modified; as such, it may still
66+
/// be necessary to persist changes to the storage medium.
67+
/// </remarks>
68+
/// <param name="excludeLastModified">
69+
/// Optionally excludes <see cref="AttributeValue"/>s whose keys start with <c>LastModified</c>. This is useful for
70+
/// excluding the byline (<c>LastModifiedBy</c>) and dateline (<c>LastModified</c>) since these values are automatically
71+
/// generated by e.g. the OnTopic Editor and, thus, may be irrelevant updates if no other attribute values have changed.
72+
/// </param>
73+
/// <returns>True if the attribute value is marked as dirty; otherwise false.</returns>
74+
public bool IsDirty(bool excludeLastModified = false)
75+
=> Items.Any(a =>
76+
a.IsDirty &&
77+
(!excludeLastModified || !a.Key.StartsWith("LastModified", StringComparison.InvariantCultureIgnoreCase))
78+
);
79+
5780
/// <summary>
5881
/// Determine if a given attribute is marked as dirty. Will return false if the attribute key cannot be found.
5982
/// </summary>
6083
/// <remarks>
6184
/// This method is intended primarily for data storage providers, such as
6285
/// <see cref="OnTopic.Repositories.ITopicRepository"/>, which may need to determine if a specific attribute key is
63-
/// dirty prior to saving it to the data storage medium. Because isDirty is a state of the current attribute value, it
64-
/// does not support inheritFromParent or inheritFromDerived (which otherwise default to true).
86+
/// dirty prior to saving it to the data storage medium. Because <c>IsDirty</c> is a state of the current <see
87+
/// cref="AttributeValue"/>, it does not support <c>inheritFromParent</c> or <c>inheritFromDerived</c> (which otherwise
88+
/// default to <c>true</c>).
6589
/// </remarks>
6690
/// <param name="name">The string identifier for the <see cref="AttributeValue"/>.</param>
6791
/// <returns>True if the attribute value is marked as dirty; otherwise false.</returns>

0 commit comments

Comments
 (0)