瀏覽代碼

Fixed (most) tests

JPVenson 7 月之前
父節點
當前提交
01d834f21a

+ 10 - 16
Jellyfin.Data/Entities/BaseItemEntity.cs

@@ -156,28 +156,12 @@ public class BaseItemEntity
 
     public Guid? ParentId { get; set; }
 
-    public BaseItemEntity? Parent { get; set; }
-
-    public ICollection<BaseItemEntity>? DirectChildren { get; set; }
-
     public Guid? TopParentId { get; set; }
 
-    public BaseItemEntity? TopParent { get; set; }
-
-    public ICollection<BaseItemEntity>? AllChildren { get; set; }
-
     public Guid? SeasonId { get; set; }
 
-    public BaseItemEntity? Season { get; set; }
-
-    public ICollection<BaseItemEntity>? SeasonEpisodes { get; set; }
-
     public Guid? SeriesId { get; set; }
 
-    public ICollection<BaseItemEntity>? SeriesEpisodes { get; set; }
-
-    public BaseItemEntity? Series { get; set; }
-
     public ICollection<People>? Peoples { get; set; }
 
     public ICollection<UserData>? UserData { get; set; }
@@ -191,4 +175,14 @@ public class BaseItemEntity
     public ICollection<BaseItemProvider>? Provider { get; set; }
 
     public ICollection<AncestorId>? AncestorIds { 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; }
+    // public BaseItemEntity? Season { get; set; }
+    // public BaseItemEntity? Parent { get; set; }
+    // public ICollection<BaseItemEntity>? DirectChildren { get; set; }
+    // public BaseItemEntity? TopParent { get; set; }
+    // public ICollection<BaseItemEntity>? AllChildren { get; set; }
+    // public ICollection<BaseItemEntity>? SeasonEpisodes { get; set; }
 }

+ 71 - 63
Jellyfin.Server.Implementations/Item/BaseItemRepository.cs

@@ -5,20 +5,16 @@ using System.Collections.Immutable;
 using System.Globalization;
 using System.Linq;
 using System.Linq.Expressions;
-using System.Security.Cryptography.X509Certificates;
 using System.Text;
 using System.Threading;
-using System.Threading.Channels;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.LiveTv;
@@ -26,6 +22,7 @@ using MediaBrowser.Model.Querying;
 using Microsoft.EntityFrameworkCore;
 using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 using BaseItemEntity = Jellyfin.Data.Entities.BaseItemEntity;
+#pragma warning disable RS0030 // Do not use banned APIs
 
 namespace Jellyfin.Server.Implementations.Item;
 
@@ -66,12 +63,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
 
         using var context = dbProvider.CreateDbContext();
         using var transaction = context.Database.BeginTransaction();
-        context.Peoples.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
-        context.Chapters.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
-        context.MediaStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
-        context.AncestorIds.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
-        context.ItemValues.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
-        context.BaseItems.Where(e => e.Id.Equals(id)).ExecuteDelete();
+        context.Peoples.Where(e => e.ItemId == id).ExecuteDelete();
+        context.Chapters.Where(e => e.ItemId == id).ExecuteDelete();
+        context.MediaStreamInfos.Where(e => e.ItemId == id).ExecuteDelete();
+        context.AncestorIds.Where(e => e.ItemId == id).ExecuteDelete();
+        context.ItemValues.Where(e => e.ItemId == id).ExecuteDelete();
+        context.BaseItems.Where(e => e.Id == id).ExecuteDelete();
         context.SaveChanges();
         transaction.Commit();
     }
@@ -113,8 +110,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         PrepareFilterQuery(filter);
 
         using var context = dbProvider.CreateDbContext();
-        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter)
-            .DistinctBy(e => e.Id);
+        var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
+        // .DistinctBy(e => e.Id);
 
         var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(filter);
         if (enableGroupByPresentationUniqueKey && filter.GroupBySeriesPresentationUniqueKey)
@@ -266,8 +263,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         PrepareFilterQuery(filter);
 
         using var context = dbProvider.CreateDbContext();
-        var dbQuery = TranslateQuery(context.BaseItems, context, filter)
-            .DistinctBy(e => e.Id);
+        var dbQuery = TranslateQuery(context.BaseItems, context, filter);
         if (filter.Limit.HasValue || filter.StartIndex.HasValue)
         {
             var offset = filter.StartIndex ?? 0;
@@ -299,6 +295,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         return dbQuery.Count();
     }
 
+#pragma warning disable CA1307 // Specify StringComparison for clarity
     private IQueryable<BaseItemEntity> TranslateQuery(
         IQueryable<BaseItemEntity> baseQuery,
         JellyfinDbContext context,
@@ -419,7 +416,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
 
         if (!string.IsNullOrEmpty(filter.SearchTerm))
         {
-            baseQuery = baseQuery.Where(e => e.CleanName!.Contains(filter.SearchTerm, StringComparison.InvariantCultureIgnoreCase) || (e.OriginalTitle != null && e.OriginalTitle.Contains(filter.SearchTerm, StringComparison.InvariantCultureIgnoreCase)));
+            baseQuery = baseQuery.Where(e => e.CleanName!.Contains(filter.SearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.Contains(filter.SearchTerm)));
         }
 
         if (filter.IsFolder.HasValue)
@@ -474,18 +471,15 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             baseQuery = baseQuery.Where(e => includeTypeName.Contains(e.Type));
         }
 
-        if (filter.ChannelIds.Count == 1)
+        if (filter.ChannelIds.Count > 0)
         {
-            baseQuery = baseQuery.Where(e => e.ChannelId == filter.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
-        }
-        else if (filter.ChannelIds.Count > 1)
-        {
-            baseQuery = baseQuery.Where(e => filter.ChannelIds.Select(f => f.ToString("N", CultureInfo.InvariantCulture)).Contains(e.ChannelId));
+            var channelIds = filter.ChannelIds.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray();
+            baseQuery = baseQuery.Where(e => channelIds.Contains(e.ChannelId));
         }
 
         if (!filter.ParentId.IsEmpty())
         {
-            baseQuery = baseQuery.Where(e => e.ParentId.Equals(filter.ParentId));
+            baseQuery = baseQuery.Where(e => e.ParentId!.Value == filter.ParentId);
         }
 
         if (!string.IsNullOrWhiteSpace(filter.Path))
@@ -591,7 +585,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
 
         if (filter.TrailerTypes.Length > 0)
         {
-            baseQuery = baseQuery.Where(e => filter.TrailerTypes.Any(f => e.TrailerTypes!.Contains(f.ToString(), StringComparison.OrdinalIgnoreCase)));
+            var trailerTypes = filter.TrailerTypes.Select(e => e.ToString()).ToArray();
+            baseQuery = baseQuery.Where(e => trailerTypes.Any(f => e.TrailerTypes!.Contains(f)));
         }
 
         if (filter.IsAiring.HasValue)
@@ -611,7 +606,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             baseQuery = baseQuery
                 .Where(e =>
                     context.Peoples.Where(w => context.BaseItems.Where(w => filter.PersonIds.Contains(w.Id)).Any(f => f.Name == w.Name))
-                        .Any(f => f.ItemId.Equals(e.Id)));
+                        .Any(f => f.ItemId == e.Id));
         }
 
         if (!string.IsNullOrWhiteSpace(filter.Person))
