瀏覽代碼

improved performance of save operations (#13889)

JPVenson 1 月之前
父節點
當前提交
5d65cfcd99

+ 91 - 52
Jellyfin.Server.Implementations/Item/BaseItemRepository.cs

@@ -453,11 +453,9 @@ public sealed class BaseItemRepository
 
         var images = item.ImageInfos.Select(e => Map(item.Id, e));
         using var context = _dbProvider.CreateDbContext();
-        using var transaction = context.Database.BeginTransaction();
         context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
         context.BaseItemImageInfos.AddRange(images);
         context.SaveChanges();
-        transaction.Commit();
     }
 
     /// <inheritdoc  />
@@ -487,17 +485,19 @@ public sealed class BaseItemRepository
             tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
         }
 
-        var localItemValueCache = new Dictionary<(int MagicNumber, string Value), Guid>();
-
         using var context = _dbProvider.CreateDbContext();
         using var transaction = context.Database.BeginTransaction();
+
+        var ids = tuples.Select(f => f.Item.Id).ToArray();
+        var existingItems = context.BaseItems.Where(e => ids.Contains(e.Id)).Select(f => f.Id).ToArray();
+
         foreach (var item in tuples)
         {
             var entity = Map(item.Item);
             // TODO: refactor this "inconsistency"
             entity.TopParentId = item.TopParent?.Id;
 
-            if (!context.BaseItems.Any(e => e.Id == entity.Id))
+            if (!existingItems.Any(e => e == entity.Id))
             {
                 context.BaseItems.Add(entity);
             }
@@ -506,59 +506,98 @@ public sealed class BaseItemRepository
                 context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
                 context.BaseItems.Attach(entity).State = EntityState.Modified;
             }
+        }
 
