From cc2ccd1bf344ec38059164d1aa9b261e50807eac Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 17 Jan 2026 15:02:26 +0100 Subject: 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 --- .../Entities/BaseItemEntity.cs | 10 ++++++ .../Entities/LinkedChildEntity.cs | 39 ++++++++++++++++++++++ .../Entities/LinkedChildType.cs | 27 +++++++++++++++ .../JellyfinDbContext.cs | 5 +++ .../ModelConfiguration/LinkedChildConfiguration.cs | 33 ++++++++++++++++++ 5 files changed, 114 insertions(+) create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildEntity.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildType.cs create mode 100644 src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/LinkedChildConfiguration.cs (limited to 'src/Jellyfin.Database/Jellyfin.Database.Implementations') diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemEntity.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemEntity.cs index d58466e5ca..73e6e338ec 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemEntity.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/BaseItemEntity.cs @@ -178,6 +178,16 @@ public class BaseItemEntity public ICollection? Images { get; set; } + /// + /// Gets or sets the linked children (for BoxSets, Playlists, etc.). + /// + public ICollection? LinkedChildEntities { get; set; } + + /// + /// Gets or sets the items this entity is linked to as a child. + /// + public ICollection? LinkedChildOfEntities { get; set; } + // those are references to __LOCAL__ ids not DB ids ... TODO: Bring the whole folder structure into the DB // public ICollection? SeriesEpisodes { get; set; } // public BaseItemEntity? Series { get; set; } diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildEntity.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildEntity.cs new file mode 100644 index 0000000000..7361775711 --- /dev/null +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildEntity.cs @@ -0,0 +1,39 @@ +using System; + +namespace Jellyfin.Database.Implementations.Entities; + +/// +/// Represents a linked child relationship between items (e.g., BoxSet to Movies, Playlist to tracks). +/// +public class LinkedChildEntity +{ + /// + /// Gets or sets the parent item ID (BoxSet, Playlist, etc.). + /// + public required Guid ParentId { get; set; } + + /// + /// Gets or sets the child item ID. + /// + public required Guid ChildId { get; set; } + + /// + /// Gets or sets the type of linked child (Manual or Shortcut). + /// + public required LinkedChildType ChildType { get; set; } + + /// + /// Gets or sets the sort order. + /// + public int? SortOrder { get; set; } + + /// + /// Gets or sets the parent item navigation property. + /// + public BaseItemEntity? Parent { get; set; } + + /// + /// Gets or sets the child item navigation property. + /// + public BaseItemEntity? Child { get; set; } +} diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildType.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildType.cs new file mode 100644 index 0000000000..09b4b84aba --- /dev/null +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/Entities/LinkedChildType.cs @@ -0,0 +1,27 @@ +namespace Jellyfin.Database.Implementations.Entities; + +/// +/// The linked child type. +/// +public enum LinkedChildType +{ + /// + /// Manually linked child. + /// + Manual = 0, + + /// + /// Shortcut linked child. + /// + Shortcut = 1, + + /// + /// Local alternate version (same item, different file path). + /// + LocalAlternateVersion = 2, + + /// + /// Linked alternate version (different item ID). + /// + LinkedAlternateVersion = 3 +} diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs index 5163bff8b6..f6fce7279a 100644 --- a/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/JellyfinDbContext.cs @@ -143,6 +143,11 @@ public class JellyfinDbContext(DbContextOptions options, ILog /// public DbSet PeopleBaseItemMap => Set(); + /// + /// Gets the containing linked children relationships. + /// + public DbSet LinkedChildren => Set(); + /// /// Gets the containing the referenced Providers with ids. /// diff --git a/src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/LinkedChildConfiguration.cs b/src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/LinkedChildConfiguration.cs new file mode 100644 index 0000000000..44bd01ad63 --- /dev/null +++ b/src/Jellyfin.Database/Jellyfin.Database.Implementations/ModelConfiguration/LinkedChildConfiguration.cs @@ -0,0 +1,33 @@ +using Jellyfin.Database.Implementations.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Jellyfin.Database.Implementations.ModelConfiguration; + +/// +/// LinkedChildEntity configuration. +/// +public class LinkedChildConfiguration : IEntityTypeConfiguration +{ + /// + public void Configure(EntityTypeBuilder 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); + } +} -- cgit v1.2.3