@@ -649,12 +644,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         {
             baseQuery = baseQuery.Where(e =>
                 e.CleanName == filter.NameContains
-                || e.OriginalTitle!.Contains(filter.NameContains!, StringComparison.Ordinal));
+                || e.OriginalTitle!.Contains(filter.NameContains!));
         }
 
         if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
         {
-            baseQuery = baseQuery.Where(e => e.SortName!.Contains(filter.NameStartsWith, StringComparison.OrdinalIgnoreCase));
+            baseQuery = baseQuery.Where(e => e.SortName!.Contains(filter.NameStartsWith));
         }
 
         if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
@@ -671,31 +666,32 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
 
         if (filter.ImageTypes.Length > 0)
         {
-            baseQuery = baseQuery.Where(e => filter.ImageTypes.Any(f => e.Images!.Contains(f.ToString(), StringComparison.InvariantCulture)));
+            var imgTypes = filter.ImageTypes.Select(e => e.ToString()).ToArray();
+            baseQuery = baseQuery.Where(e => imgTypes.Any(f => e.Images!.Contains(f)));
         }
 
         if (filter.IsLiked.HasValue)
         {
             baseQuery = baseQuery
-                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue);
+                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue);
         }
 
         if (filter.IsFavoriteOrLiked.HasValue)
         {
             baseQuery = baseQuery
-                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked);
+                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked);
         }
 
         if (filter.IsFavorite.HasValue)
         {
             baseQuery = baseQuery
-                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite);
+                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite);
         }
 
         if (filter.IsPlayed.HasValue)
         {
             baseQuery = baseQuery
-                   .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value);
+                   .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value);
         }
 
         if (filter.IsResumable.HasValue)
@@ -703,12 +699,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             if (filter.IsResumable.Value)
             {
                 baseQuery = baseQuery
-                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0);
+                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0);
             }
             else
             {
                 baseQuery = baseQuery
-                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(filter.User!.Id) && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0);
+                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0);
             }
         }
 
@@ -925,7 +921,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         if (filter.HasDeadParentId.HasValue && filter.HasDeadParentId.Value)
         {
             baseQuery = baseQuery
-                .Where(e => e.ParentId.HasValue && context.BaseItems.Any(f => f.Id.Equals(e.ParentId.Value)));
+                .Where(e => e.ParentId.HasValue && context.BaseItems.Any(f => f.Id == e.ParentId.Value));
         }
 
         if (filter.IsDeadArtist.HasValue && filter.IsDeadArtist.Value)
@@ -1048,11 +1044,11 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
             if (enableItemsByName && includedItemByNameTypes.Count > 0)
             {
-                baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w.Equals(e.TopParentId!.Value)));
+                baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w == e.TopParentId!.Value));
             }
             else
             {
-                baseQuery = baseQuery.Where(e => queryTopParentIds.Any(w => w.Equals(e.TopParentId!.Value)));
+                baseQuery = baseQuery.Where(e => queryTopParentIds.Any(w => w == e.TopParentId!.Value));
             }
         }
 
@@ -1064,7 +1060,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
         {
             baseQuery = baseQuery
-                .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.AncestorIds!.Any(w => w.ItemId.Equals(f.Id))));
+                .Where(e => context.BaseItems.Where(f => f.PresentationUniqueKey == filter.AncestorWithPresentationUniqueKey).Any(f => f.AncestorIds!.Any(w => w.ItemId == f.Id)));
         }
 
         if (!string.IsNullOrWhiteSpace(filter.SeriesPresentationUniqueKey))
@@ -1090,7 +1086,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
                     .Where(e => e.ItemValues!.Where(e => e.Type == 6)
                         .Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue))
                         ||
-                        (e.ParentId.HasValue && context.ItemValues.Where(w => w.ItemId.Equals(e.ParentId.Value))!.Where(e => e.Type == 6)
+                        (e.ParentId.HasValue && context.ItemValues.Where(w => w.ItemId == e.ParentId.Value)!.Where(e => e.Type == 6)
                         .Any(f => filter.IncludeInheritedTags.Contains(f.CleanValue))));
             }
 
@@ -1112,21 +1108,23 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
 
         if (filter.SeriesStatuses.Length > 0)
         {
+            var seriesStatus = filter.SeriesStatuses.Select(e => e.ToString()).ToArray();
             baseQuery = baseQuery
-                .Where(e => filter.SeriesStatuses.Any(f => e.Data!.Contains(f.ToString(), StringComparison.InvariantCultureIgnoreCase)));
+                .Where(e => seriesStatus.Any(f => e.Data!.Contains(f)));
         }
 
         if (filter.BoxSetLibraryFolders.Length > 0)
         {
+            var boxsetFolders = filter.BoxSetLibraryFolders.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray();
             baseQuery = baseQuery
-                .Where(e => filter.BoxSetLibraryFolders.Any(f => e.Data!.Contains(f.ToString("N", CultureInfo.InvariantCulture), StringComparison.InvariantCultureIgnoreCase)));
+                .Where(e => boxsetFolders.Any(f => e.Data!.Contains(f)));
         }
 
         if (filter.VideoTypes.Length > 0)
         {
             var videoTypeBs = filter.VideoTypes.Select(e => $"\"VideoType\":\"" + e + "\"");
             baseQuery = baseQuery
-                .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f, StringComparison.InvariantCultureIgnoreCase)));
+                .Where(e => videoTypeBs.Any(f => e.Data!.Contains(f)));
         }
 
         if (filter.Is3D.HasValue)