-            context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
-            if (item.Item.SupportsAncestors && item.AncestorIds != null)
+        context.SaveChanges();
+
+        var itemValueMaps = tuples
+            .Select(e => (Item: e.Item, Values: GetItemValuesToSave(e.Item, e.InheritedTags)))
+            .ToArray();
+        var allListedItemValues = itemValueMaps
+            .SelectMany(f => f.Values)
+            .Distinct()
+            .ToArray();
+        var existingValues = context.ItemValues
+            .Select(e => new
             {
-                foreach (var ancestorId in item.AncestorIds)
-                {
-                    if (!context.BaseItems.Any(f => f.Id == ancestorId))
-                    {
-                        continue;
-                    }
+                item = e,
+                Key = e.Type + "+" + e.Value
+            })
+            .Where(f => allListedItemValues.Select(e => $"{(int)e.MagicNumber}+{e.Value}").Contains(f.Key))
+            .Select(e => e.item)
+            .ToArray();
+        var missingItemValues = allListedItemValues.Except(existingValues.Select(f => (MagicNumber: f.Type, f.Value))).Select(f => new ItemValue()
+        {
+            CleanValue = GetCleanValue(f.Value),
+            ItemValueId = Guid.NewGuid(),
+            Type = f.MagicNumber,
+            Value = f.Value
+        }).ToArray();
+        context.ItemValues.AddRange(missingItemValues);
+        context.SaveChanges();
+
+        var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
+        var valueMap = itemValueMaps
+            .Select(f => (Item: f.Item, Values: f.Values.Select(e => itemValuesStore.First(g => g.Value == e.Value && g.Type == e.MagicNumber)).ToArray()))
+            .ToArray();
+
+        var mappedValues = context.ItemValuesMap.Where(e => ids.Contains(e.ItemId)).ToList();
 
-                    context.AncestorIds.Add(new AncestorId()
+        foreach (var item in valueMap)
+        {
+            var itemMappedValues = mappedValues.Where(e => e.ItemId == item.Item.Id).ToList();
+            foreach (var itemValue in item.Values)
+            {
+                var existingItem = itemMappedValues.FirstOrDefault(f => f.ItemValueId == itemValue.ItemValueId);
+                if (existingItem is null)
+                {
+                    context.ItemValuesMap.Add(new ItemValueMap()
                     {
-                        ParentItemId = ancestorId,
-                        ItemId = entity.Id,
                         Item = null!,
-                        ParentItem = null!
+                        ItemId = item.Item.Id,
+                        ItemValue = null!,
+                        ItemValueId = itemValue.ItemValueId
                     });
                 }
-            }
-
-            // Never save duplicate itemValues as they are now mapped anyway.
-            var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags).DistinctBy(e => (GetCleanValue(e.Value), e.MagicNumber));
-            context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete();
-            foreach (var itemValue in itemValuesToSave)
-            {
-                if (!localItemValueCache.TryGetValue(itemValue, out var refValue))
+                else
                 {
-                    refValue = context.ItemValues
-                                .Where(f => f.Value == itemValue.Value && (int)f.Type == itemValue.MagicNumber)
-                                .Select(e => e.ItemValueId)
-                                .FirstOrDefault();
+                    // map exists, remove from list so its been handled.
+                    itemMappedValues.Remove(existingItem);
                 }
+            }
 
-                if (refValue.IsEmpty())
+            // all still listed values are not in the new list so remove them.
+            context.ItemValuesMap.RemoveRange(itemMappedValues);
+        }
+
+        context.SaveChanges();
+
+        foreach (var item in tuples)
+        {
+            if (item.Item.SupportsAncestors && item.AncestorIds != null)
+            {
+                var existingAncestorIds = context.AncestorIds.Where(e => e.ItemId == item.Item.Id).ToList();
+                var validAncestorIds = context.BaseItems.Where(e => item.AncestorIds.Contains(e.Id)).Select(f => f.Id).ToArray();
+                foreach (var ancestorId in validAncestorIds)
                 {
-                    context.ItemValues.Add(new ItemValue()
+                    var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId);
+                    if (existingAncestorId is null)
                     {
-                        CleanValue = GetCleanValue(itemValue.Value),
-                        Type = (ItemValueType)itemValue.MagicNumber,
-                        ItemValueId = refValue = Guid.NewGuid(),
-                        Value = itemValue.Value
-                    });
-                    localItemValueCache[itemValue] = refValue;
+                        context.AncestorIds.Add(new AncestorId()
+                        {
+                            ParentItemId = ancestorId,
+                            ItemId = item.Item.Id,
+                            Item = null!,
+                            ParentItem = null!
+                        });
+                    }
+                    else
+                    {
+                        existingAncestorIds.Remove(existingAncestorId);
+                    }
                 }
 
-                context.ItemValuesMap.Add(new ItemValueMap()
-                {
-                    Item = null!,
-                    ItemId = entity.Id,
-                    ItemValue = null!,
-                    ItemValueId = refValue
-                });
+                context.AncestorIds.RemoveRange(existingAncestorIds);
             }
         }
 
@@ -1102,27 +1141,27 @@ public sealed class BaseItemRepository
         return value.RemoveDiacritics().ToLowerInvariant();
     }
 
-    private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inheritedTags)
+    private List<(ItemValueType MagicNumber, string Value)> GetItemValuesToSave(BaseItemDto item, List<string> inheritedTags)
     {
-        var list = new List<(int, string)>();
+        var list = new List<(ItemValueType, string)>();
 
         if (item is IHasArtist hasArtist)
         {
-            list.AddRange(hasArtist.Artists.Select(i => (0, i)));
+            list.AddRange(hasArtist.Artists.Select(i => ((ItemValueType)0, i)));
         }
 
         if (item is IHasAlbumArtist hasAlbumArtist)
         {
-            list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (1, i)));
+            list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => (ItemValueType.AlbumArtist, i)));
         }
 
