浏览代码

i have too much time.
Refactored BaseItem and UserData relation

JPVenson 7 月之前
父节点
当前提交
10a2a316a4

+ 7 - 4
Emby.Server.Implementations/Library/UserDataManager.cs

@@ -16,6 +16,7 @@ using MediaBrowser.Model.Entities;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore;
 using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
 using AudioBook = MediaBrowser.Controller.Entities.AudioBook;
 using Book = MediaBrowser.Controller.Entities.Book;
 using Book = MediaBrowser.Controller.Entities.Book;
+#pragma warning disable RS0030 // Do not use banned APIs
 
 
 namespace Emby.Server.Implementations.Library
 namespace Emby.Server.Implementations.Library
 {
 {
@@ -134,7 +135,9 @@ namespace Emby.Server.Implementations.Library
         {
         {
             return new UserData()
             return new UserData()
             {
             {
-                Key = dto.Key,
+                ItemId = Guid.Parse(dto.Key),
+                Item = null!,
+                User = null!,
                 AudioStreamIndex = dto.AudioStreamIndex,
                 AudioStreamIndex = dto.AudioStreamIndex,
                 IsFavorite = dto.IsFavorite,
                 IsFavorite = dto.IsFavorite,
                 LastPlayedDate = dto.LastPlayedDate,
                 LastPlayedDate = dto.LastPlayedDate,
@@ -152,7 +155,7 @@ namespace Emby.Server.Implementations.Library
         {
         {
             return new UserItemData()
             return new UserItemData()
             {
             {
-                Key = dto.Key,
+                Key = dto.ItemId.ToString("D"),
                 AudioStreamIndex = dto.AudioStreamIndex,
                 AudioStreamIndex = dto.AudioStreamIndex,
                 IsFavorite = dto.IsFavorite,
                 IsFavorite = dto.IsFavorite,
                 LastPlayedDate = dto.LastPlayedDate,
                 LastPlayedDate = dto.LastPlayedDate,
@@ -182,12 +185,12 @@ namespace Emby.Server.Implementations.Library
         {
         {
             using var context = _repository.CreateDbContext();
             using var context = _repository.CreateDbContext();
             var key = keys.FirstOrDefault();
             var key = keys.FirstOrDefault();
-            if (key is null)
+            if (key is null || Guid.TryParse(key, out var itemId))
             {
             {
                 return null;
                 return null;
             }
             }
 
 
-            var userData = context.UserData.AsNoTracking().FirstOrDefault(e => e.Key == key && e.UserId.Equals(userId));
+            var userData = context.UserData.AsNoTracking().FirstOrDefault(e => e.ItemId == itemId && e.UserId.Equals(userId));
 
 
             if (userData is not null)
             if (userData is not null)
             {
             {

+ 0 - 2
Jellyfin.Data/Entities/BaseItemEntity.cs

@@ -110,8 +110,6 @@ public class BaseItemEntity
 
 
     public string? SeriesName { get; set; }
     public string? SeriesName { get; set; }
 
 
-    public string? UserDataKey { get; set; }
-
     public string? SeasonName { get; set; }
     public string? SeasonName { get; set; }
 
 
     public string? ExternalSeriesId { get; set; }
     public string? ExternalSeriesId { get; set; }

+ 13 - 8
Jellyfin.Data/Entities/UserData.cs

@@ -8,12 +8,6 @@ namespace Jellyfin.Data.Entities;
 /// </summary>
 /// </summary>
 public class UserData
 public class UserData
 {
 {
-    /// <summary>
-    /// Gets or sets the key.
-    /// </summary>
-    /// <value>The key.</value>
-    public required string Key { get; set; }
-
     /// <summary>
     /// <summary>
     /// Gets or sets the users 0-10 rating.
     /// Gets or sets the users 0-10 rating.
     /// </summary>
     /// </summary>
@@ -69,13 +63,24 @@ public class UserData
     /// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value>
     /// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value>
     public bool? Likes { get; set; }
     public bool? Likes { get; set; }
 
 
+    /// <summary>
+    /// Gets or sets the key.
+    /// </summary>
+    /// <value>The key.</value>
+    public required Guid ItemId { get; set; }
+
+    /// <summary>
+    /// Gets or Sets the BaseItem.
+    /// </summary>
+    public required BaseItemEntity? Item { get; set; }
+
     /// <summary>
     /// <summary>
     /// Gets or Sets the UserId.
     /// Gets or Sets the UserId.
     /// </summary>
     /// </summary>
-    public Guid UserId { get; set; }
+    public required Guid UserId { get; set; }
 
 
     /// <summary>
     /// <summary>
     /// Gets or Sets the User.
     /// Gets or Sets the User.
     /// </summary>
     /// </summary>
-    public User? User { get; set; }
+    public required User? User { get; set; }
 }
 }

+ 11 - 11
Jellyfin.Server.Implementations/Item/BaseItemRepository.cs

@@ -671,25 +671,25 @@ public sealed class BaseItemRepository(
         if (filter.IsLiked.HasValue)
         if (filter.IsLiked.HasValue)
         {
         {
             baseQuery = baseQuery
             baseQuery = baseQuery
-                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Rating >= UserItemData.MinLikeValue);
+                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Rating >= UserItemData.MinLikeValue);
         }
         }
 
 
         if (filter.IsFavoriteOrLiked.HasValue)
         if (filter.IsFavoriteOrLiked.HasValue)
         {
         {
             baseQuery = baseQuery
             baseQuery = baseQuery
-                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavoriteOrLiked);
+                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavoriteOrLiked);
         }
         }
 
 
         if (filter.IsFavorite.HasValue)
         if (filter.IsFavorite.HasValue)
         {
         {
             baseQuery = baseQuery
             baseQuery = baseQuery
-                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.IsFavorite == filter.IsFavorite);
+                .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.IsFavorite == filter.IsFavorite);
         }
         }
 
 
         if (filter.IsPlayed.HasValue)
         if (filter.IsPlayed.HasValue)
         {
         {
             baseQuery = baseQuery
             baseQuery = baseQuery
-                   .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.Played == filter.IsPlayed.Value);
+                   .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.Played == filter.IsPlayed.Value);
         }
         }
 
 
         if (filter.IsResumable.HasValue)
         if (filter.IsResumable.HasValue)
@@ -697,12 +697,12 @@ public sealed class BaseItemRepository(
             if (filter.IsResumable.Value)
             if (filter.IsResumable.Value)
             {
             {
                 baseQuery = baseQuery
                 baseQuery = baseQuery
-                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks > 0);
+                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks > 0);
             }
             }
             else
             else
             {
             {
                 baseQuery = baseQuery
                 baseQuery = baseQuery
-                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id && f.Key == e.UserDataKey)!.PlaybackPositionTicks == 0);
+                       .Where(e => e.UserData!.FirstOrDefault(f => f.UserId == filter.User!.Id)!.PlaybackPositionTicks == 0);
             }
             }
         }
         }
 
 
@@ -2019,12 +2019,12 @@ public sealed class BaseItemRepository(
             ItemSortBy.AirTime => e => e.SortName, // TODO
             ItemSortBy.AirTime => e => e.SortName, // TODO
             ItemSortBy.Runtime => e => e.RunTimeTicks,
             ItemSortBy.Runtime => e => e.RunTimeTicks,
             ItemSortBy.Random => e => EF.Functions.Random(),
             ItemSortBy.Random => e => EF.Functions.Random(),
-            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.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.LastPlayedDate,
+            ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.PlayCount,
+            ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.IsFavorite,
             ItemSortBy.IsFolder => e => e.IsFolder,
             ItemSortBy.IsFolder => e => e.IsFolder,
-            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.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.Played,
+            ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId == query.User!.Id)!.Played,
             ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
             ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
             ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue),
             ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue),
             ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue),
             ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue),

