diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index c0782c8f9b..144238e3e0 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -441,7 +441,7 @@ namespace Emby.Server.Implementations.Library
newPrimary.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
// Re-route playlist/collection references from deleted primary to new primary
- _itemRepository.RerouteLinkedChildren(video.Id, newPrimary.Id);
+ RerouteLinkedChildReferencesAsync(video.Id, newPrimary.Id).GetAwaiter().GetResult();
// Update remaining alternates to point to new primary
foreach (var alternate in alternateVersions.Skip(1))
@@ -455,7 +455,7 @@ namespace Emby.Server.Implementations.Library
else if (item is Video alternateVideo && alternateVideo.PrimaryVersionId.HasValue)
{
// If deleting an alternate version, re-route references to its primary
- _itemRepository.RerouteLinkedChildren(alternateVideo.Id, alternateVideo.PrimaryVersionId.Value);
+ RerouteLinkedChildReferencesAsync(alternateVideo.Id, alternateVideo.PrimaryVersionId.Value).GetAwaiter().GetResult();
}
var children = item.IsFolder
@@ -3655,9 +3655,26 @@ namespace Emby.Server.Implementations.Library
}
///
- public int RerouteLinkedChildReferences(Guid fromChildId, Guid toChildId)
+ public async Task RerouteLinkedChildReferencesAsync(Guid fromChildId, Guid toChildId)
{
- return _itemRepository.RerouteLinkedChildren(fromChildId, toChildId);
+ var affectedParentIds = _itemRepository.RerouteLinkedChildren(fromChildId, toChildId);
+
+ // Update in-memory LinkedChildren and re-save metadata (NFO) for affected parents
+ foreach (var parentId in affectedParentIds)
+ {
+ if (GetItemById(parentId) is Folder parent)
+ {
+ foreach (var lc in parent.LinkedChildren)
+ {
+ if (lc.ItemId.HasValue && lc.ItemId.Value.Equals(fromChildId))
+ {
+ lc.ItemId = toChildId;
+ }
+ }
+
+ await RunMetadataSavers(parent, ItemUpdateType.MetadataEdit).ConfigureAwait(false);
+ }
+ }
}
///
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 155871770a..2161391105 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -223,7 +223,7 @@ public class VideosController : BaseJellyfinApiController
await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
// Re-route any playlist/collection references from this item to the primary
- _libraryManager.RerouteLinkedChildReferences(item.Id, primaryVersion.Id);
+ await _libraryManager.RerouteLinkedChildReferencesAsync(item.Id, primaryVersion.Id).ConfigureAwait(false);
if (!alternateVersionsOfPrimary.Any(i => i.ItemId.HasValue && i.ItemId.Value.Equals(item.Id)))
{
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
index dea4e04108..6e80d38d7d 100644
--- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
@@ -4439,10 +4439,22 @@ public sealed class BaseItemRepository
}
///
- public int RerouteLinkedChildren(Guid fromChildId, Guid toChildId)
+ public IReadOnlyList RerouteLinkedChildren(Guid fromChildId, Guid toChildId)
{
using var context = _dbProvider.CreateDbContext();
+ // Collect all affected parent IDs before modifying
+ var affectedParentIds = context.LinkedChildren
+ .Where(lc => lc.ChildId == fromChildId && lc.ChildType == DbLinkedChildType.Manual)
+ .Select(lc => lc.ParentId)
+ .Distinct()
+ .ToList();
+
+ if (affectedParentIds.Count == 0)
+ {
+ return affectedParentIds;
+ }
+
// Get parents that already reference toChildId (to avoid duplicates)
var parentsWithTarget = context.LinkedChildren
.Where(lc => lc.ChildId == toChildId && lc.ChildType == DbLinkedChildType.Manual)
@@ -4450,7 +4462,7 @@ public sealed class BaseItemRepository
.ToHashSet();
// Update references that won't create duplicates
- var updated = context.LinkedChildren
+ context.LinkedChildren
.Where(lc => lc.ChildId == fromChildId
&& lc.ChildType == DbLinkedChildType.Manual
&& !parentsWithTarget.Contains(lc.ParentId))
@@ -4463,7 +4475,7 @@ public sealed class BaseItemRepository
&& parentsWithTarget.Contains(lc.ParentId))
.ExecuteDelete();
- return updated;
+ return affectedParentIds;
}
///
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 3428d1fc12..dce5664672 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -590,7 +590,7 @@ namespace MediaBrowser.Controller.Entities
await oldPrimary.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
// Re-route playlist/collection references from old primary to new primary
- LibraryManager.RerouteLinkedChildReferences(oldPrimary.Id, newPrimary.Id);
+ await LibraryManager.RerouteLinkedChildReferencesAsync(oldPrimary.Id, newPrimary.Id).ConfigureAwait(false);
}
// After removing items, reattach any detached user data to remaining children
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 5cfb940891..bcabfff772 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -767,8 +767,8 @@ namespace MediaBrowser.Controller.Library
///
/// The child ID to re-route from.
/// The child ID to re-route to.
- /// Number of references updated.
- int RerouteLinkedChildReferences(Guid fromChildId, Guid toChildId);
+ /// A representing the asynchronous operation.
+ Task RerouteLinkedChildReferencesAsync(Guid fromChildId, Guid toChildId);
///
/// Gets legacy query filters for filtering UI.
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index bcbbcc4785..52250b4058 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -254,8 +254,8 @@ public interface IItemRepository
///
/// The child ID to re-route from.
/// The child ID to re-route to.
- /// Number of references updated.
- int RerouteLinkedChildren(Guid fromChildId, Guid toChildId);
+ /// List of parent item IDs whose LinkedChildren were modified.
+ IReadOnlyList RerouteLinkedChildren(Guid fromChildId, Guid toChildId);
///
/// Creates or updates a LinkedChild entry linking a parent to a child item.