mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-03 23:36:38 +01:00
Normalize OwnerId to GUID and add performance indexes
- Change OwnerId from string to Guid for proper foreign key relationships - Add Owner/Extras navigation properties for extras relationship - Add indexes on OwnerId and ExtraType columns for efficient queries - Add optimized composite indexes for latest items queries sorted by DateCreated - Update BaseItemRepository and migration to handle new Guid type
This commit is contained in:
@@ -856,7 +856,7 @@ public sealed class BaseItemRepository
|
||||
dto.ChannelId = entity.ChannelId ?? Guid.Empty;
|
||||
dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
|
||||
dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
|
||||
dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ownerId) ? ownerId : Guid.Empty);
|
||||
dto.OwnerId = entity.OwnerId ?? Guid.Empty;
|
||||
dto.Width = entity.Width.GetValueOrDefault();
|
||||
dto.Height = entity.Height.GetValueOrDefault();
|
||||
dto.UserData = entity.UserData;
|
||||
@@ -1023,7 +1023,7 @@ public sealed class BaseItemRepository
|
||||
entity.ChannelId = dto.ChannelId;
|
||||
entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
|
||||
entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
|
||||
entity.OwnerId = dto.OwnerId.ToString();
|
||||
entity.OwnerId = dto.OwnerId == Guid.Empty ? null : dto.OwnerId;
|
||||
entity.Width = dto.Width;
|
||||
entity.Height = dto.Height;
|
||||
entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
|
||||
|
||||
@@ -1216,9 +1216,9 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
||||
entity.ShowId = showId;
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var ownerId))
|
||||
if (reader.TryGetString(index++, out var ownerId) && Guid.TryParse(ownerId, out var ownerIdGuid))
|
||||
{
|
||||
entity.OwnerId = ownerId;
|
||||
entity.OwnerId = ownerIdGuid;
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var mediaType))
|
||||
|
||||
@@ -134,7 +134,17 @@ public class BaseItemEntity
|
||||
|
||||
public string? ShowId { get; set; }
|
||||
|
||||
public string? OwnerId { get; set; }
|
||||
public Guid? OwnerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the owner item (for extras like trailers, theme songs, etc.).
|
||||
/// </summary>
|
||||
public BaseItemEntity? Owner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extras owned by this item (trailers, theme songs, behind the scenes, etc.).
|
||||
/// </summary>
|
||||
public ICollection<BaseItemEntity>? Extras { get; set; }
|
||||
|
||||
public int? Width { get; set; }
|
||||
|
||||
|
||||
@@ -28,12 +28,16 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
|
||||
builder.HasMany(e => e.Parents);
|
||||
builder.HasMany(e => e.Children);
|
||||
builder.HasMany(e => e.DirectChildren).WithOne(e => e.DirectParent).HasForeignKey(e => e.ParentId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasMany(e => e.Extras).WithOne(e => e.Owner).HasForeignKey(e => e.OwnerId).OnDelete(DeleteBehavior.NoAction);
|
||||
builder.HasMany(e => e.LockedFields);
|
||||
builder.HasMany(e => e.TrailerTypes);
|
||||
builder.HasMany(e => e.Images);
|
||||
|
||||
builder.HasIndex(e => e.Path);
|
||||
builder.HasIndex(e => e.ParentId);
|
||||
builder.HasIndex(e => e.OwnerId);
|
||||
builder.HasIndex(e => e.ExtraType);
|
||||
builder.HasIndex(e => new { e.ExtraType, e.OwnerId });
|
||||
builder.HasIndex(e => e.PresentationUniqueKey);
|
||||
builder.HasIndex(e => new { e.Id, e.Type, e.IsFolder, e.IsVirtualItem });
|
||||
|
||||
@@ -53,6 +57,10 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
|
||||
// latest items
|
||||
builder.HasIndex(e => new { e.Type, e.TopParentId, e.IsVirtualItem, e.PresentationUniqueKey, e.DateCreated });
|
||||
builder.HasIndex(e => new { e.IsFolder, e.TopParentId, e.IsVirtualItem, e.PresentationUniqueKey, e.DateCreated });
|
||||
// latest items - optimized for sorting by DateCreated (no PresentationUniqueKey breaking the sort)
|
||||
builder.HasIndex(e => new { e.TopParentId, e.Type, e.IsVirtualItem, e.DateCreated });
|
||||
builder.HasIndex(e => new { e.TopParentId, e.IsFolder, e.IsVirtualItem, e.DateCreated });
|
||||
builder.HasIndex(e => new { e.TopParentId, e.MediaType, e.IsVirtualItem, e.DateCreated });
|
||||
// resume
|
||||
builder.HasIndex(e => new { e.MediaType, e.TopParentId, e.IsVirtualItem, e.PresentationUniqueKey });
|
||||
|
||||
@@ -60,7 +68,7 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
|
||||
{
|
||||
Id = Guid.Parse("00000000-0000-0000-0000-000000000001"),
|
||||
Type = "PLACEHOLDER",
|
||||
Name = "This is a placeholder item for UserData that has been detacted from its original item",
|
||||
Name = "This is a placeholder item for UserData that has been detached from its original item",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChangeOwnerIdToGuid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Normalize OwnerId to uppercase GUID format
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET OwnerId = UPPER(OwnerId)
|
||||
WHERE OwnerId IS NOT NULL");
|
||||
|
||||
// Clear invalid OwnerId values (not 36 characters = not a valid GUID)
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET OwnerId = null
|
||||
WHERE OwnerId IS NOT NULL AND length(OwnerId) != 36");
|
||||
|
||||
// Clear placeholder/empty GUIDs
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "OwnerId",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
column: "OwnerId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "OwnerId",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
column: "OwnerId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
columns: new[] { "BaseItemEntityId", "Name", "OwnerId" },
|
||||
values: new object[] { null, "This is a placeholder item for UserData that has been detached from its original item", null });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_ExtraType",
|
||||
table: "BaseItems",
|
||||
column: "ExtraType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_ExtraType_OwnerId",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "ExtraType", "OwnerId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_OwnerId",
|
||||
table: "BaseItems",
|
||||
column: "OwnerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_TopParentId_IsFolder_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "TopParentId", "IsFolder", "IsVirtualItem", "DateCreated" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_TopParentId_MediaType_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "TopParentId", "MediaType", "IsVirtualItem", "DateCreated" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_TopParentId_Type_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems",
|
||||
columns: new[] { "TopParentId", "Type", "IsVirtualItem", "DateCreated" });
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
column: "OwnerId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.Sql(
|
||||
@"UPDATE BaseItems
|
||||
SET OwnerId = LOWER(OwnerId)
|
||||
WHERE OwnerId IS NOT NULL");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_ExtraType",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_ExtraType_OwnerId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_OwnerId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_TopParentId_IsFolder_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_TopParentId_MediaType_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_TopParentId_Type_IsVirtualItem_DateCreated",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
columns: new[] { "Name", "OwnerId" },
|
||||
values: new object[] { "This is a placeholder item for UserData that has been detacted from its original item", null });
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Database.Providers.Sqlite.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddForeignKeyToOwnerId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_OwnerId",
|
||||
table: "BaseItems",
|
||||
column: "OwnerId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_OwnerId",
|
||||
table: "BaseItems");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "BaseItems",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("00000000-0000-0000-0000-000000000001"),
|
||||
column: "BaseItemEntityId",
|
||||
value: null);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BaseItems_BaseItems_BaseItemEntityId",
|
||||
table: "BaseItems",
|
||||
column: "BaseItemEntityId",
|
||||
principalTable: "BaseItems",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +273,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<string>("Overview")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("OwnerId")
|
||||
b.Property<Guid?>("OwnerId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("ParentId")
|
||||
@@ -363,12 +363,18 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ExtraType");
|
||||
|
||||
b.HasIndex("OwnerId");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.HasIndex("Path");
|
||||
|
||||
b.HasIndex("PresentationUniqueKey");
|
||||
|
||||
b.HasIndex("ExtraType", "OwnerId");
|
||||
|
||||
b.HasIndex("TopParentId", "Id");
|
||||
|
||||
b.HasIndex("Type", "TopParentId", "Id");
|
||||
@@ -381,6 +387,12 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.HasIndex("MediaType", "TopParentId", "IsVirtualItem", "PresentationUniqueKey");
|
||||
|
||||
b.HasIndex("TopParentId", "IsFolder", "IsVirtualItem", "DateCreated");
|
||||
|
||||
b.HasIndex("TopParentId", "MediaType", "IsVirtualItem", "DateCreated");
|
||||
|
||||
b.HasIndex("TopParentId", "Type", "IsVirtualItem", "DateCreated");
|
||||
|
||||
b.HasIndex("Type", "SeriesPresentationUniqueKey", "IsFolder", "IsVirtualItem");
|
||||
|
||||
b.HasIndex("Type", "SeriesPresentationUniqueKey", "PresentationUniqueKey", "SortName");
|
||||
@@ -404,7 +416,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
IsRepeat = false,
|
||||
IsSeries = false,
|
||||
IsVirtualItem = false,
|
||||
Name = "This is a placeholder item for UserData that has been detacted from its original item",
|
||||
Name = "This is a placeholder item for UserData that has been detached from its original item",
|
||||
Type = "PLACEHOLDER"
|
||||
});
|
||||
});
|
||||
@@ -1483,12 +1495,19 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemEntity", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Database.Implementations.Entities.BaseItemEntity", "Owner")
|
||||
.WithMany("Extras")
|
||||
.HasForeignKey("OwnerId")
|
||||
.OnDelete(DeleteBehavior.NoAction);
|
||||
|
||||
b.HasOne("Jellyfin.Database.Implementations.Entities.BaseItemEntity", "DirectParent")
|
||||
.WithMany("DirectChildren")
|
||||
.HasForeignKey("ParentId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.Navigation("DirectParent");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Database.Implementations.Entities.BaseItemImageInfo", b =>
|
||||
@@ -1714,6 +1733,8 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
|
||||
b.Navigation("DirectChildren");
|
||||
|
||||
b.Navigation("Extras");
|
||||
|
||||
b.Navigation("Images");
|
||||
|
||||
b.Navigation("ItemValues");
|
||||
|
||||
Reference in New Issue
Block a user