Skip to content

Commit 0c860ae

Browse files
committed
Introduce handling of unresolved references
When recursively saving a topic graph, it is possible for a topic being saved to reference a new topic in the graph that hasn't yet been saved. This prevents it from resolving that reference. I.e., while the _object model_ is able to model that reference, it can't be _persisted_ to the underlying SQL database since the target topic doesn't yet have a `Topic.Id`. Previously, this required calling `Save()` twice in this scenario. Now, a new private overload allows a `List<Topic>` to be maintained of `unresolvedTopics`. Then, once the entire graph has been saved, `Save()` will save each individual topic with unresolved references, thus ensuring that they are properly persisted. This works for both `Topic.Relationships` and `Topic.DerivedTopic` references. There are limitations to this implementation. It isn't able to resolve _implicit_ topic pointers (i.e., attributes with an `Id` extension) since those aren't mapped via the object model, but rather runtime interpretation. As such, when working with the **OnTopic Data Transfer** library, which includes special handling for implicit topic pointers, `Save()` must be called twice (alongside `Import()`). That's expected, and there isn't really a clean workaround. Second, obviously any references that are outside the scope of the current recusive `Save()` operation will continue to be unresolved. This shouldn't happen in most use cases, but could occur with some custom configuration scripts. That's also expected, and not something we plan on resolving. Still, this addresses the most common and otherwise unintuitive issue, without adding much overhead to most `Save()` operations. (Obviously, the overhead will depend on how many unresolved references there are.)
1 parent d64d242 commit 0c860ae

1 file changed

Lines changed: 66 additions & 2 deletions

File tree

OnTopic.Data.Sql/SqlTopicRepository.cs

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
| Project Topics Library
55
\=============================================================================================================================*/
66
using System;
7+
using System.Collections.Generic;
78
using System.Data;
89
using System.Data.SqlTypes;
910
using System.Diagnostics.CodeAnalysis;
@@ -264,6 +265,56 @@ public override Topic Load(int topicId, DateTime version) {
264265
/// <inheritdoc />
265266
public override int Save([NotNull]Topic topic, bool isRecursive = false, bool isDraft = false) {
266267

268+
/*------------------------------------------------------------------------------------------------------------------------
269+
| Establish dependencies
270+
\-----------------------------------------------------------------------------------------------------------------------*/
271+
var unresolvedTopics = new List<Topic>();
272+
273+
/*------------------------------------------------------------------------------------------------------------------------
274+
| Handle first pass
275+
\-----------------------------------------------------------------------------------------------------------------------*/
276+
var topicId = Save(topic, isRecursive, isDraft, unresolvedTopics);
277+
278+
/*------------------------------------------------------------------------------------------------------------------------
279+
| Attempt to resolve outstanding relationships
280+
\-----------------------------------------------------------------------------------------------------------------------*/
281+
foreach (var unresolvedTopic in unresolvedTopics) {
282+
Save(unresolvedTopic, false, isDraft, new List<Topic>());
283+
}
284+
285+
/*------------------------------------------------------------------------------------------------------------------------
286+
| Return value
287+
\-----------------------------------------------------------------------------------------------------------------------*/
288+
return topicId;
289+
290+
}
291+
292+
/// <summary>
293+
/// The private overload of the <see cref="Save"/> method provides support for sharing the <see cref="SqlConnection"/>
294+
/// between multiple requests, and maintaining a list of <paramref name="unresolvedRelationships"/>.
295+
/// </summary>
296+
/// <remarks>
297+
/// <para>
298+
/// When recursively saving a topic graph, it is conceivable that references to other topics—such as <see
299+
/// cref="Topic.Relationships"/> or <see cref="Topic.DerivedTopic"/>—can't yet be persisted because the target <see
300+
/// cref="Topic"/> hasn't yet been saved, and thus the <see cref="Topic.Id"/> is still set to <c>-1</c>. To mitigate
301+
/// this, the <paramref name="unresolvedRelationships"/> allows this private overload to keep track of unresolved
302+
/// relationships. The public <see cref="Save(Topic, Boolean, Boolean)"/> overload uses this list to resave any topics
303+
/// that include such references. This adds some overhead due to the duplicate <see cref="Save"/>, but helps avoid
304+
/// potential data loss when working with complex topic graphs.
305+
/// </para>
306+
/// </remarks>
307+
/// <param name="topic">The source <see cref="Topic"/> to save.</param>
308+
/// <param name="isRecursive">Determines whether or not to recursively save <see cref="Topic.Children"/>.</param>
309+
/// <param name="isDraft">Determines if the <see cref="Topic"/> should be saved as a draft version.</param>
310+
/// <param name="unresolvedRelationships">A list of <see cref="Topic"/>s with unresolved topic references.</param>
311+
private int Save(
312+
[NotNull]Topic topic,
313+
bool isRecursive,
314+
bool isDraft,
315+
List<Topic> unresolvedRelationships
316+
) {
317+
267318
/*------------------------------------------------------------------------------------------------------------------------
268319
| Call base method - will trigger any events associated with the save
269320
\-----------------------------------------------------------------------------------------------------------------------*/
@@ -361,13 +412,26 @@ void addUnmatchedAttribute(string key) {
361412
CommandType = CommandType.StoredProcedure
362413
};
363414
var version = new SqlDateTime(DateTime.Now);
415+
var areReferencesResolved = true;
416+
417+
/*------------------------------------------------------------------------------------------------------------------------
418+
| Handle unresolved references
419+
>-------------------------------------------------------------------------------------------------------------------------
420+
| If it's a recursive save and there are any unresolved relationships, come back to this after the topic graph has been
421+
| saved; that ensures that any relationships within the topic graph have been saved and can be properly persisted. The
422+
| same can be done for DerivedTopics references, which are effectively establish a 1:1 relationship.
423+
\-----------------------------------------------------------------------------------------------------------------------*/
424+
if (isRecursive && (topic.DerivedTopic?.Id < 0 || topic.Relationships.Any(r => r.Any(t => t.Id < 0)))) {
425+
unresolvedRelationships.Add(topic);
426+
areReferencesResolved = false;
427+
}
364428

365429
/*------------------------------------------------------------------------------------------------------------------------
366430
| Establish query parameters
367431
\-----------------------------------------------------------------------------------------------------------------------*/
368432
if (!isNew) {
369433
command.AddParameter("TopicID", topic.Id);
370-
command.AddParameter("DeleteRelationships", true);
434+
command.AddParameter("DeleteRelationships", areReferencesResolved);
371435
}
372436
else if (topic.Parent != null) {
373437
command.AddParameter("ParentID", topic.Parent.Id);
@@ -423,7 +487,7 @@ void addUnmatchedAttribute(string key) {
423487
if (isRecursive) {
424488
foreach (var childTopic in topic.Children) {
425489
childTopic.Attributes.SetValue("ParentID", topic.Id.ToString(CultureInfo.InvariantCulture));
426-
Save(childTopic, isRecursive, isDraft);
490+
Save(childTopic, isRecursive, isDraft, unresolvedRelationships);
427491
}
428492
}
429493

0 commit comments

Comments
 (0)