+ 1 - 1
Jellyfin.Server.Implementations/Item/PeopleRepository.cs

@@ -121,7 +121,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
         {
         {
             var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
             var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
             query = query.Where(e => e.PersonType == personType)
             query = query.Where(e => e.PersonType == personType)
-                .Where(e => context.BaseItems.Where(d => context.UserData.Where(w => w.IsFavorite == filter.IsFavorite && w.UserId.Equals(filter.User.Id)).Any(f => f.Key == d.UserDataKey))
+                .Where(e => context.BaseItems.Where(d => d.UserData!.Any(w => w.IsFavorite == filter.IsFavorite && w.UserId.Equals(filter.User.Id)))
                     .Select(f => f.Name).Contains(e.Name));
                     .Select(f => f.Name).Contains(e.Name));
         }
         }
 
 

+ 0 - 1
Jellyfin.Server.Implementations/ModelConfiguration/BaseItemConfiguration.cs

@@ -35,7 +35,6 @@ public class BaseItemConfiguration : IEntityTypeConfiguration<BaseItemEntity>
         builder.HasIndex(e => e.ParentId);
         builder.HasIndex(e => e.ParentId);
         builder.HasIndex(e => e.PresentationUniqueKey);
         builder.HasIndex(e => e.PresentationUniqueKey);
         builder.HasIndex(e => new { e.Id, e.Type, e.IsFolder, e.IsVirtualItem });
         builder.HasIndex(e => new { e.Id, e.Type, e.IsFolder, e.IsVirtualItem });
