|
4 | 4 | | Project Topics Library |
5 | 5 | \=============================================================================================================================*/ |
6 | 6 | using System; |
| 7 | +using System.Collections.Generic; |
7 | 8 | using System.Data; |
8 | 9 | using System.Data.SqlTypes; |
9 | 10 | using System.Diagnostics.CodeAnalysis; |
@@ -264,6 +265,56 @@ public override Topic Load(int topicId, DateTime version) { |
264 | 265 | /// <inheritdoc /> |
265 | 266 | public override int Save([NotNull]Topic topic, bool isRecursive = false, bool isDraft = false) { |
266 | 267 |
|
| 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 | + |
267 | 318 | /*------------------------------------------------------------------------------------------------------------------------ |
268 | 319 | | Call base method - will trigger any events associated with the save |
269 | 320 | \-----------------------------------------------------------------------------------------------------------------------*/ |
@@ -361,13 +412,26 @@ void addUnmatchedAttribute(string key) { |
361 | 412 | CommandType = CommandType.StoredProcedure |
362 | 413 | }; |
363 | 414 | 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 | + } |
364 | 428 |
|
365 | 429 | /*------------------------------------------------------------------------------------------------------------------------ |
366 | 430 | | Establish query parameters |
367 | 431 | \-----------------------------------------------------------------------------------------------------------------------*/ |
368 | 432 | if (!isNew) { |
369 | 433 | command.AddParameter("TopicID", topic.Id); |
370 | | - command.AddParameter("DeleteRelationships", true); |
| 434 | + command.AddParameter("DeleteRelationships", areReferencesResolved); |
371 | 435 | } |
372 | 436 | else if (topic.Parent != null) { |
373 | 437 | command.AddParameter("ParentID", topic.Parent.Id); |
@@ -423,7 +487,7 @@ void addUnmatchedAttribute(string key) { |
423 | 487 | if (isRecursive) { |
424 | 488 | foreach (var childTopic in topic.Children) { |
425 | 489 | childTopic.Attributes.SetValue("ParentID", topic.Id.ToString(CultureInfo.InvariantCulture)); |
426 | | - Save(childTopic, isRecursive, isDraft); |
| 490 | + Save(childTopic, isRecursive, isDraft, unresolvedRelationships); |
427 | 491 | } |
428 | 492 | } |
429 | 493 |
|
|
0 commit comments