@@ -1134,12 +1132,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             if (filter.Is3D.Value)
             {
                 baseQuery = baseQuery
-                    .Where(e => e.Data!.Contains("Video3DFormat", StringComparison.InvariantCultureIgnoreCase));
+                    .Where(e => e.Data!.Contains("Video3DFormat"));
             }
             else
             {
                 baseQuery = baseQuery
-                    .Where(e => !e.Data!.Contains("Video3DFormat", StringComparison.InvariantCultureIgnoreCase));
+                    .Where(e => !e.Data!.Contains("Video3DFormat"));
             }
         }
 
@@ -1148,12 +1146,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             if (filter.IsPlaceHolder.Value)
             {
                 baseQuery = baseQuery
-                    .Where(e => e.Data!.Contains("IsPlaceHolder\":true", StringComparison.InvariantCultureIgnoreCase));
+                    .Where(e => e.Data!.Contains("IsPlaceHolder\":true"));
             }
             else
             {
                 baseQuery = baseQuery
-                    .Where(e => !e.Data!.Contains("IsPlaceHolder\":true", StringComparison.InvariantCultureIgnoreCase));
+                    .Where(e => !e.Data!.Contains("IsPlaceHolder\":true"));
             }
         }
 
@@ -1212,7 +1210,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         using var db = dbProvider.CreateDbContext();
 
         db.BaseItems
-            .Where(e => e.Id.Equals(item.Id))
+            .Where(e => e.Id == item.Id)
             .ExecuteUpdate(e => e.SetProperty(f => f.Images, images));
     }
 
@@ -1246,11 +1244,20 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         }
 
         using var context = dbProvider.CreateDbContext();
+        using var transaction = context.Database.BeginTransaction();
         foreach (var item in tuples)
         {
             var entity = Map(item.Item);
-            context.BaseItems.Add(entity);
+            if (!context.BaseItems.Any(e => e.Id == entity.Id))
+            {
+                context.BaseItems.Add(entity);
+            }
+            else
+            {
+                context.BaseItems.Attach(entity).State = EntityState.Modified;
+            }
 
+            context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
             if (item.Item.SupportsAncestors && item.AncestorIds != null)
             {
                 foreach (var ancestorId in item.AncestorIds)
@@ -1260,13 +1267,13 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
                         Item = entity,
                         AncestorIdText = ancestorId.ToString(),
                         Id = ancestorId,
-                        ItemId = entity.Id
+                        ItemId = Guid.Empty
                     });
                 }
             }
 
             var itemValues = GetItemValuesToSave(item.Item, item.InheritedTags);
-            context.ItemValues.Where(e => e.ItemId.Equals(entity.Id)).ExecuteDelete();
+            context.ItemValues.Where(e => e.ItemId == entity.Id).ExecuteDelete();
             foreach (var itemValue in itemValues)
             {
                 context.ItemValues.Add(new()
@@ -1275,12 +1282,13 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
                     Type = itemValue.MagicNumber,
                     Value = itemValue.Value,
                     CleanValue = GetCleanValue(itemValue.Value),
-                    ItemId = entity.Id
+                    ItemId = Guid.Empty
                 });
             }
         }
 
-        context.SaveChanges(true);
+        context.SaveChanges();
+        transaction.Commit();
     }
 
     /// <inheritdoc cref="IItemRepository" />
@@ -1292,7 +1300,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         }
 
         using var context = dbProvider.CreateDbContext();
-        var item = context.BaseItems.FirstOrDefault(e => e.Id.Equals(id));
+        var item = context.BaseItems.FirstOrDefault(e => e.Id == id);
         if (item is null)
         {
             return null;
@@ -1380,7 +1388,7 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             dto.Audio = Enum.Parse<ProgramAudio>(entity.Audio);
         }
 
-        dto.ExtraIds = entity.ExtraIds?.Split('|').Select(e => Guid.Parse(e)).ToArray();
+        dto.ExtraIds = string.IsNullOrWhiteSpace(entity.ExtraIds) ? null : entity.ExtraIds.Split('|').Select(e => Guid.Parse(e)).ToArray();
         dto.ProductionLocations = entity.ProductionLocations?.Split('|');
         dto.Studios = entity.Studios?.Split('|');
         dto.Tags = entity.Tags?.Split('|');
@@ -1535,8 +1543,8 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
         entity.Audio = dto.Audio?.ToString();
         entity.ExtraType = dto.ExtraType?.ToString();
 
-        entity.ExtraIds = string.Join('|', dto.ExtraIds);
-        entity.ProductionLocations = string.Join('|', dto.ProductionLocations);
+        entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
+        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations) : null;
         entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
         entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
         entity.LockedFields = dto.LockedFields is not null ? string.Join('|', dto.LockedFields) : null;
@@ -1628,15 +1636,15 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             .Where(e => itemValueTypes.Contains(e.Type));
         if (withItemTypes.Count > 0)
         {
-            query = query.Where(e => context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId.Equals(e.ItemId))));
+            query = query.Where(e => context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId == e.ItemId)));
         }
 
         if (excludeItemTypes.Count > 0)
         {
-            query = query.Where(e => !context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId.Equals(e.ItemId))));
+            query = query.Where(e => !context.BaseItems.Where(e => withItemTypes.Contains(e.Type)).Any(f => f.ItemValues!.Any(w => w.ItemId == e.ItemId)));
         }
 
-        query = query.DistinctBy(e => e.CleanValue);
+        // query = query.DistinctBy(e => e.CleanValue);
         return query.Select(e => e.CleanValue).ToImmutableArray();
     }
 
@@ -2131,12 +2139,12 @@ public sealed class BaseItemRepository(IDbContextFactory<JellyfinDbContext> dbPr
             ItemSortBy.AirTime => e => e.SortName, // TODO
             ItemSortBy.Runtime => e => e.RunTimeTicks,
             ItemSortBy.Random => e => EF.Functions.Random(),
-            ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.LastPlayedDate,
-            ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.PlayCount,
-            ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.IsFavorite,
+            ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.LastPlayedDate,
+            ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.PlayCount,
+            ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.IsFavorite,
             ItemSortBy.IsFolder => e => e.IsFolder,
-            ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played,
-            ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id) && f.Key == e.UserDataKey)!.Played,
+            ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.Played,
+            ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id && f.Key == e.UserDataKey)!.Played,
             ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
             ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.Type == 0).Select(f => f.CleanValue),
             ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.Type == 1).Select(f => f.CleanValue),

