Skip to content

Commit 24b2e9b

Browse files
committed
Provide more explicit messaging on key conflict
When a `KeyedDictionary` finds a duplicate key, it provides a generic `ArgumentException` announcing this. It doesn't provide any context, however, such as the key that is being duplicated. This makes debugging difficult. To mitigate this, I've added explicit `InsertItem` overrides that detect duplicate keys and, if they exist, provide an `ArgumentException` with an explicit error message containing not only the key value, but also any other relevant context (such as any associated `Topic` or `T` type, as appropriate).
1 parent b63dc07 commit 24b2e9b

11 files changed

Lines changed: 120 additions & 18 deletions

File tree

Ignia.Topics.Data.Caching/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
[assembly: AssemblyTrademark("")]
2222
[assembly: AssemblyCulture("")]
2323
[assembly: ComVisible(false)]
24-
[assembly: AssemblyVersion("3.5.1759.0")]
25-
[assembly: AssemblyFileVersion("3.5.1791.0")]
24+
[assembly: AssemblyVersion("3.5.1760.0")]
25+
[assembly: AssemblyFileVersion("3.5.1792.0")]
2626
[assembly: CLSCompliant(true)]
2727
[assembly: Guid("206b7f91-ca25-4e9d-9576-60d2e54a2c0a")]
2828

Ignia.Topics.Tests/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
[assembly: AssemblyTrademark("")]
2222
[assembly: AssemblyCulture("")]
2323
[assembly: ComVisible(false)]
24-
[assembly: AssemblyVersion("3.5.1788.0")]
25-
[assembly: AssemblyFileVersion("3.5.1836.0")]
24+
[assembly: AssemblyVersion("3.5.1789.0")]
25+
[assembly: AssemblyFileVersion("3.5.1837.0")]
2626
[assembly: CLSCompliant(true)]
2727
[assembly: Guid("27632801-bfe3-41d9-8678-3c4bbe45e6c9")]

Ignia.Topics.ViewModels/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
[assembly: AssemblyTrademark("")]
2222
[assembly: AssemblyCulture("")]
2323
[assembly: ComVisible(false)]
24-
[assembly: AssemblyVersion("3.5.1759.0")]
25-
[assembly: AssemblyFileVersion("3.5.1790.0")]
24+
[assembly: AssemblyVersion("3.5.1760.0")]
25+
[assembly: AssemblyFileVersion("3.5.1791.0")]
2626
[assembly: CLSCompliant(true)]
2727
[assembly: Guid("e52fc633-b4c5-4a2b-8caf-30e756d7a6a7")]
2828

Ignia.Topics.Web.Mvc/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
[assembly: AssemblyTrademark("")]
2222
[assembly: AssemblyCulture("")]
2323
[assembly: ComVisible(false)]
24-
[assembly: AssemblyVersion("3.5.1764.0")]
25-
[assembly: AssemblyFileVersion("3.5.1796.0")]
24+
[assembly: AssemblyVersion("3.5.1765.0")]
25+
[assembly: AssemblyFileVersion("3.5.1797.0")]
2626
[assembly: CLSCompliant(true)]
2727
[assembly: Guid("3b3ce34d-b5e5-47ca-bfef-e6740650f378")]

Ignia.Topics.Web/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
[assembly: AssemblyTrademark("")]
2222
[assembly: AssemblyCulture("")]
2323
[assembly: ComVisible(false)]
24-
[assembly: AssemblyVersion("3.5.1756.0")]
25-
[assembly: AssemblyFileVersion("3.5.1780.0")]
24+
[assembly: AssemblyVersion("3.5.1757.0")]
25+
[assembly: AssemblyFileVersion("3.5.1781.0")]
2626
[assembly: CLSCompliant(true)]
2727
[assembly: Guid("c98f7b48-a085-4394-b820-c244f23868ce")]