-        list.AddRange(item.Genres.Select(i => (2, i)));
-        list.AddRange(item.Studios.Select(i => (3, i)));
-        list.AddRange(item.Tags.Select(i => (4, i)));
+        list.AddRange(item.Genres.Select(i => (ItemValueType.Genre, i)));
+        list.AddRange(item.Studios.Select(i => (ItemValueType.Studios, i)));
+        list.AddRange(item.Tags.Select(i => (ItemValueType.Tags, i)));
 
         // keywords was 5
 
-        list.AddRange(inheritedTags.Select(i => (6, i)));
+        list.AddRange(inheritedTags.Select(i => (ItemValueType.InheritedTags, i)));
 
         // Remove all invalid values.
         list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));

+ 29 - 22
Jellyfin.Server.Implementations/Item/PeopleRepository.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using Jellyfin.Data.Enums;
 using Jellyfin.Database.Implementations;
 using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Entities.Libraries;
 using Jellyfin.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Persistence;
@@ -68,34 +69,41 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
     public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
     {
         using var context = _dbProvider.CreateDbContext();
-        using var transaction = context.Database.BeginTransaction();
 
-        context.PeopleBaseItemMap.Where(e => e.ItemId == itemId).ExecuteDelete();
         // TODO: yes for __SOME__ reason there can be duplicates.
-        foreach (var item in people.DistinctBy(e => e.Id))
+        people = people.DistinctBy(e => e.Id).ToArray();
+        var personids = people.Select(f => f.Id);
+        var existingPersons = context.Peoples.Where(p => personids.Contains(p.Id)).Select(f => f.Id).ToArray();
+        context.Peoples.AddRange(people.Where(e => !existingPersons.Contains(e.Id)).Select(Map));
+        context.SaveChanges();
+
+        var maps = context.PeopleBaseItemMap.Where(e => e.ItemId == itemId).ToList();
+        foreach (var person in people)
         {
-            var personEntity = Map(item);
-            var existingEntity = context.Peoples.FirstOrDefault(e => e.Id == personEntity.Id);
-            if (existingEntity is null)
+            var existingMap = maps.FirstOrDefault(e => e.PeopleId == person.Id);
+            if (existingMap is null)
             {
-                context.Peoples.Add(personEntity);
-                existingEntity = personEntity;
+                context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
+                {
+                    Item = null!,
+                    ItemId = itemId,
+                    People = null!,
+                    PeopleId = person.Id,
+                    ListOrder = person.SortOrder,
+                    SortOrder = person.SortOrder,
+                    Role = person.Role
+                });
             }
-
-            context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
+            else
             {
-                Item = null!,
-                ItemId = itemId,
-                People = existingEntity,
-                PeopleId = existingEntity.Id,
-                ListOrder = item.SortOrder,
-                SortOrder = item.SortOrder,
-                Role = item.Role
-            });
+                // person mapping already exists so remove from list
+                maps.Remove(existingMap);
+            }
         }
 
+        context.PeopleBaseItemMap.RemoveRange(maps);
+
         context.SaveChanges();
-        transaction.Commit();
     }
 
     private PersonInfo Map(People people)
@@ -133,9 +141,8 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
         if (filter.User is not null && filter.IsFavorite.HasValue)
         {
             var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
-            query = query.Where(e => e.PersonType == personType)
-                .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));
+            query = query
+                .Where(e => context.BaseItems.Any(b => b.Type == personType && b.Name == e.Name && b.UserData!.Any(u => u.IsFavorite == filter.IsFavorite && u.UserId.Equals(filter.User.Id))));
         }
 
         if (!filter.ItemId.IsEmpty())

+ 2 - 3
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -128,8 +128,7 @@ namespace MediaBrowser.Providers.Manager
 
             var metadataResult = new MetadataResult<TItemType>
             {
-                Item = itemOfType,
-                People = LibraryManager.GetPeople(item)
+                Item = itemOfType
             };
 
             var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
@@ -253,7 +252,7 @@ namespace MediaBrowser.Providers.Manager
 
         protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
         {
-            if (result.Item.SupportsPeople)
+            if (result.Item.SupportsPeople && result.People is not null)
             {
                 var baseItem = result.Item;