+ 16 - 10
Jellyfin.Server.Implementations/JellyfinDbContext.cs

@@ -4,20 +4,18 @@ using Jellyfin.Data.Entities;
 using Jellyfin.Data.Entities.Security;
 using Jellyfin.Data.Interfaces;
 using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
 
 namespace Jellyfin.Server.Implementations;
 
 /// <inheritdoc/>
-public class JellyfinDbContext : DbContext
+/// <summary>
+/// Initializes a new instance of the <see cref="JellyfinDbContext"/> class.
+/// </summary>
+/// <param name="options">The database context options.</param>
+/// <param name="logger">Logger.</param>
+public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILogger<JellyfinDbContext> logger) : DbContext(options)
 {
-    /// <summary>
-    /// Initializes a new instance of the <see cref="JellyfinDbContext"/> class.
-    /// </summary>
-    /// <param name="options">The database context options.</param>
-    public JellyfinDbContext(DbContextOptions<JellyfinDbContext> options) : base(options)
-    {
-    }
-
     /// <summary>
     /// Gets the <see cref="DbSet{TEntity}"/> containing the access schedules.
     /// </summary>
@@ -228,7 +226,15 @@ public class JellyfinDbContext : DbContext
             saveEntity.OnSavingChanges();
         }
 
-        return base.SaveChanges();
+        try
+        {
+            return base.SaveChanges();
+        }
+        catch (Exception e)
+        {
+            logger.LogError(e, "Error trying to save changes.");
+            throw;
+        }
     }
 
     /// <inheritdoc />

+ 0 - 775
Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.Designer.cs