Ignia.Topics/Collections/AttributeValueCollection.cs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,17 +475,37 @@ internal void SetValue(string key, string value, bool? isDirty, bool enforceBusi
475475
/// business logic is enforced.
476476
/// </summary>
477477
/// <remarks>
478-
/// If a settable property is available corresponding to the <see cref="AttributeValue.Key"/>, the call should be routed
479-
/// through that to ensure local business logic is enforced. This is determined by looking for the "__" prefix, which is
480-
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean)"/>'s enforceBusinessLogic parameter. To avoid an
481-
/// infinite loop, internal setters _must_ call this overload.
478+
/// <para>
479+
/// If a settable property is available corresponding to the <see cref="AttributeValue.Key"/>, the call should be routed
480+
/// through that to ensure local business logic is enforced. This is determined by looking for the "__" prefix, which is
481+
/// set by the <see cref="SetValue(String, String, Boolean?, Boolean)"/>'s enforceBusinessLogic parameter. To avoid an
482+
/// infinite loop, internal setters _must_ call this overload.
483+
/// </para>
484+
/// <para>
485+
/// Compared to the base implementation, will throw a specific <see cref="ArgumentException"/> error if a duplicate key
486+
/// is inserted. This conveniently provides the name of the <see cref="AttributeValue.Key"/> so it's clear what key is
487+
/// being duplicated.
488+
/// </para>
482489
/// </remarks>
483490
/// <param name="index">The location that the <see cref="AttributeValue"/> should be set.</param>
484491
/// <param name="item">The <see cref="AttributeValue"/> object which is being inserted.</param>
485492
/// <returns>The key for the specified collection item.</returns>
493+
/// <exception cref="ArgumentException">
494+
/// An AttributeValue with the Key '{item.Key}' already exists. The Value of the existing item is "{this[item.Key].Value};
495+
/// the new item's Value is '{item.Value}'. These AttributeValues are associated with the Topic '{GetUniqueKey()}'."
496+
/// </exception>
486497
protected override void InsertItem(int index, AttributeValue item) {
487498
if (EnforceBusinessLogic(item, out item)) {
488-
base.InsertItem(index, item);
499+
if (!Contains(item.Key)) {
500+
base.InsertItem(index, item);
501+
}
502+
else {
503+
throw new ArgumentException(
504+
$"An {nameof(AttributeValue)} with the Key '{item.Key}' already exists. The Value of the existing item is " +
505+
$"{this[item.Key].Value}; the new item's Value is '{item.Value}'. These {nameof(AttributeValue)}s are associated " +
506+
$"with the {nameof(Topic)} '{_associatedTopic.GetUniqueKey()}'."
507+
);
508+
}
489509
}
490510
}
491511

