diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index 72c8d7a9d2..9e138dbdaa 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -177,53 +177,72 @@ namespace Emby.Server.Implementations.Library }; } - private UserItemData? GetUserData(User user, Guid itemId, List keys) + /// + public Dictionary GetUserDataBatch(IReadOnlyList items, User user) { - var cacheKey = GetCacheKey(user.InternalId, itemId); + var result = new Dictionary(items.Count); + var itemsNeedingQuery = new List<(BaseItem Item, List Keys)>(); - if (_cache.TryGet(cacheKey, out var data)) + foreach (var item in items) { - return data; - } - - data = GetUserDataInternal(user.Id, itemId, keys); - - if (data is null) - { - return new UserItemData() + var cacheKey = GetCacheKey(user.InternalId, item.Id); + if (_cache.TryGet(cacheKey, out var cachedData)) { - Key = keys[0], - }; - } - - return _cache.GetOrAdd(cacheKey, _ => data); - } - - private UserItemData? GetUserDataInternal(Guid userId, Guid itemId, List keys) - { - if (keys.Count == 0) - { - return null; - } - - using var context = _repository.CreateDbContext(); - var userData = context.UserData.AsNoTracking().Where(e => e.ItemId == itemId && keys.Contains(e.CustomDataKey) && e.UserId.Equals(userId)).ToArray(); - - if (userData.Length > 0) - { - var directDataReference = userData.FirstOrDefault(e => e.CustomDataKey == itemId.ToString("N")); - if (directDataReference is not null) - { - return Map(directDataReference); + result[item.Id] = cachedData; + } + else + { + var userData = item.UserData?.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault(); + if (userData is not null) + { + result[item.Id] = userData; + _cache.AddOrUpdate(cacheKey, userData); + } + else + { + var keys = item.GetUserDataKeys(); + itemsNeedingQuery.Add((item, keys)); + } } - - return Map(userData.First()); } - return new UserItemData + if (itemsNeedingQuery.Count == 0) { - Key = keys.Last()! - }; + return result; + } + + // Build a single query for all missing items + var allItemIds = itemsNeedingQuery.Select(x => x.Item.Id).ToList(); + var allKeys = itemsNeedingQuery.SelectMany(x => x.Keys).Distinct().ToList(); + if (allKeys.Count > 0) + { + using var context = _repository.CreateDbContext(); + var userDataArray = context.UserData + .AsNoTracking() + .Where(e => allItemIds.Contains(e.ItemId) && allKeys.Contains(e.CustomDataKey) && e.UserId.Equals(user.Id)) + .ToArray(); + + var userDataByItem = userDataArray.GroupBy(e => e.ItemId).ToDictionary(g => g.Key, g => g.ToArray()); + foreach (var (item, keys) in itemsNeedingQuery) + { + UserItemData userData; + if (userDataByItem.TryGetValue(item.Id, out var itemUserData) && itemUserData.Length > 0) + { + var directDataReference = itemUserData.FirstOrDefault(e => e.CustomDataKey == item.Id.ToString("N")); + userData = directDataReference is not null ? Map(directDataReference) : Map(itemUserData.First()); + } + else + { + userData = new UserItemData { Key = keys.Count > 0 ? keys[0] : string.Empty }; + } + + result[item.Id] = userData; + var cacheKey = GetCacheKey(user.InternalId, item.Id); + _cache.AddOrUpdate(cacheKey, userData); + } + } + + return result; } /// diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index eb46611dd9..798812bf1f 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -54,6 +54,14 @@ namespace MediaBrowser.Controller.Library /// User data dto. UserItemDataDto? GetUserDataDto(BaseItem item, User user); + /// + /// Gets user data for multiple items in a single batch operation. + /// + /// The items to get user data for. + /// The user. + /// A dictionary mapping item IDs to their user data. + Dictionary GetUserDataBatch(IReadOnlyList items, User user); + /// /// Gets the user data dto. ///