@@ -1,775 +0,0 @@
-// <auto-generated />
-using System;
-using Jellyfin.Server.Implementations;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-
-#nullable disable
-
-namespace Jellyfin.Server.Implementations.Migrations
-{
-    [DbContext(typeof(JellyfinDbContext))]
-    [Migration("20240907123425_UserDataInJfLib")]
-    partial class UserDataInJfLib
-    {
-        /// <inheritdoc />
-        protected override void BuildTargetModel(ModelBuilder modelBuilder)
-        {
-#pragma warning disable 612, 618
-            modelBuilder.HasAnnotation("ProductVersion", "8.0.8");
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("DayOfWeek")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<double>("EndHour")
-                        .HasColumnType("REAL");
-
-                    b.Property<double>("StartHour")
-                        .HasColumnType("REAL");
-
-                    b.Property<Guid>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("UserId");
-
-                    b.ToTable("AccessSchedules");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<DateTime>("DateCreated")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("ItemId")
-                        .HasMaxLength(256)
-                        .HasColumnType("TEXT");
-
-                    b.Property<int>("LogSeverity")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Name")
-                        .IsRequired()
-                        .HasMaxLength(512)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Overview")
-                        .HasMaxLength(512)
-                        .HasColumnType("TEXT");
-
-                    b.Property<uint>("RowVersion")
-                        .IsConcurrencyToken()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("ShortOverview")
-                        .HasMaxLength(512)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Type")
-                        .IsRequired()
-                        .HasMaxLength(256)
-                        .HasColumnType("TEXT");
-
-                    b.Property<Guid>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("DateCreated");
-
-                    b.ToTable("ActivityLogs");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Client")
-                        .IsRequired()
-                        .HasMaxLength(32)
-                        .HasColumnType("TEXT");
-
-                    b.Property<Guid>("ItemId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Key")
-                        .IsRequired()
-                        .HasColumnType("TEXT");
-
-                    b.Property<Guid>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Value")
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("UserId", "ItemId", "Client", "Key")
-                        .IsUnique();
-
-                    b.ToTable("CustomItemDisplayPreferences");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("ChromecastVersion")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Client")
-                        .IsRequired()
-                        .HasMaxLength(32)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("DashboardTheme")
-                        .HasMaxLength(32)
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool>("EnableNextVideoInfoOverlay")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int?>("IndexBy")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid>("ItemId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<int>("ScrollDirection")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("ShowBackdrop")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("ShowSidebar")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("SkipBackwardLength")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("SkipForwardLength")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("TvHome")
-                        .HasMaxLength(32)
-                        .HasColumnType("TEXT");
-
-                    b.Property<Guid>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("UserId", "ItemId", "Client")
-                        .IsUnique();
-
-                    b.ToTable("DisplayPreferences");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("DisplayPreferencesId")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Order")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Type")
-                        .HasColumnType("INTEGER");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("DisplayPreferencesId");
-
-                    b.ToTable("HomeSection");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<DateTime>("LastModified")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Path")
-                        .IsRequired()
-                        .HasMaxLength(512)
-                        .HasColumnType("TEXT");
-
-                    b.Property<Guid?>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("UserId")
-                        .IsUnique();
-
-                    b.ToTable("ImageInfos");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Client")
-                        .IsRequired()
-                        .HasMaxLength(32)
-                        .HasColumnType("TEXT");
-
-                    b.Property<int?>("IndexBy")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid>("ItemId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool>("RememberIndexing")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("RememberSorting")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("SortBy")
-                        .IsRequired()
-                        .HasMaxLength(64)
-                        .HasColumnType("TEXT");
-
-                    b.Property<int>("SortOrder")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<int>("ViewType")
-                        .HasColumnType("INTEGER");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("UserId");
-
-                    b.ToTable("ItemDisplayPreferences");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.MediaSegment", b =>
-                {
-                    b.Property<Guid>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("TEXT");
-
-                    b.Property<long>("EndTicks")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid>("ItemId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("SegmentProviderId")
-                        .IsRequired()
-                        .HasColumnType("TEXT");
-
-                    b.Property<long>("StartTicks")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Type")
-                        .HasColumnType("INTEGER");
-
-                    b.HasKey("Id");
-
-                    b.ToTable("MediaSegments");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Kind")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid?>("Permission_Permissions_Guid")
-                        .HasColumnType("TEXT");
-
-                    b.Property<uint>("RowVersion")
-                        .IsConcurrencyToken()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid?>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool>("Value")
-                        .HasColumnType("INTEGER");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("UserId", "Kind")
-                        .IsUnique()
-                        .HasFilter("[UserId] IS NOT NULL");
-
-                    b.ToTable("Permissions");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Kind")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid?>("Preference_Preferences_Guid")
-                        .HasColumnType("TEXT");
-
-                    b.Property<uint>("RowVersion")
-                        .IsConcurrencyToken()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid?>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Value")
-                        .IsRequired()
-                        .HasMaxLength(65535)
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("UserId", "Kind")
-                        .IsUnique()
-                        .HasFilter("[UserId] IS NOT NULL");
-
-                    b.ToTable("Preferences");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("AccessToken")
-                        .IsRequired()
-                        .HasColumnType("TEXT");
-
-                    b.Property<DateTime>("DateCreated")
-                        .HasColumnType("TEXT");
-
-                    b.Property<DateTime>("DateLastActivity")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("Name")
-                        .IsRequired()
-                        .HasMaxLength(64)
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("AccessToken")
-                        .IsUnique();
-
-                    b.ToTable("ApiKeys");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("AccessToken")
-                        .IsRequired()
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("AppName")
-                        .IsRequired()
-                        .HasMaxLength(64)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("AppVersion")
-                        .IsRequired()
-                        .HasMaxLength(32)
-                        .HasColumnType("TEXT");
-
-                    b.Property<DateTime>("DateCreated")
-                        .HasColumnType("TEXT");
-
-                    b.Property<DateTime>("DateLastActivity")
-                        .HasColumnType("TEXT");
-
-                    b.Property<DateTime>("DateModified")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("DeviceId")
-                        .IsRequired()
-                        .HasMaxLength(256)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("DeviceName")
-                        .IsRequired()
-                        .HasMaxLength(64)
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool>("IsActive")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("DeviceId");
-
-                    b.HasIndex("AccessToken", "DateLastActivity");
-
-                    b.HasIndex("DeviceId", "DateLastActivity");
-
-                    b.HasIndex("UserId", "DeviceId");
-
-                    b.ToTable("Devices");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
-                {
-                    b.Property<int>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("CustomName")
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("DeviceId")
-                        .IsRequired()
-                        .HasColumnType("TEXT");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("DeviceId")
-                        .IsUnique();
-
-                    b.ToTable("DeviceOptions");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.TrickplayInfo", b =>
-                {
-                    b.Property<Guid>("ItemId")
-                        .HasColumnType("TEXT");
-
-                    b.Property<int>("Width")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Bandwidth")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Height")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("Interval")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("ThumbnailCount")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("TileHeight")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("TileWidth")
-                        .HasColumnType("INTEGER");
-
-                    b.HasKey("ItemId", "Width");
-
-                    b.ToTable("TrickplayInfos");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
-                {
-                    b.Property<Guid>("Id")
-                        .ValueGeneratedOnAdd()
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("AudioLanguagePreference")
-                        .HasMaxLength(255)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("AuthenticationProviderId")
-                        .IsRequired()
-                        .HasMaxLength(255)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("CastReceiverId")
-                        .HasMaxLength(32)
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool>("DisplayCollectionsView")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("DisplayMissingEpisodes")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("EnableAutoLogin")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("EnableLocalPassword")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("EnableNextEpisodeAutoPlay")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("EnableUserPreferenceAccess")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("HidePlayedInLatest")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<long>("InternalId")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("InvalidLoginAttemptCount")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<DateTime?>("LastActivityDate")
-                        .HasColumnType("TEXT");
-
-                    b.Property<DateTime?>("LastLoginDate")
-                        .HasColumnType("TEXT");
-
-                    b.Property<int?>("LoginAttemptsBeforeLockout")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("MaxActiveSessions")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int?>("MaxParentalAgeRating")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("MustUpdatePassword")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Password")
-                        .HasMaxLength(65535)
-                        .HasColumnType("TEXT");
-
-                    b.Property<string>("PasswordResetProviderId")
-                        .IsRequired()
-                        .HasMaxLength(255)
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool>("PlayDefaultAudioTrack")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("RememberAudioSelections")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("RememberSubtitleSelections")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int?>("RemoteClientBitrateLimit")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<uint>("RowVersion")
-                        .IsConcurrencyToken()
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("SubtitleLanguagePreference")
-                        .HasMaxLength(255)
-                        .HasColumnType("TEXT");
-
-                    b.Property<int>("SubtitleMode")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("SyncPlayAccess")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Username")
-                        .IsRequired()
-                        .HasMaxLength(255)
-                        .HasColumnType("TEXT")
-                        .UseCollation("NOCASE");
-
-                    b.HasKey("Id");
-
-                    b.HasIndex("Username")
-                        .IsUnique();
-
-                    b.ToTable("Users");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.UserData", b =>
-                {
-                    b.Property<int?>("AudioStreamIndex")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("IsFavorite")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<string>("Key")
-                        .IsRequired()
-                        .HasColumnType("TEXT");
-
-                    b.Property<DateTime?>("LastPlayedDate")
-                        .HasColumnType("TEXT");
-
-                    b.Property<bool?>("Likes")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<int>("PlayCount")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<long>("PlaybackPositionTicks")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<bool>("Played")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<double?>("Rating")
-                        .HasColumnType("REAL");
-
-                    b.Property<int?>("SubtitleStreamIndex")
-                        .HasColumnType("INTEGER");
-
-                    b.Property<Guid>("UserId")
-                        .HasColumnType("TEXT");
-
-                    b.HasIndex("UserId");
-
-                    b.HasIndex("Key", "UserId")
-                        .IsUnique();
-
-                    b.HasIndex("Key", "UserId", "IsFavorite");
-
-                    b.HasIndex("Key", "UserId", "LastPlayedDate");
-
-                    b.HasIndex("Key", "UserId", "PlaybackPositionTicks");
-
-                    b.HasIndex("Key", "UserId", "Played");
-
-                    b.ToTable("UserData");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", null)
-                        .WithMany("AccessSchedules")
-                        .HasForeignKey("UserId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", null)
-                        .WithMany("DisplayPreferences")
-                        .HasForeignKey("UserId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
-                        .WithMany("HomeSections")
-                        .HasForeignKey("DisplayPreferencesId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", null)
-                        .WithOne("ProfileImage")
-                        .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId")
-                        .OnDelete(DeleteBehavior.Cascade);
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", null)
-                        .WithMany("ItemDisplayPreferences")
-                        .HasForeignKey("UserId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", null)
-                        .WithMany("Permissions")
-                        .HasForeignKey("UserId")
-                        .OnDelete(DeleteBehavior.Cascade);
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", null)
-                        .WithMany("Preferences")
-                        .HasForeignKey("UserId")
-                        .OnDelete(DeleteBehavior.Cascade);
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", "User")
-                        .WithMany()
-                        .HasForeignKey("UserId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
-
-                    b.Navigation("User");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.UserData", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.User", "User")
-                        .WithMany()
-                        .HasForeignKey("UserId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
-
-                    b.Navigation("User");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
-                {
-                    b.Navigation("HomeSections");
-                });
-
-            modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
-                {
-                    b.Navigation("AccessSchedules");
-
-                    b.Navigation("DisplayPreferences");
-
-                    b.Navigation("ItemDisplayPreferences");
-
-                    b.Navigation("Permissions");
-
-                    b.Navigation("Preferences");
-
-                    b.Navigation("ProfileImage");
-                });
-#pragma warning restore 612, 618
-        }
-    }
-}

+ 0 - 79
Jellyfin.Server.Implementations/Migrations/20240907123425_UserDataInJfLib.cs

@@ -1,79 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace Jellyfin.Server.Implementations.Migrations
-{
-    /// <inheritdoc />
-    public partial class UserDataInJfLib : Migration
-    {
-        /// <inheritdoc />
-        protected override void Up(MigrationBuilder migrationBuilder)
-        {
-            migrationBuilder.CreateTable(
-                name: "UserData",
-                columns: table => new
-                {
-                    Key = table.Column<string>(type: "TEXT", nullable: false),
-                    Rating = table.Column<double>(type: "REAL", nullable: true),
-                    PlaybackPositionTicks = table.Column<long>(type: "INTEGER", nullable: false),
-                    PlayCount = table.Column<int>(type: "INTEGER", nullable: false),
-                    IsFavorite = table.Column<bool>(type: "INTEGER", nullable: false),
-                    LastPlayedDate = table.Column<DateTime>(type: "TEXT", nullable: true),
-                    Played = table.Column<bool>(type: "INTEGER", nullable: false),
-                    AudioStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
-                    SubtitleStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
-                    Likes = table.Column<bool>(type: "INTEGER", nullable: true),
-                    UserId = table.Column<Guid>(type: "TEXT", nullable: false)
-                },
-                constraints: table =>
-                {
-                    table.ForeignKey(
-                        name: "FK_UserData_Users_UserId",
-                        column: x => x.UserId,
-                        principalTable: "Users",
-                        principalColumn: "Id",
-                        onDelete: ReferentialAction.Cascade);
-                });
-
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_Key_UserId",
-                table: "UserData",
-                columns: new[] { "Key", "UserId" },
-                unique: true);
-
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_Key_UserId_IsFavorite",
-                table: "UserData",
-                columns: new[] { "Key", "UserId", "IsFavorite" });
-
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_Key_UserId_LastPlayedDate",
-                table: "UserData",
-                columns: new[] { "Key", "UserId", "LastPlayedDate" });
-
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_Key_UserId_PlaybackPositionTicks",
-                table: "UserData",
-                columns: new[] { "Key", "UserId", "PlaybackPositionTicks" });
-
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_Key_UserId_Played",
-                table: "UserData",
-                columns: new[] { "Key", "UserId", "Played" });
-
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_UserId",
-                table: "UserData",
-                column: "UserId");
-        }
-
-        /// <inheritdoc />
-        protected override void Down(MigrationBuilder migrationBuilder)
-        {
-            migrationBuilder.DropTable(
-                name: "UserData");
-        }
-    }
-}

