mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-23 15:30:56 -04:00
improved performance of save operations (#13889)
This commit is contained in:
parent
2ea7af777b
commit
5d65cfcd99
@ -453,11 +453,9 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
var images = item.ImageInfos.Select(e => Map(item.Id, e));
|
var images = item.ImageInfos.Select(e => Map(item.Id, e));
|
||||||
using var context = _dbProvider.CreateDbContext();
|
using var context = _dbProvider.CreateDbContext();
|
||||||
using var transaction = context.Database.BeginTransaction();
|
|
||||||
context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
|
context.BaseItemImageInfos.Where(e => e.ItemId == item.Id).ExecuteDelete();
|
||||||
context.BaseItemImageInfos.AddRange(images);
|
context.BaseItemImageInfos.AddRange(images);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
transaction.Commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -487,17 +485,19 @@ public sealed class BaseItemRepository
|
|||||||
tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
|
tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
|
||||||
}
|
}
|
||||||
|
|
||||||
var localItemValueCache = new Dictionary<(int MagicNumber, string Value), Guid>();
|
|
||||||
|
|
||||||
using var context = _dbProvider.CreateDbContext();
|
using var context = _dbProvider.CreateDbContext();
|
||||||
using var transaction = context.Database.BeginTransaction();
|
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)
|
foreach (var item in tuples)
|
||||||
{
|
{
|
||||||
var entity = Map(item.Item);
|
var entity = Map(item.Item);
|
||||||
// TODO: refactor this "inconsistency"
|
// TODO: refactor this "inconsistency"
|
||||||
entity.TopParentId = item.TopParent?.Id;
|
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);
|
context.BaseItems.Add(entity);
|
||||||
}
|
}
|
||||||
@ -506,59 +506,98 @@ public sealed class BaseItemRepository
|
|||||||
context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
context.BaseItemProviders.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
||||||
context.BaseItems.Attach(entity).State = EntityState.Modified;
|
context.BaseItems.Attach(entity).State = EntityState.Modified;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context.AncestorIds.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
context.SaveChanges();
|
||||||
if (item.Item.SupportsAncestors && item.AncestorIds != null)
|
|
||||||
|
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)
|
item = e,
|
||||||
{
|
Key = e.Type + "+" + e.Value
|
||||||
if (!context.BaseItems.Any(f => f.Id == ancestorId))
|
})
|
||||||
{
|
.Where(f => allListedItemValues.Select(e => $"{(int)e.MagicNumber}+{e.Value}").Contains(f.Key))
|
||||||
continue;
|
.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();
|
||||||
|
|
||||||
context.AncestorIds.Add(new AncestorId()
|
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();
|
||||||
|
|
||||||
|
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!,
|
Item = null!,
|
||||||
ParentItem = null!
|
ItemId = item.Item.Id,
|
||||||
|
ItemValue = null!,
|
||||||
|
ItemValueId = itemValue.ItemValueId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// map exists, remove from list so its been handled.
|
||||||
|
itemMappedValues.Remove(existingItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never save duplicate itemValues as they are now mapped anyway.
|
// all still listed values are not in the new list so remove them.
|
||||||
var itemValuesToSave = GetItemValuesToSave(item.Item, item.InheritedTags).DistinctBy(e => (GetCleanValue(e.Value), e.MagicNumber));
|
context.ItemValuesMap.RemoveRange(itemMappedValues);
|
||||||
context.ItemValuesMap.Where(e => e.ItemId == entity.Id).ExecuteDelete();
|
}
|
||||||
foreach (var itemValue in itemValuesToSave)
|
|
||||||
|
context.SaveChanges();
|
||||||
|
|
||||||
|
foreach (var item in tuples)
|
||||||
|
{
|
||||||
|
if (item.Item.SupportsAncestors && item.AncestorIds != null)
|
||||||
{
|
{
|
||||||
if (!localItemValueCache.TryGetValue(itemValue, out var refValue))
|
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)
|
||||||
{
|
{
|
||||||
refValue = context.ItemValues
|
var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId);
|
||||||
.Where(f => f.Value == itemValue.Value && (int)f.Type == itemValue.MagicNumber)
|
if (existingAncestorId is null)
|
||||||
.Select(e => e.ItemValueId)
|
|
||||||
.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refValue.IsEmpty())
|
|
||||||
{
|
|
||||||
context.ItemValues.Add(new ItemValue()
|
|
||||||
{
|
{
|
||||||
CleanValue = GetCleanValue(itemValue.Value),
|
context.AncestorIds.Add(new AncestorId()
|
||||||
Type = (ItemValueType)itemValue.MagicNumber,
|
{
|
||||||
ItemValueId = refValue = Guid.NewGuid(),
|
ParentItemId = ancestorId,
|
||||||
Value = itemValue.Value
|
ItemId = item.Item.Id,
|
||||||
});
|
Item = null!,
|
||||||
localItemValueCache[itemValue] = refValue;
|
ParentItem = null!
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingAncestorIds.Remove(existingAncestorId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ItemValuesMap.Add(new ItemValueMap()
|
context.AncestorIds.RemoveRange(existingAncestorIds);
|
||||||
{
|
|
||||||
Item = null!,
|
|
||||||
ItemId = entity.Id,
|
|
||||||
ItemValue = null!,
|
|
||||||
ItemValueId = refValue
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1102,27 +1141,27 @@ public sealed class BaseItemRepository
|
|||||||
return value.RemoveDiacritics().ToLowerInvariant();
|
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)
|
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)
|
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.Genres.Select(i => (ItemValueType.Genre, i)));
|
||||||
list.AddRange(item.Studios.Select(i => (3, i)));
|
list.AddRange(item.Studios.Select(i => (ItemValueType.Studios, i)));
|
||||||
list.AddRange(item.Tags.Select(i => (4, i)));
|
list.AddRange(item.Tags.Select(i => (ItemValueType.Tags, i)));
|
||||||
|
|
||||||
// keywords was 5
|
// keywords was 5
|
||||||
|
|
||||||
list.AddRange(inheritedTags.Select(i => (6, i)));
|
list.AddRange(inheritedTags.Select(i => (ItemValueType.InheritedTags, i)));
|
||||||
|
|
||||||
// Remove all invalid values.
|
// Remove all invalid values.
|
||||||
list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
|
list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
|
||||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Database.Implementations;
|
using Jellyfin.Database.Implementations;
|
||||||
using Jellyfin.Database.Implementations.Entities;
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
|
using Jellyfin.Database.Implementations.Entities.Libraries;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
@ -68,34 +69,41 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
|||||||
public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
|
public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
|
||||||
{
|
{
|
||||||
using var context = _dbProvider.CreateDbContext();
|
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.
|
// 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 personEntity = Map(item);
|
var existingPersons = context.Peoples.Where(p => personids.Contains(p.Id)).Select(f => f.Id).ToArray();
|
||||||
var existingEntity = context.Peoples.FirstOrDefault(e => e.Id == personEntity.Id);
|
context.Peoples.AddRange(people.Where(e => !existingPersons.Contains(e.Id)).Select(Map));
|
||||||
if (existingEntity is null)
|
context.SaveChanges();
|
||||||
{
|
|
||||||
context.Peoples.Add(personEntity);
|
|
||||||
existingEntity = personEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
|
var maps = context.PeopleBaseItemMap.Where(e => e.ItemId == itemId).ToList();
|
||||||
|
foreach (var person in people)
|
||||||
|
{
|
||||||
|
var existingMap = maps.FirstOrDefault(e => e.PeopleId == person.Id);
|
||||||
|
if (existingMap is null)
|
||||||
{
|
{
|
||||||
Item = null!,
|
context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
|
||||||
ItemId = itemId,
|
{
|
||||||
People = existingEntity,
|
Item = null!,
|
||||||
PeopleId = existingEntity.Id,
|
ItemId = itemId,
|
||||||
ListOrder = item.SortOrder,
|
People = null!,
|
||||||
SortOrder = item.SortOrder,
|
PeopleId = person.Id,
|
||||||
Role = item.Role
|
ListOrder = person.SortOrder,
|
||||||
});
|
SortOrder = person.SortOrder,
|
||||||
|
Role = person.Role
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// person mapping already exists so remove from list
|
||||||
|
maps.Remove(existingMap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.PeopleBaseItemMap.RemoveRange(maps);
|
||||||
|
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
transaction.Commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PersonInfo Map(People people)
|
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)
|
if (filter.User is not null && filter.IsFavorite.HasValue)
|
||||||
{
|
{
|
||||||
var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
|
var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
|
||||||
query = query.Where(e => e.PersonType == personType)
|
query = query
|
||||||
.Where(e => context.BaseItems.Where(d => d.UserData!.Any(w => w.IsFavorite == filter.IsFavorite && w.UserId.Equals(filter.User.Id)))
|
.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))));
|
||||||
.Select(f => f.Name).Contains(e.Name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filter.ItemId.IsEmpty())
|
if (!filter.ItemId.IsEmpty())
|
||||||
|
@ -128,8 +128,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
|
|
||||||
var metadataResult = new MetadataResult<TItemType>
|
var metadataResult = new MetadataResult<TItemType>
|
||||||
{
|
{
|
||||||
Item = itemOfType,
|
Item = itemOfType
|
||||||
People = LibraryManager.GetPeople(item)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
|
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)
|
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;
|
var baseItem = result.Item;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user