Refactor PostgreSQL provider: multi-schema & async prep

- Refactor migrations and provider to use multiple PostgreSQL schemas, each matching a legacy SQLite database (activitylog, authentication, displaypreferences, library, users).
- All tables, foreign keys, and indexes are now schema-qualified; Down migration drops tables by schema.
- Provider ensures schemas exist before migrations; entities are mapped to correct schemas in OnModelCreating.
- Add support for max-pool-size, min-pool-size, and multiplexing connection options; update logging accordingly.
- VACUUM ANALYZE now runs per schema during scheduled optimization.
- TruncateAllTablesAsync now truncates tables with schema qualification.
- README updated with schema structure, new options, and multiplexing warnings.
- CacheDecorator now calls async repository methods using .GetAwaiter().GetResult(), with documentation.
- Lays groundwork for full async/await and multiplexing support in the database layer.
This commit is contained in:
2026-02-23 09:38:22 -05:00
parent ede6904433
commit 86883cd5c6
51 changed files with 6719 additions and 187 deletions
@@ -176,12 +176,23 @@ public sealed class BaseItemRepository
/// <inheritdoc />
public IReadOnlyList<Guid> GetItemIdsList(InternalItemsQuery filter)
{
return GetItemIdsListAsync(filter, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc />
public async Task<IReadOnlyList<Guid>> GetItemIdsListAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(filter);
PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext();
return ApplyQueryFilter(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context, filter).Select(e => e.Id).ToArray();
await using var context = _dbProvider.CreateDbContext();
return await ApplyQueryFilter(context.BaseItems.AsNoTracking().Where(e => e.Id != EF.Constant(PlaceholderId)), context, filter)
.Select(e => e.Id)
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
}
/// <inheritdoc />
@@ -252,11 +263,19 @@ public sealed class BaseItemRepository
/// <inheritdoc />
public QueryResult<BaseItemDto> GetItems(InternalItemsQuery filter)
{
return GetItemsAsync(filter, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc />
public async Task<QueryResult<BaseItemDto>> GetItemsAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(filter);
if (!filter.EnableTotalRecordCount || ((filter.Limit ?? 0) == 0 && (filter.StartIndex ?? 0) == 0))
{
var returnList = GetItemList(filter);
var returnList = await GetItemListAsync(filter, cancellationToken).ConfigureAwait(false);
return new QueryResult<BaseItemDto>(
filter.StartIndex,
returnList.Count,
@@ -266,7 +285,7 @@ public sealed class BaseItemRepository
PrepareFilterQuery(filter);
var result = new QueryResult<BaseItemDto>();
using var context = _dbProvider.CreateDbContext();
await using var context = _dbProvider.CreateDbContext();
IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
@@ -275,24 +294,42 @@ public sealed class BaseItemRepository
if (filter.EnableTotalRecordCount)
{
result.TotalRecordCount = dbQuery.Count();
result.TotalRecordCount = await dbQuery
.CountAsync(cancellationToken)
.ConfigureAwait(false);
}
dbQuery = ApplyQueryPaging(dbQuery, filter);
dbQuery = ApplyNavigations(dbQuery, filter);
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!;
var items = await dbQuery
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
result.Items = items
.Where(e => e is not null)
.Select(w => DeserializeBaseItem(w, filter.SkipDeserialization))
.Where(dto => dto is not null)
.ToArray()!;
result.StartIndex = filter.StartIndex ?? 0;
return result;
}
/// <inheritdoc />
public IReadOnlyList<BaseItemDto> GetItemList(InternalItemsQuery filter)
{
return GetItemListAsync(filter, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc />
public async Task<IReadOnlyList<BaseItemDto>> GetItemListAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(filter);
PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext();
await using var context = _dbProvider.CreateDbContext();
IQueryable<BaseItemEntity> dbQuery = PrepareItemQuery(context, filter);
dbQuery = TranslateQuery(dbQuery, context, filter);
@@ -303,14 +340,21 @@ public sealed class BaseItemRepository
var hasRandomSort = filter.OrderBy.Any(e => e.OrderBy == ItemSortBy.Random);
if (hasRandomSort)
{
var orderedIds = dbQuery.Select(e => e.Id).ToList();
var orderedIds = await dbQuery
.Select(e => e.Id)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
if (orderedIds.Count == 0)
{
return Array.Empty<BaseItemDto>();
}
var itemsById = ApplyNavigations(context.BaseItems.Where(e => orderedIds.Contains(e.Id)), filter)
.AsEnumerable()
var items = await ApplyNavigations(context.BaseItems.Where(e => orderedIds.Contains(e.Id)), filter)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
var itemsById = items
.Select(w => DeserializeBaseItem(w, filter.SkipDeserialization))
.Where(dto => dto is not null)
.ToDictionary(i => i!.Id);
@@ -320,7 +364,15 @@ public sealed class BaseItemRepository
dbQuery = ApplyNavigations(dbQuery, filter);
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).Where(dto => dto is not null).ToArray()!;
var results = await dbQuery
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
return results
.Where(e => e is not null)
.Select(w => DeserializeBaseItem(w, filter.SkipDeserialization))
.Where(dto => dto is not null)
.ToArray()!;
}
/// <inheritdoc/>
@@ -496,30 +548,47 @@ public sealed class BaseItemRepository
/// <inheritdoc/>
public int GetCount(InternalItemsQuery filter)
{
ArgumentNullException.ThrowIfNull(filter);
// Hack for right now since we currently don't support filtering out these duplicates within a query
PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
return dbQuery.Count();
return GetCountAsync(filter, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc />
public ItemCounts GetItemCounts(InternalItemsQuery filter)
/// <inheritdoc/>
public async Task<int> GetCountAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(filter);
// Hack for right now since we currently don't support filtering out these duplicates within a query
PrepareFilterQuery(filter);
using var context = _dbProvider.CreateDbContext();
await using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
var counts = dbQuery
return await dbQuery.CountAsync(cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public ItemCounts GetItemCounts(InternalItemsQuery filter)
{
return GetItemCountsAsync(filter, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc />
public async Task<ItemCounts> GetItemCountsAsync(InternalItemsQuery filter, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(filter);
// Hack for right now since we currently don't support filtering out these duplicates within a query
PrepareFilterQuery(filter);
await using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.BaseItems.AsNoTracking(), context, filter);
var counts = await dbQuery
.GroupBy(x => x.Type)
.Select(x => new { x.Key, Count = x.Count() })
.ToArray();
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
var lookup = _itemTypeLookup.BaseItemKindNames;
var result = new ItemCounts();
@@ -36,16 +36,18 @@ public class ChapterRepository : IChapterRepository
}
/// <inheritdoc />
public ChapterInfo? GetChapter(Guid baseItemId, int index)
public async Task<ChapterInfo?> GetChapterAsync(Guid baseItemId, int index, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
var chapter = context.Chapters.AsNoTracking()
await using var context = _dbProvider.CreateDbContext();
var chapter = await context.Chapters.AsNoTracking()
.Select(e => new
{
chapter = e,
baseItemPath = e.Item.Path
})
.FirstOrDefault(e => e.chapter.ItemId.Equals(baseItemId) && e.chapter.ChapterIndex == index);
.FirstOrDefaultAsync(e => e.chapter.ItemId.Equals(baseItemId) && e.chapter.ChapterIndex == index, cancellationToken)
.ConfigureAwait(false);
if (chapter is not null)
{
return Map(chapter.chapter, chapter.baseItemPath!);
@@ -55,36 +57,41 @@ public class ChapterRepository : IChapterRepository
}
/// <inheritdoc />
public IReadOnlyList<ChapterInfo> GetChapters(Guid baseItemId)
public async Task<IReadOnlyList<ChapterInfo>> GetChaptersAsync(Guid baseItemId, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
return context.Chapters.AsNoTracking().Where(e => e.ItemId.Equals(baseItemId))
await using var context = _dbProvider.CreateDbContext();
var chapters = await context.Chapters.AsNoTracking()
.Where(e => e.ItemId.Equals(baseItemId))
.Select(e => new
{
chapter = e,
baseItemPath = e.Item.Path
})
.AsEnumerable()
.Select(e => Map(e.chapter, e.baseItemPath!))
.ToArray();
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
return chapters.Select(e => Map(e.chapter, e.baseItemPath!)).ToArray();
}
/// <inheritdoc />
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
public async Task SaveChaptersAsync(Guid itemId, IReadOnlyList<ChapterInfo> chapters, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
using (var transaction = context.Database.BeginTransaction())
{
context.Chapters.Where(e => e.ItemId.Equals(itemId)).ExecuteDelete();
for (var i = 0; i < chapters.Count; i++)
{
var chapter = chapters[i];
context.Chapters.Add(Map(chapter, i, itemId));
}
await using var context = _dbProvider.CreateDbContext();
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
context.SaveChanges();
transaction.Commit();
await context.Chapters
.Where(e => e.ItemId.Equals(itemId))
.ExecuteDeleteAsync(cancellationToken)
.ConfigureAwait(false);
for (var i = 0; i < chapters.Count; i++)
{
var chapter = chapters[i];
await context.Chapters.AddAsync(Map(chapter, i, itemId), cancellationToken).ConfigureAwait(false);
}
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -48,31 +48,34 @@ public class KeyframeRepository : IKeyframeRepository
}
/// <inheritdoc />
public IReadOnlyList<MediaEncoding.Keyframes.KeyframeData> GetKeyframeData(Guid itemId)
public async Task<IReadOnlyList<MediaEncoding.Keyframes.KeyframeData>> GetKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
await using var context = _dbProvider.CreateDbContext();
return context.KeyframeData.AsNoTracking().Where(e => e.ItemId.Equals(itemId)).Select(e => Map(e)).ToList();
return await context.KeyframeData
.AsNoTracking()
.Where(e => e.ItemId.Equals(itemId))
.Select(e => Map(e))
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
}
/// <inheritdoc />
public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken)
public async Task SaveKeyframeDataAsync(Guid itemId, MediaEncoding.Keyframes.KeyframeData data, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
await using (transaction.ConfigureAwait(false))
{
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
await context.KeyframeData.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
await using var context = _dbProvider.CreateDbContext();
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
await context.KeyframeData.AddAsync(Map(data, itemId), cancellationToken).ConfigureAwait(false);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken)
public async Task DeleteKeyframeDataAsync(Guid itemId, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
await using var context = _dbProvider.CreateDbContext();
await context.KeyframeData.Where(e => e.ItemId.Equals(itemId)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Persistence;
@@ -22,38 +23,53 @@ using Microsoft.EntityFrameworkCore;
public class MediaAttachmentRepository(IDbContextFactory<JellyfinDbContext> dbProvider) : IMediaAttachmentRepository
{
/// <inheritdoc />
public void SaveMediaAttachments(
public async Task SaveMediaAttachmentsAsync(
Guid id,
IReadOnlyList<MediaAttachment> attachments,
CancellationToken cancellationToken)
CancellationToken cancellationToken = default)
{
using var context = dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
await using var context = dbProvider.CreateDbContext();
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
// Users may replace a media with a version that includes attachments to one without them.
// So when saving attachments is triggered by a library scan, we always unconditionally
// clear the old ones, and then add the new ones if given.
context.AttachmentStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
await context.AttachmentStreamInfos
.Where(e => e.ItemId.Equals(id))
.ExecuteDeleteAsync(cancellationToken)
.ConfigureAwait(false);
if (attachments.Any())
{
context.AttachmentStreamInfos.AddRange(attachments.Select(e => Map(e, id)));
await context.AttachmentStreamInfos
.AddRangeAsync(attachments.Select(e => Map(e, id)), cancellationToken)
.ConfigureAwait(false);
}
context.SaveChanges();
transaction.Commit();
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public IReadOnlyList<MediaAttachment> GetMediaAttachments(MediaAttachmentQuery filter)
public async Task<IReadOnlyList<MediaAttachment>> GetMediaAttachmentsAsync(
MediaAttachmentQuery filter,
CancellationToken cancellationToken = default)
{
using var context = dbProvider.CreateDbContext();
var query = context.AttachmentStreamInfos.AsNoTracking().Where(e => e.ItemId.Equals(filter.ItemId));
await using var context = dbProvider.CreateDbContext();
var query = context.AttachmentStreamInfos
.AsNoTracking()
.Where(e => e.ItemId.Equals(filter.ItemId));
if (filter.Index.HasValue)
{
query = query.Where(e => e.Index == filter.Index);
}
return query.AsEnumerable().Select(Map).ToArray();
var attachments = await query
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
return attachments.Select(Map).ToArray();
}
private MediaAttachment Map(AttachmentStreamInfo attachment)
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller;
@@ -40,23 +41,33 @@ public class MediaStreamRepository : IMediaStreamRepository
}
/// <inheritdoc />
public void SaveMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken)
public async Task SaveMediaStreamsAsync(Guid id, IReadOnlyList<MediaStream> streams, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
await using var context = _dbProvider.CreateDbContext();
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
context.MediaStreamInfos.Where(e => e.ItemId.Equals(id)).ExecuteDelete();
context.MediaStreamInfos.AddRange(streams.Select(f => Map(f, id)));
context.SaveChanges();
await context.MediaStreamInfos
.Where(e => e.ItemId.Equals(id))
.ExecuteDeleteAsync(cancellationToken)
.ConfigureAwait(false);
transaction.Commit();
await context.MediaStreamInfos
.AddRangeAsync(streams.Select(f => Map(f, id)), cancellationToken)
.ConfigureAwait(false);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public IReadOnlyList<MediaStream> GetMediaStreams(MediaStreamQuery filter)
public async Task<IReadOnlyList<MediaStream>> GetMediaStreamsAsync(MediaStreamQuery filter, CancellationToken cancellationToken = default)
{
using var context = _dbProvider.CreateDbContext();
return TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter).AsEnumerable().Select(Map).ToArray();
await using var context = _dbProvider.CreateDbContext();
var streams = await TranslateQuery(context.MediaStreamInfos.AsNoTracking(), filter)
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
return streams.Select(Map).ToArray();
}
private string? GetPathToSave(string? path)
@@ -8,6 +8,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations;
using Jellyfin.Database.Implementations.Entities;
@@ -37,7 +39,15 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
/// <inheritdoc/>
public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter)
{
using var context = _dbProvider.CreateDbContext();
return GetPeopleAsync(filter, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc/>
public async Task<IReadOnlyList<PersonInfo>> GetPeopleAsync(InternalPeopleQuery filter, CancellationToken cancellationToken = default)
{
await using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter);
// Include PeopleBaseItemMap
@@ -58,26 +68,49 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
dbQuery = dbQuery.Take(filter.Limit);
}
return dbQuery.AsEnumerable().Select(Map).ToArray();
var results = await dbQuery
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
return results.Select(Map).ToArray();
}
/// <inheritdoc/>
public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter)
{
using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter).Select(e => e.Name).Distinct();
return GetPeopleNamesAsync(filter, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc/>
public async Task<IReadOnlyList<string>> GetPeopleNamesAsync(InternalPeopleQuery filter, CancellationToken cancellationToken = default)
{
await using var context = _dbProvider.CreateDbContext();
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter)
.Select(e => e.Name)
.Distinct();
// dbQuery = dbQuery.OrderBy(e => e.ListOrder);
if (filter.Limit > 0)
{
dbQuery = dbQuery.Take(filter.Limit);
}
return dbQuery.ToArray();
return await dbQuery
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
}
/// <inheritdoc />
public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
{
UpdatePeopleAsync(itemId, people, CancellationToken.None)
.GetAwaiter()
.GetResult();
}
/// <inheritdoc />
public async Task UpdatePeopleAsync(Guid itemId, IReadOnlyList<PersonInfo> people, CancellationToken cancellationToken = default)
{
foreach (var person in people)
{
@@ -89,27 +122,35 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
people = people.DistinctBy(e => e.Name + "-" + e.Type).ToArray();
var personKeys = people.Select(e => e.Name + "-" + e.Type).ToArray();
using var context = _dbProvider.CreateDbContext();
using var transaction = context.Database.BeginTransaction();
var existingPersons = context.Peoples.Select(e => new
await using var context = _dbProvider.CreateDbContext();
await using var transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
var existingPersons = await context.Peoples
.Select(e => new
{
item = e,
SelectionKey = e.Name + "-" + e.PersonType
})
.Where(p => personKeys.Contains(p.SelectionKey))
.Select(f => f.item)
.ToArray();
.ToArrayAsync(cancellationToken)
.ConfigureAwait(false);
var toAdd = people
.Where(e => e.Type is not PersonKind.Artist && e.Type is not PersonKind.AlbumArtist)
.Where(e => !existingPersons.Any(f => f.Name == e.Name && f.PersonType == e.Type.ToString()))
.Select(Map);
context.Peoples.AddRange(toAdd);
context.SaveChanges();
await context.Peoples.AddRangeAsync(toAdd, cancellationToken).ConfigureAwait(false);
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
var personsEntities = toAdd.Concat(existingPersons).ToArray();
var existingMaps = context.PeopleBaseItemMap.Include(e => e.People).Where(e => e.ItemId == itemId).ToList();
var existingMaps = await context.PeopleBaseItemMap
.Include(e => e.People)
.Where(e => e.ItemId == itemId)
.ToListAsync(cancellationToken)
.ConfigureAwait(false);
var listOrder = 0;
@@ -124,7 +165,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
var existingMap = existingMaps.FirstOrDefault(e => e.People.Name == person.Name && e.People.PersonType == person.Type.ToString() && e.Role == person.Role);
if (existingMap is null)
{
context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
await context.PeopleBaseItemMap.AddAsync(new PeopleBaseItemMap()
{
Item = null!,
ItemId = itemId,
@@ -133,7 +174,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
ListOrder = listOrder,
SortOrder = person.SortOrder,
Role = person.Role
});
}, cancellationToken).ConfigureAwait(false);
}
else
{
@@ -149,8 +190,8 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
context.PeopleBaseItemMap.RemoveRange(existingMaps);
context.SaveChanges();
transaction.Commit();
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
}
private PersonInfo Map(People people)