Skip to content

Commit bba59bb

Browse files
committed
Merge branch 'feature/AttributeValue-record' into develop
This update migrates `AttributeValue` to a new C# 9.0 `record` type, thus fulfilling the original intent of it being an immutable, read-only value object, while also enabling improvements over the syntax (due to e.g. `init` setters and `with` shallow copies). Immutable value objects require less memory and, importantly, also help prevent some corner cases where business logic could be inadvertantly bypassed by working directly with the `AttributeValue`. By using an immutable object, there is more incentive to go through the `SetValue()` method, which is more explicit in how it handles e.g. `IsDirty` and `LastModified` handling. As part of this, all calls that previously modified e.g. `IsDirty` or `LastModified` directly on the `AttributeValue` class now use `SetValue()` instead.
2 parents 40ce310 + 6254557 commit bba59bb

4 files changed

Lines changed: 38 additions & 18 deletions

File tree

OnTopic/Attributes/AttributeValue.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ namespace OnTopic.Attributes {
3838
/// method.
3939
/// </para>
4040
/// </remarks>
41-
public class AttributeValue {
41+
public record AttributeValue {
4242

4343
/*==========================================================================================================================
4444
| CONSTRUCTOR
@@ -128,15 +128,15 @@ internal AttributeValue(
128128
/// exception="T:System.ArgumentException">
129129
/// !value.Contains(" ")
130130
/// </requires>
131-
public string Key { get; }
131+
public string Key { get; init; }
132132

133133
/*==========================================================================================================================
134134
| PROPERTY: VALUE
135135
\-------------------------------------------------------------------------------------------------------------------------*/
136136
/// <summary>
137137
/// Gets the current value of the attribute.
138138
/// </summary>
139-
public string? Value { get; }
139+
public string? Value { get; init; }
140140

141141
/*==========================================================================================================================
142142
| PROPERTY: IS DIRTY
@@ -151,15 +151,15 @@ internal AttributeValue(
151151
/// ignored, thus preventing the need to update attributes (or create new versions of attributes) whose values haven't
152152
/// changed.
153153
/// </remarks>
154-
public bool IsDirty { get; set; }
154+
public bool IsDirty { get; init; }
155155

156156
/*==========================================================================================================================
157157
| PROPERTY: LAST MODIFIED
158158
\-------------------------------------------------------------------------------------------------------------------------*/
159159
/// <summary>
160160
/// Read-only reference to the last DateTime the <see cref="AttributeValue"/> instance was updated.
161161
/// </summary>
162-
public DateTime LastModified { get; internal set; } = DateTime.Now;
162+
public DateTime LastModified { get; init; } = DateTime.Now;
163163

164164
/*==========================================================================================================================
165165
| PROPERTY: IS EXTENDED ATTRIBUTE
@@ -190,7 +190,7 @@ internal AttributeValue(
190190
/// data <i>should</i> be stored.
191191
/// </para>
192192
/// </remarks>
193-
public bool? IsExtendedAttribute { get; internal set; }
193+
public bool? IsExtendedAttribute { get; init; }
194194

195195
} //Class
196-
} //Namespace
196+
} //Namespace

OnTopic/Collections/AttributeValueCollection.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,8 @@ public bool IsDirty(string name) {
171171
/// VersionHistory"/>.
172172
/// </param>
173173
public void MarkClean(DateTime? version = null) {
174-
foreach (var attribute in Items.Where(a => a.IsDirty)) {
175-
attribute.IsDirty = false;
176-
attribute.LastModified = version?? DateTime.UtcNow;
174+
foreach (var attribute in Items.Where(a => a.IsDirty).ToArray()) {
175+
SetValue(attribute.Key, attribute.Value, false, false, version?? DateTime.UtcNow);
177176
}
178177
DeletedAttributes.Clear();
179178
}
@@ -196,10 +195,9 @@ public void MarkClean(DateTime? version = null) {
196195
/// </param>
197196
public void MarkClean(string name, DateTime? version = null) {
198197
if (Contains(name)) {
199-
var attributeValue = this[name];
200-
if (attributeValue.IsDirty) {
201-
attributeValue.IsDirty = false;
202-
attributeValue.LastModified = version?? DateTime.UtcNow;
198+
var attribute = this[name];
199+
if (attribute.IsDirty) {
200+
SetValue(attribute.Key, attribute.Value, false, false, version?? DateTime.UtcNow);
203201
}
204202
}
205203
}
@@ -456,7 +454,12 @@ internal void SetValue(
456454
else if (originalAttributeValue.Value != value) {
457455
markAsDirty = true;
458456
}
459-
updatedAttributeValue = new AttributeValue(key, value, markAsDirty, version, isExtendedAttribute);
457+
updatedAttributeValue = originalAttributeValue with {
458+
Value = value,
459+
IsDirty = markAsDirty,
460+
LastModified = version?? originalAttributeValue.LastModified,
461+
IsExtendedAttribute = isExtendedAttribute?? originalAttributeValue.IsExtendedAttribute
462+
};
460463
}
461464

462465
/*------------------------------------------------------------------------------------------------------------------------
@@ -623,8 +626,6 @@ private bool EnforceBusinessLogic(AttributeValue originalAttribute) {
623626
);
624627
}
625628
_typeCache.SetPropertyValue(_associatedTopic, originalAttribute.Key, originalAttribute.Value);
626-
this[originalAttribute.Key].IsDirty = originalAttribute.IsDirty;
627-
this[originalAttribute.Key].LastModified = originalAttribute.LastModified;
628629
_setCounter = 0;
629630
return false;
630631
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*==============================================================================================================================
2+
| Author Ignia, LLC
3+
| Client Ignia, LLC
4+
| Project Topics Library
5+
\=============================================================================================================================*/
6+
7+
namespace System.Runtime.CompilerServices {
8+
9+
/*============================================================================================================================
10+
| CLASS: IS EXTERNAL INIT
11+
\---------------------------------------------------------------------------------------------------------------------------*/
12+
/// <summary>
13+
/// The <see cref="IsExternalInit"/> class is made available as part of the .NET 5.0 CLR in order to enable init accessors.
14+
/// As this is not available in .NET Standard, however, we must maintain this separate copy until we migrate to .NET 5.0.
15+
/// </summary>
16+
internal class IsExternalInit {
17+
18+
} //Class
19+
} //Namespace

OnTopic/Repositories/TopicRepositoryBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public virtual void Rollback([ValidatedNotNull]Topic topic, DateTime version) {
260260
\-----------------------------------------------------------------------------------------------------------------------*/
261261
foreach (var attribute in originalVersion.Attributes) {
262262
if (!topic.Attributes.Contains(attribute.Key) || topic.Attributes.GetValue(attribute.Key) != attribute.Value) {
263-
attribute.IsDirty = true;
263+
originalVersion.Attributes.SetValue(attribute.Key, attribute.Value, false);
264264
}
265265
}
266266

0 commit comments

Comments
 (0)