+ 1 - 40
Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.Designer.cs → Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.Designer.cs

@@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
 namespace Jellyfin.Server.Implementations.Migrations
 {
     [DbContext(typeof(JellyfinDbContext))]
-    [Migration("20241009112234_BaseItemRefactor")]
+    [Migration("20241009132112_BaseItemRefactor")]
     partial class BaseItemRefactor
     {
         /// <inheritdoc />
@@ -379,10 +379,6 @@ namespace Jellyfin.Server.Implementations.Migrations
 
                     b.HasIndex("PresentationUniqueKey");
 
-                    b.HasIndex("SeasonId");
-
-                    b.HasIndex("SeriesId");
-
                     b.HasIndex("TopParentId", "Id");
 
                     b.HasIndex("UserDataKey", "Type");
@@ -1275,33 +1271,6 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.Navigation("Item");
                 });
 
-            modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Parent")
-                        .WithMany("DirectChildren")
-                        .HasForeignKey("ParentId");
-
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Season")
-                        .WithMany("SeasonEpisodes")
-                        .HasForeignKey("SeasonId");
-
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Series")
-                        .WithMany("SeriesEpisodes")
-                        .HasForeignKey("SeriesId");
-
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "TopParent")
-                        .WithMany("AllChildren")
-                        .HasForeignKey("TopParentId");
-
-                    b.Navigation("Parent");
-
-                    b.Navigation("Season");
-
-                    b.Navigation("Series");
-
-                    b.Navigation("TopParent");
-                });
-
             modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b =>
                 {
                     b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
@@ -1436,14 +1405,10 @@ namespace Jellyfin.Server.Implementations.Migrations
 
             modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
                 {
-                    b.Navigation("AllChildren");
-
                     b.Navigation("AncestorIds");
 
                     b.Navigation("Chapters");
 
-                    b.Navigation("DirectChildren");
-
                     b.Navigation("ItemValues");
 
                     b.Navigation("MediaStreams");
@@ -1452,10 +1417,6 @@ namespace Jellyfin.Server.Implementations.Migrations
 
                     b.Navigation("Provider");
 
-                    b.Navigation("SeasonEpisodes");
-
-                    b.Navigation("SeriesEpisodes");
-
                     b.Navigation("UserData");
                 });
 

+ 63 - 76
Jellyfin.Server.Implementations/Migrations/20241009112234_BaseItemRefactor.cs → Jellyfin.Server.Implementations/Migrations/20241009132112_BaseItemRefactor.cs

