Complete LinkedChildren integration and batch DTO optimizations

This commit integrates remaining performance changes:

- Add batch user data fetching in DtoService to reduce N+1 queries
- Add GetNextUpEpisodesBatch in TVSeriesManager for efficient batch retrieval
- Update Video/Movie/BoxSet to use LibraryManager for alternate versions
- Transition LinkedChild to use ItemId instead of Path (obsolete Path/LibraryItemId)
- Update providers and controllers for LinkedChildren-based references
- Add NextUpEpisodeBatchResult for batched episode queries
- Integrate IDescendantQueryProvider in SqliteDatabaseProvider
This commit is contained in:
Shadowghost
2026-01-17 17:10:07 +01:00
parent dfa78590c2
commit 5996c4afce
35 changed files with 2277 additions and 936 deletions

View File

@@ -780,7 +780,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
}
/// <summary>
/// Get linked child.
/// Get linked child from XML. Uses deprecated Path/LibraryItemId properties for backward compatibility
/// with existing XML files. These will be resolved to ItemId when the linked child is accessed.
/// </summary>
/// <param name="reader">The xml reader.</param>
/// <returns>The linked child.</returns>
@@ -791,6 +792,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
reader.MoveToContent();
reader.Read();
#pragma warning disable CS0618 // Type or member is obsolete - reading legacy XML format for backward compatibility
// Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
@@ -820,6 +822,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
return linkedItem;
}
#pragma warning restore CS0618
return null;
}

View File

@@ -467,41 +467,40 @@ namespace MediaBrowser.LocalMetadata.Savers
}
/// <summary>
/// ADd linked children.
/// Add linked children.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="writer">The xml writer.</param>
/// <param name="pluralNodeName">The plural node name.</param>
/// <param name="singularNodeName">The singular node name.</param>
/// <returns>The task object representing the asynchronous operation.</returns>
private static async Task AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName)
private async Task AddLinkedChildren(Folder item, XmlWriter writer, string pluralNodeName, string singularNodeName)
{
var items = item.LinkedChildren
var linkedChildren = item.LinkedChildren
.Where(i => i.Type == LinkedChildType.Manual)
.ToList();
if (items.Count == 0)
if (linkedChildren.Count == 0)
{
return;
}
await writer.WriteStartElementAsync(null, pluralNodeName, null).ConfigureAwait(false);
foreach (var link in items)
foreach (var link in linkedChildren)
{
if (!string.IsNullOrWhiteSpace(link.Path) || !string.IsNullOrWhiteSpace(link.LibraryItemId))
// Resolve ItemId to get the item's path for XML portability
string? path = null;
if (link.ItemId.HasValue && !link.ItemId.Value.Equals(Guid.Empty))
{
var linkedItem = LibraryManager.GetItemById(link.ItemId.Value);
path = linkedItem?.Path;
}
if (!string.IsNullOrWhiteSpace(path))
{
await writer.WriteStartElementAsync(null, singularNodeName, null).ConfigureAwait(false);
if (!string.IsNullOrWhiteSpace(link.Path))
{
await writer.WriteElementStringAsync(null, "Path", null, link.Path).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(link.LibraryItemId))
{
await writer.WriteElementStringAsync(null, "ItemId", null, link.LibraryItemId).ConfigureAwait(false);
}
await writer.WriteElementStringAsync(null, "Path", null, path).ConfigureAwait(false);
await writer.WriteEndElementAsync().ConfigureAwait(false);
}
}