Add LinkedChildren database table for normalized relationships

Introduces a new database table to store linked child relationships for
boxsets, playlists, and video alternate versions. This replaces the
JSON-serialized Data column approach with a proper relational structure.

- Add LinkedChildEntity and LinkedChildType enum
- Add entity configuration with proper foreign keys
- Add EF Core migration for SQLite
This commit is contained in:
Shadowghost
2026-01-17 15:02:26 +01:00
parent 1491494bcb
commit cc2ccd1bf3
8 changed files with 2070 additions and 1 deletions

View File

@@ -178,6 +178,16 @@ public class BaseItemEntity
public ICollection<BaseItemImageInfo>? Images { get; set; }
/// <summary>
/// Gets or sets the linked children (for BoxSets, Playlists, etc.).
/// </summary>
public ICollection<LinkedChildEntity>? LinkedChildEntities { get; set; }
/// <summary>
/// Gets or sets the items this entity is linked to as a child.
/// </summary>
public ICollection<LinkedChildEntity>? LinkedChildOfEntities { get; set; }
// those are references to __LOCAL__ ids not DB ids ... TODO: Bring the whole folder structure into the DB
// public ICollection<BaseItemEntity>? SeriesEpisodes { get; set; }
// public BaseItemEntity? Series { get; set; }

View File

@@ -0,0 +1,39 @@
using System;
namespace Jellyfin.Database.Implementations.Entities;
/// <summary>
/// Represents a linked child relationship between items (e.g., BoxSet to Movies, Playlist to tracks).
/// </summary>
public class LinkedChildEntity
{
/// <summary>
/// Gets or sets the parent item ID (BoxSet, Playlist, etc.).
/// </summary>
public required Guid ParentId { get; set; }
/// <summary>
/// Gets or sets the child item ID.
/// </summary>
public required Guid ChildId { get; set; }
/// <summary>
/// Gets or sets the type of linked child (Manual or Shortcut).
/// </summary>
public required LinkedChildType ChildType { get; set; }
/// <summary>
/// Gets or sets the sort order.
/// </summary>
public int? SortOrder { get; set; }
/// <summary>
/// Gets or sets the parent item navigation property.
/// </summary>
public BaseItemEntity? Parent { get; set; }
/// <summary>
/// Gets or sets the child item navigation property.
/// </summary>
public BaseItemEntity? Child { get; set; }
}

View File

@@ -0,0 +1,27 @@
namespace Jellyfin.Database.Implementations.Entities;
/// <summary>
/// The linked child type.
/// </summary>
public enum LinkedChildType
{
/// <summary>
/// Manually linked child.
/// </summary>
Manual = 0,
/// <summary>
/// Shortcut linked child.
/// </summary>
Shortcut = 1,
/// <summary>
/// Local alternate version (same item, different file path).
/// </summary>
LocalAlternateVersion = 2,
/// <summary>
/// Linked alternate version (different item ID).
/// </summary>
LinkedAlternateVersion = 3
}

View File

@@ -143,6 +143,11 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
/// </summary>
public DbSet<PeopleBaseItemMap> PeopleBaseItemMap => Set<PeopleBaseItemMap>();
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing linked children relationships.
/// </summary>
public DbSet<LinkedChildEntity> LinkedChildren => Set<LinkedChildEntity>();
/// <summary>
/// Gets the <see cref="DbSet{TEntity}"/> containing the referenced Providers with ids.
/// </summary>

View File

@@ -0,0 +1,33 @@
using Jellyfin.Database.Implementations.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Jellyfin.Database.Implementations.ModelConfiguration;
/// <summary>
/// LinkedChildEntity configuration.
/// </summary>
public class LinkedChildConfiguration : IEntityTypeConfiguration<LinkedChildEntity>
{
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<LinkedChildEntity> builder)
{
builder.ToTable("LinkedChildren");
builder.HasKey(e => new { e.ParentId, e.ChildId });
builder.HasIndex(e => e.ParentId);
builder.HasIndex(e => e.ChildId);
builder.HasIndex(e => new { e.ParentId, e.SortOrder });
builder.HasIndex(e => new { e.ParentId, e.ChildType });
builder.HasIndex(e => new { e.ChildId, e.ChildType });
builder.HasOne(e => e.Parent)
.WithMany(e => e.LinkedChildEntities)
.HasForeignKey(e => e.ParentId)
.OnDelete(DeleteBehavior.NoAction);
builder.HasOne(e => e.Child)
.WithMany(e => e.LinkedChildOfEntities)
.HasForeignKey(e => e.ChildId)
.OnDelete(DeleteBehavior.NoAction);
}
}