@@ -11,21 +11,6 @@ namespace Jellyfin.Server.Implementations.Migrations
         /// <inheritdoc />
         protected override void Up(MigrationBuilder migrationBuilder)
         {
-            migrationBuilder.DropIndex(
-                name: "IX_UserData_Key_UserId",
-                table: "UserData");
-
-            migrationBuilder.AddColumn<Guid>(
-                name: "BaseItemEntityId",
-                table: "UserData",
-                type: "TEXT",
-                nullable: true);
-
-            migrationBuilder.AddPrimaryKey(
-                name: "PK_UserData",
-                table: "UserData",
-                columns: new[] { "Key", "UserId" });
-
             migrationBuilder.CreateTable(
                 name: "BaseItems",
                 columns: table => new
@@ -109,26 +94,6 @@ namespace Jellyfin.Server.Implementations.Migrations
                 constraints: table =>
                 {
                     table.PrimaryKey("PK_BaseItems", x => x.Id);
-                    table.ForeignKey(
-                        name: "FK_BaseItems_BaseItems_ParentId",
-                        column: x => x.ParentId,
-                        principalTable: "BaseItems",
-                        principalColumn: "Id");
-                    table.ForeignKey(
-                        name: "FK_BaseItems_BaseItems_SeasonId",
-                        column: x => x.SeasonId,
-                        principalTable: "BaseItems",
-                        principalColumn: "Id");
-                    table.ForeignKey(
-                        name: "FK_BaseItems_BaseItems_SeriesId",
-                        column: x => x.SeriesId,
-                        principalTable: "BaseItems",
-                        principalColumn: "Id");
-                    table.ForeignKey(
-                        name: "FK_BaseItems_BaseItems_TopParentId",
-                        column: x => x.TopParentId,
-                        principalTable: "BaseItems",
-                        principalColumn: "Id");
                 });
 
             migrationBuilder.CreateTable(
@@ -318,10 +283,38 @@ namespace Jellyfin.Server.Implementations.Migrations
                         onDelete: ReferentialAction.Cascade);
                 });
 
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_BaseItemEntityId",
-                table: "UserData",
-                column: "BaseItemEntityId");
+            migrationBuilder.CreateTable(
+                name: "UserData",
+                columns: table => new
+                {
+                    Key = table.Column<string>(type: "TEXT", nullable: false),
+                    UserId = table.Column<Guid>(type: "TEXT", nullable: false),
+                    Rating = table.Column<double>(type: "REAL", nullable: true),
+                    PlaybackPositionTicks = table.Column<long>(type: "INTEGER", nullable: false),
+                    PlayCount = table.Column<int>(type: "INTEGER", nullable: false),
+                    IsFavorite = table.Column<bool>(type: "INTEGER", nullable: false),
+                    LastPlayedDate = table.Column<DateTime>(type: "TEXT", nullable: true),
+                    Played = table.Column<bool>(type: "INTEGER", nullable: false),
+                    AudioStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
+                    SubtitleStreamIndex = table.Column<int>(type: "INTEGER", nullable: true),
+                    Likes = table.Column<bool>(type: "INTEGER", nullable: true),
+                    BaseItemEntityId = table.Column<Guid>(type: "TEXT", nullable: true)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_UserData", x => new { x.Key, x.UserId });
+                    table.ForeignKey(
+                        name: "FK_UserData_BaseItems_BaseItemEntityId",
+                        column: x => x.BaseItemEntityId,
+                        principalTable: "BaseItems",
+                        principalColumn: "Id");
+                    table.ForeignKey(
+                        name: "FK_UserData_Users_UserId",
+                        column: x => x.UserId,
+                        principalTable: "Users",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Cascade);
+                });
 
             migrationBuilder.CreateIndex(
                 name: "IX_AncestorIds_Id",
@@ -368,16 +361,6 @@ namespace Jellyfin.Server.Implementations.Migrations
                 table: "BaseItems",
                 column: "PresentationUniqueKey");
 
-            migrationBuilder.CreateIndex(
-                name: "IX_BaseItems_SeasonId",
-                table: "BaseItems",
-                column: "SeasonId");
-
-            migrationBuilder.CreateIndex(
-                name: "IX_BaseItems_SeriesId",
-                table: "BaseItems",
-                column: "SeriesId");
-
             migrationBuilder.CreateIndex(
                 name: "IX_BaseItems_TopParentId_Id",
                 table: "BaseItems",
@@ -453,21 +436,40 @@ namespace Jellyfin.Server.Implementations.Migrations
                 table: "Peoples",
                 column: "Name");
 
-            migrationBuilder.AddForeignKey(
-                name: "FK_UserData_BaseItems_BaseItemEntityId",
+            migrationBuilder.CreateIndex(
+                name: "IX_UserData_BaseItemEntityId",
+                table: "UserData",
+                column: "BaseItemEntityId");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_UserData_Key_UserId_IsFavorite",
+                table: "UserData",
+                columns: new[] { "Key", "UserId", "IsFavorite" });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_UserData_Key_UserId_LastPlayedDate",
+                table: "UserData",
+                columns: new[] { "Key", "UserId", "LastPlayedDate" });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_UserData_Key_UserId_PlaybackPositionTicks",
                 table: "UserData",
-                column: "BaseItemEntityId",
-                principalTable: "BaseItems",
-                principalColumn: "Id");
+                columns: new[] { "Key", "UserId", "PlaybackPositionTicks" });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_UserData_Key_UserId_Played",
+                table: "UserData",
+                columns: new[] { "Key", "UserId", "Played" });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_UserData_UserId",
+                table: "UserData",
+                column: "UserId");
         }
 
         /// <inheritdoc />
         protected override void Down(MigrationBuilder migrationBuilder)
         {
-            migrationBuilder.DropForeignKey(
-                name: "FK_UserData_BaseItems_BaseItemEntityId",
-                table: "UserData");
-
             migrationBuilder.DropTable(
                 name: "AncestorIds");
 
@@ -490,25 +492,10 @@ namespace Jellyfin.Server.Implementations.Migrations
                 name: "Peoples");
 
             migrationBuilder.DropTable(
-                name: "BaseItems");
+                name: "UserData");
 
-            migrationBuilder.DropPrimaryKey(
-                name: "PK_UserData",
-                table: "UserData");
-
-            migrationBuilder.DropIndex(
-                name: "IX_UserData_BaseItemEntityId",
-                table: "UserData");
-
-            migrationBuilder.DropColumn(
-                name: "BaseItemEntityId",
-                table: "UserData");
-
-            migrationBuilder.CreateIndex(
-                name: "IX_UserData_Key_UserId",
-                table: "UserData",
-                columns: new[] { "Key", "UserId" },
-                unique: true);
+            migrationBuilder.DropTable(
+                name: "BaseItems");
         }
     }
 }

+ 2 - 1
Jellyfin.Server.Implementations/Migrations/DesignTimeJellyfinDbFactory.cs