-        builder.HasIndex(e => new { e.UserDataKey, e.Type });
 
 
         // covering index
         // covering index
         builder.HasIndex(e => new { e.TopParentId, e.Id });
         builder.HasIndex(e => new { e.TopParentId, e.Id });

+ 6 - 5
Jellyfin.Server.Implementations/ModelConfiguration/UserDataConfiguration.cs

@@ -13,10 +13,11 @@ public class UserDataConfiguration : IEntityTypeConfiguration<UserData>
     /// <inheritdoc/>
     /// <inheritdoc/>
     public void Configure(EntityTypeBuilder<UserData> builder)
     public void Configure(EntityTypeBuilder<UserData> builder)
     {
     {
-        builder.HasKey(d => new { d.Key, d.UserId });
-        builder.HasIndex(d => new { d.Key, d.UserId, d.Played });
-        builder.HasIndex(d => new { d.Key, d.UserId, d.PlaybackPositionTicks });
-        builder.HasIndex(d => new { d.Key, d.UserId, d.IsFavorite });
-        builder.HasIndex(d => new { d.Key, d.UserId, d.LastPlayedDate });
+        builder.HasKey(d => new { d.ItemId, d.UserId });
+        builder.HasIndex(d => new { d.ItemId, d.UserId, d.Played });
+        builder.HasIndex(d => new { d.ItemId, d.UserId, d.PlaybackPositionTicks });
+        builder.HasIndex(d => new { d.ItemId, d.UserId, d.IsFavorite });
+        builder.HasIndex(d => new { d.ItemId, d.UserId, d.LastPlayedDate });
+        builder.HasOne(e => e.Item);
     }
     }
 }
 }

+ 45 - 25
Jellyfin.Server/Migrations/Routines/MigrateLibraryDb.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Collections.Immutable;
 using System.Data;
 using System.Data;
 using System.Diagnostics;
 using System.Diagnostics;
@@ -71,43 +72,55 @@ public class MigrateLibraryDb : IMigrationRoutine
         connection.Open();
         connection.Open();
         using var dbContext = _provider.CreateDbContext();
         using var dbContext = _provider.CreateDbContext();
 
 