Ignia.Topics/Collections/ReadOnlyTopicCollection{T}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Ignia.Topics.Collections {
1212
| CLASS: READ ONLY TOPIC COLLECTION
1313
\---------------------------------------------------------------------------------------------------------------------------*/
1414
/// <summary>
15-
/// Provides a read-only keyed collection of topics.
15+
/// Provides a read-only collection of topics.
1616
/// </summary>
1717
public class ReadOnlyTopicCollection<T> : ReadOnlyCollection<T> where T : Topic {
1818

Ignia.Topics/Collections/RelatedTopicCollection.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,35 @@ public void SetTopic(string scope, Topic topic, bool isIncoming) {
229229

230230
}
231231

232+
/*==========================================================================================================================
233+
| OVERRIDE: INSERT ITEM
234+
\-------------------------------------------------------------------------------------------------------------------------*/
235+
/// <summary>Fires any time a <see cref="NamedTopicCollection"/> is added to the collection.</summary>
236+
/// <remarks>
237+
/// Compared to the base implementation, will throw a specific <see cref="ArgumentException"/> error if a duplicate key is
238+
/// inserted. This conveniently provides the name of the <see cref="NamedTopicCollection"/>, so it's clear what key is
239+
/// being duplicated.
240+
/// </remarks>
241+
/// <param name="index">The zero-based index at which <paramref name="item" /> should be inserted.</param>
242+
/// <param name="item">The <see cref="NamedTopicCollection"/> instance to insert.</param>
243+
/// <exception cref="ArgumentException">
244+
/// A NamedTopicCollection with the Name '{item.Name}' already exists in this RelatedTopicCollection. The existing key is
245+
/// {this[item.Name].Name}'; the new item's is '{item.Name}'. This collection is associated with the '{GetUniqueKey()}'
246+
/// Topic.
247+
/// </exception>
248+
protected override void InsertItem(int index, NamedTopicCollection item) {
249+
if (!Contains(item.Name)) {
250+
base.InsertItem(index, item);
251+
}
252+
else {
253+
throw new ArgumentException(
254+
$"A {nameof(NamedTopicCollection)} with the Name '{item.Name}' already exists in this " +
255+
$"{nameof(RelatedTopicCollection)}. The existing key is '{this[item.Name].Name}'; the new item's is '{item.Name}'. " +
256+
$"This collection is associated with the '{_parent.GetUniqueKey()}' Topic."
257+
);
258+
}
259+
}
260+
232261
/*==========================================================================================================================
233262
| OVERRIDE: GET KEY FOR ITEM
234263
\-------------------------------------------------------------------------------------------------------------------------*/

Ignia.Topics/Collections/TopicCollection{T}.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,33 @@ public ReadOnlyTopicCollection<T> AsReadOnly() {
7272
return new ReadOnlyTopicCollection<T>(this);
7373
}
7474

75+
/*==========================================================================================================================
76+
| OVERRIDE: INSERT ITEM
77+
\-------------------------------------------------------------------------------------------------------------------------*/
78+
/// <summary>Fires any time a <typeparamref name="T"/> is added to the collection.</summary>
79+
/// <remarks>
80+
/// Compared to the base implementation, will throw a specific <see cref="ArgumentException"/> error if a duplicate key is
81+
/// inserted. This conveniently provides the name of the <typeparamref name="T"/>'s <see cref="Topic.UniqueKey"/>, so it's
82+
/// clear what key is being duplicated.
83+
/// </remarks>
84+
/// <param name="index">The zero-based index at which <paramref name="item" /> should be inserted.</param>
85+
/// <param name="item">The <typeparamref name="T"/> instance to insert.</param>
86+
/// <exception cref="ArgumentException">
87+
/// A {typeof(T).Name} with the Key '{item.Key}' already exists. The UniqueKey of the existing {typeof(T).Name} is
88+
/// '{GetUniqueKey()}'; the new item's is '{item.GetUniqueKey()}'.
89+
/// </exception>
90+
protected override void InsertItem(int index, T item) {
91+
if (!Contains(item.Key)) {
92+
base.InsertItem(index, item);
93+
}
94+
else {
95+
throw new ArgumentException(
96+
$"A {typeof(T).Name} with the Key '{item.Key}' already exists. The UniqueKey of the existing {typeof(T).Name} is " +
97+
$"'{this[item.Key].GetUniqueKey()}'; the new item's is '{item.GetUniqueKey()}'."
98+
);
99+
}
100+
}
101+
75102
/*==========================================================================================================================
76103
| METHOD: CHANGE KEY
77104
\-------------------------------------------------------------------------------------------------------------------------*/

Ignia.Topics/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
[assembly: AssemblyTrademark("")]
2323
[assembly: AssemblyCulture("")]
2424
[assembly: ComVisible(false)]
25-
[assembly: AssemblyVersion("3.5.1759.0")]
26-
[assembly: AssemblyFileVersion("3.5.1791.0")]
25+
[assembly: AssemblyVersion("3.5.1760.0")]
26+
[assembly: AssemblyFileVersion("3.5.1792.0")]
2727
[assembly: InternalsVisibleTo("Ignia.Topics.Tests")]
2828
[assembly: CLSCompliant(true)]
2929
[assembly: GuidAttribute("3CA9F6CB-B45A-4E74-AAA4-0C87CAA2704F")]

0 commit comments

Comments
 (0)