@@ -1,5 +1,6 @@
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Design;
+using Microsoft.Extensions.Logging.Abstractions;
 
 namespace Jellyfin.Server.Implementations.Migrations
 {
@@ -14,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
             var optionsBuilder = new DbContextOptionsBuilder<JellyfinDbContext>();
             optionsBuilder.UseSqlite("Data Source=jellyfin.db");
 
-            return new JellyfinDbContext(optionsBuilder.Options);
+            return new JellyfinDbContext(optionsBuilder.Options, NullLogger<JellyfinDbContext>.Instance);
         }
     }
 }

+ 0 - 39
Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs

@@ -376,10 +376,6 @@ namespace Jellyfin.Server.Implementations.Migrations
 
                     b.HasIndex("PresentationUniqueKey");
 
-                    b.HasIndex("SeasonId");
-
-                    b.HasIndex("SeriesId");
-
                     b.HasIndex("TopParentId", "Id");
 
                     b.HasIndex("UserDataKey", "Type");
@@ -1272,33 +1268,6 @@ namespace Jellyfin.Server.Implementations.Migrations
                     b.Navigation("Item");
                 });
 
-            modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
-                {
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Parent")
-                        .WithMany("DirectChildren")
-                        .HasForeignKey("ParentId");
-
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Season")
-                        .WithMany("SeasonEpisodes")
-                        .HasForeignKey("SeasonId");
-
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Series")
-                        .WithMany("SeriesEpisodes")
-                        .HasForeignKey("SeriesId");
-
-                    b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "TopParent")
-                        .WithMany("AllChildren")
-                        .HasForeignKey("TopParentId");
-
-                    b.Navigation("Parent");
-
-                    b.Navigation("Season");
-
-                    b.Navigation("Series");
-
-                    b.Navigation("TopParent");
-                });
-
             modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemProvider", b =>
                 {
                     b.HasOne("Jellyfin.Data.Entities.BaseItemEntity", "Item")
@@ -1433,14 +1402,10 @@ namespace Jellyfin.Server.Implementations.Migrations
 
             modelBuilder.Entity("Jellyfin.Data.Entities.BaseItemEntity", b =>
                 {
-                    b.Navigation("AllChildren");
-
                     b.Navigation("AncestorIds");
 
                     b.Navigation("Chapters");
 
-                    b.Navigation("DirectChildren");
-
                     b.Navigation("ItemValues");
 
                     b.Navigation("MediaStreams");
@@ -1449,10 +1414,6 @@ namespace Jellyfin.Server.Implementations.Migrations
 
                     b.Navigation("Provider");
 
-                    b.Navigation("SeasonEpisodes");
-
-                    b.Navigation("SeriesEpisodes");
-
                     b.Navigation("UserData");
                 });
 

+ 5 - 4
Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs

@@ -15,10 +15,11 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
     public void Configure(EntityTypeBuilder<BaseItemEntity> builder)
     {
         builder.HasKey(e => e.Id);
-        builder.HasOne(e => e.Parent).WithMany(e => e.DirectChildren).HasForeignKey(e => e.ParentId);
-        builder.HasOne(e => e.TopParent).WithMany(e => e.AllChildren).HasForeignKey(e => e.TopParentId);
-        builder.HasOne(e => e.Season).WithMany(e => e.SeasonEpisodes).HasForeignKey(e => e.SeasonId);
-        builder.HasOne(e => e.Series).WithMany(e => e.SeriesEpisodes).HasForeignKey(e => e.SeriesId);
+        // TODO: See rant in entity file.
+        // builder.HasOne(e => e.Parent).WithMany(e => e.DirectChildren).HasForeignKey(e => e.ParentId);
+        // builder.HasOne(e => e.TopParent).WithMany(e => e.AllChildren).HasForeignKey(e => e.TopParentId);
+        // builder.HasOne(e => e.Season).WithMany(e => e.SeasonEpisodes).HasForeignKey(e => e.SeasonId);
+        // builder.HasOne(e => e.Series).WithMany(e => e.SeriesEpisodes).HasForeignKey(e => e.SeriesId);
         builder.HasMany(e => e.Peoples);
         builder.HasMany(e => e.UserData);
         builder.HasMany(e => e.ItemValues);

+ 1 - 1
MediaBrowser.Controller/Providers/MetadataResult.cs

@@ -36,7 +36,7 @@ namespace MediaBrowser.Controller.Providers
         public IReadOnlyList<PersonInfo> People
         {
             get => _people;
-            set => _people = value.ToList();
+            set => _people = value?.ToList();
         }
 
         public bool HasMetadata { get; set; }

+ 1 - 1
tests/Jellyfin.Providers.Tests/Manager/MetadataServiceTests.cs

@@ -330,7 +330,7 @@ namespace Jellyfin.Providers.Tests.Manager
             MetadataService<Movie, MovieInfo>.MergeBaseItemData(source, target, lockedFields, replaceData, false);
 
             actualValue = target.People;
-            return newValue?.Equals(actualValue) ?? actualValue is null;
+            return newValue?.SequenceEqual((IEnumerable<PersonInfo>)actualValue!) ?? actualValue is null;
         }
 
         /// <summary>

+ 15 - 4
tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs

@@ -13,7 +13,7 @@ using Xunit.Priority;
 
 namespace Jellyfin.Server.Integration.Tests.Controllers;
 
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+// [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
 public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinApplicationFactory>
 {
     private readonly JellyfinApplicationFactory _factory;
@@ -62,12 +62,23 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
     }
 
     [Fact]
-    [Priority(0)]
+    [Priority(-2)]
     public async Task UpdateLibraryOptions_Valid_Success()
     {
         var client = _factory.CreateClient();
         client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
 
+        var createBody = new AddVirtualFolderDto()
+        {
+            LibraryOptions = new LibraryOptions()
+            {
+                Enabled = false
+            }
+        };
+
+        using var createResponse = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", createBody, _jsonOptions);
+        Assert.Equal(HttpStatusCode.NoContent, createResponse.StatusCode);
+
         using var response = await client.GetAsync("Library/VirtualFolders");
         Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 
@@ -80,13 +91,13 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
         Assert.False(options.Enabled);
         options.Enabled = true;
 
-        var body = new UpdateLibraryOptionsDto()
+        var existBody = new UpdateLibraryOptionsDto()
         {
             Id = Guid.Parse(library.ItemId),
             LibraryOptions = options
         };
 
-        using var response2 = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions);
+        using var response2 = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", existBody, _jsonOptions);
         Assert.Equal(HttpStatusCode.NoContent, response2.StatusCode);
     }