+        var stepElapsed = stopwatch.Elapsed;
+        _logger.LogInformation("Saving UserData entries took {0}.", stepElapsed);
+
+        _logger.LogInformation("Start moving TypedBaseItem.");
+        var typedBaseItemsQuery = "SELECT type, data, StartDate, EndDate, ChannelId, IsMovie, IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, guid, Genres, ParentId, Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypedBaseItems";
+        dbContext.BaseItems.ExecuteDelete();
+
+        var legacyBaseItemWithUserKeys = new Dictionary<string, BaseItemEntity>();
+        foreach (SqliteDataReader dto in connection.Query(typedBaseItemsQuery))
+        {
+            var baseItem = GetItem(dto);
+            dbContext.BaseItems.Add(baseItem.BaseItem);
+            legacyBaseItemWithUserKeys[baseItem.LegacyUserDataKey] = baseItem.BaseItem;
+        }
+
+        _logger.LogInformation("Try saving {0} BaseItem entries.", dbContext.BaseItems.Local.Count);
+        dbContext.SaveChanges();
+        stepElapsed = stopwatch.Elapsed - stepElapsed;
+        _logger.LogInformation("Saving BaseItems entries took {0}.", stepElapsed);
+
         _logger.LogInformation("Start moving UserData.");
         _logger.LogInformation("Start moving UserData.");
         var queryResult = connection.Query("SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas");
         var queryResult = connection.Query("SELECT key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex FROM UserDatas");
 
 
         dbContext.UserData.ExecuteDelete();
         dbContext.UserData.ExecuteDelete();
 
 
         var users = dbContext.Users.AsNoTracking().ToImmutableArray();
         var users = dbContext.Users.AsNoTracking().ToImmutableArray();
+        var oldUserdata = new Dictionary<string, UserData>();
 
 
         foreach (var entity in queryResult)
         foreach (var entity in queryResult)
         {
         {
             var userData = GetUserData(users, entity);
             var userData = GetUserData(users, entity);
-            if (userData is null)
+            if (userData.Data is null)
             {
             {
                 _logger.LogError("Was not able to migrate user data with key {0}", entity.GetString(0));
                 _logger.LogError("Was not able to migrate user data with key {0}", entity.GetString(0));
                 continue;
                 continue;
             }
             }
 
 
-            dbContext.UserData.Add(userData);
-        }
-
-        _logger.LogInformation("Try saving {0} UserData entries.", dbContext.UserData.Local.Count);
-        dbContext.SaveChanges();
-        var stepElapsed = stopwatch.Elapsed;
-        _logger.LogInformation("Saving UserData entries took {0}.", stepElapsed);
-
-        _logger.LogInformation("Start moving TypedBaseItem.");
-        var typedBaseItemsQuery = "SELECT type, data, StartDate, EndDate, ChannelId, IsMovie, IsSeries, EpisodeTitle, IsRepeat, CommunityRating, CustomRating, IndexNumber, IsLocked, PreferredMetadataLanguage, PreferredMetadataCountryCode, Width, Height, DateLastRefreshed, Name, Path, PremiereDate, Overview, ParentIndexNumber, ProductionYear, OfficialRating, ForcedSortName, RunTimeTicks, Size, DateCreated, DateModified, guid, Genres, ParentId, Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId, DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, SeasonName, SeasonId, SeriesId, PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate, ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId FROM TypedBaseItems";
-        dbContext.BaseItems.ExecuteDelete();
+            if (!legacyBaseItemWithUserKeys.TryGetValue(userData.LegacyUserDataKey!, out var refItem))
+            {
+                _logger.LogError("Was not able to migrate user data with key {0} because it does not reference a valid BaseItem.", entity.GetString(0));
+                continue;
+            }
 
 
-        foreach (SqliteDataReader dto in connection.Query(typedBaseItemsQuery))
-        {
-            dbContext.BaseItems.Add(GetItem(dto));
+            userData.Data.ItemId = refItem.Id;
+            dbContext.UserData.Add(userData.Data);
         }
         }
 
 
-        _logger.LogInformation("Try saving {0} BaseItem entries.", dbContext.BaseItems.Local.Count);
+        _logger.LogInformation("Try saving {0} UserData entries.", dbContext.UserData.Local.Count);
         dbContext.SaveChanges();
         dbContext.SaveChanges();
-        stepElapsed = stopwatch.Elapsed - stepElapsed;
-        _logger.LogInformation("Saving BaseItems entries took {0}.", stepElapsed);
 
 
         _logger.LogInformation("Start moving MediaStreamInfos.");
         _logger.LogInformation("Start moving MediaStreamInfos.");
         var mediaStreamQuery = "SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path, IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width, AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag, Comment, NalLengthSize, IsAvc, Title, TimeBase, CodecTimeBase, ColorPrimaries, ColorSpace, ColorTransfer, DvVersionMajor, DvVersionMinor, DvProfile, DvLevel, RpuPresentFlag, ElPresentFlag, BlPresentFlag, DvBlSignalCompatibilityId, IsHearingImpaired FROM MediaStreams";
         var mediaStreamQuery = "SELECT ItemId, StreamIndex, StreamType, Codec, Language, ChannelLayout, Profile, AspectRatio, Path, IsInterlaced, BitRate, Channels, SampleRate, IsDefault, IsForced, IsExternal, Height, Width, AverageFrameRate, RealFrameRate, Level, PixelFormat, BitDepth, IsAnamorphic, RefFrames, CodecTag, Comment, NalLengthSize, IsAvc, Title, TimeBase, CodecTimeBase, ColorPrimaries, ColorSpace, ColorTransfer, DvVersionMajor, DvVersionMinor, DvProfile, DvLevel, RpuPresentFlag, ElPresentFlag, BlPresentFlag, DvBlSignalCompatibilityId, IsHearingImpaired FROM MediaStreams";
@@ -266,17 +279,19 @@ public class MigrateLibraryDb : IMigrationRoutine
         }
         }
     }
     }
 
 
-    private static UserData? GetUserData(ImmutableArray<User> users, SqliteDataReader dto)
+    private static (UserData? Data, string? LegacyUserDataKey) GetUserData(ImmutableArray<User> users, SqliteDataReader dto)
     {
     {
         var indexOfUser = dto.GetInt32(1);
         var indexOfUser = dto.GetInt32(1);
         if (users.Length < indexOfUser)
         if (users.Length < indexOfUser)
         {
         {
-            return null;
+            return (null, null);
         }
         }
 
 
-        return new UserData()
+        var oldKey = dto.GetString(0);
+
+        return (new UserData()
         {
         {
-            Key = dto.GetString(0),
+            ItemId = Guid.NewGuid(),
             UserId = users.ElementAt(indexOfUser).Id,
             UserId = users.ElementAt(indexOfUser).Id,
             Rating = dto.IsDBNull(2) ? null : dto.GetDouble(2),
             Rating = dto.IsDBNull(2) ? null : dto.GetDouble(2),
             Played = dto.GetBoolean(3),
             Played = dto.GetBoolean(3),
@@ -288,7 +303,8 @@ public class MigrateLibraryDb : IMigrationRoutine
             SubtitleStreamIndex = dto.IsDBNull(9) ? null : dto.GetInt32(9),
             SubtitleStreamIndex = dto.IsDBNull(9) ? null : dto.GetInt32(9),
             Likes = null,
             Likes = null,
             User = null!,
             User = null!,
-        };
+            Item = null!
+        }, oldKey);
     }
     }
 
 
     private AncestorId GetAncestorId(SqliteDataReader reader)
     private AncestorId GetAncestorId(SqliteDataReader reader)
@@ -604,7 +620,7 @@ public class MigrateLibraryDb : IMigrationRoutine
         return item;
         return item;
     }
     }
 
 
-    private BaseItemEntity GetItem(SqliteDataReader reader)
+    private (BaseItemEntity BaseItem, string LegacyUserDataKey) GetItem(SqliteDataReader reader)
     {
     {
         var entity = new BaseItemEntity()
         var entity = new BaseItemEntity()
         {
         {
@@ -870,6 +886,10 @@ public class MigrateLibraryDb : IMigrationRoutine
             entity.SeriesName = seriesName;
             entity.SeriesName = seriesName;
         }
         }
 
 
+        if (reader.TryGetString(index++, out var userDataKey))
+        {
+        }
+
         if (reader.TryGetString(index++, out var seasonName))
         if (reader.TryGetString(index++, out var seasonName))
         {
         {
             entity.SeasonName = seasonName;
             entity.SeasonName = seasonName;
@@ -971,7 +991,7 @@ public class MigrateLibraryDb : IMigrationRoutine
             entity.OwnerId = ownerId.ToString("N");
             entity.OwnerId = ownerId.ToString("N");
         }
         }
 
 
-        return entity;
+        return (entity, userDataKey);
     }
     }
 
 
     private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)
     private static BaseItemImageInfo Map(Guid baseItemId, ItemImageInfo e)