diff --git a/Lactose/Repositories/AlbumRepository.cs b/Lactose/Repositories/AlbumRepository.cs
index b5aea31..6afa2ef 100644
--- a/Lactose/Repositories/AlbumRepository.cs
+++ b/Lactose/Repositories/AlbumRepository.cs
@@ -28,7 +28,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
}
///
- public IEnumerable SearchQuery(string query, int page = 0, int pageSize = PagedParametersDto.MaxPageSize, string? sortBy = null, bool sortAsc = false, bool unassigned = false, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User, Guid? uploadedBy = null, Guid? personOwnerId = null) {
+ public async Task> SearchQueryAsync(string query, int page, int pageSize, string? sortBy, bool sortAsc, bool unassigned, Guid userId, EAccessLevel accessLevel, Guid? uploadedBy, Guid? personOwnerId, CancellationToken cancellationToken) {
IQueryable albumsQuery = context.Albums.AsNoTracking();
if (!string.IsNullOrEmpty(query))
@@ -63,31 +63,31 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
};
var pageOffset = page * pageSize;
- var albumIds = ordered
+ var albumIds = await ordered
.Skip(pageOffset)
.Take(pageSize)
.Select(a => a.Id)
- .ToList();
+ .ToListAsync(cancellationToken);
if (albumIds.Count == 0)
return [];
- var pagedAlbums = context.Albums
+ var pagedAlbums = await context.Albums
.AsNoTracking()
.Include(a => a.PersonOwner)
.Include(a => a.CoverAsset)
.Where(a => albumIds.Contains(a.Id))
- .ToList();
+ .ToListAsync(cancellationToken);
// Compute visible asset count per album at the DB level
Dictionary assetCounts;
if (accessLevel >= EAccessLevel.Curator) {
- assetCounts = context.Albums
+ assetCounts = await context.Albums
.Where(a => albumIds.Contains(a.Id))
.Select(a => new { a.Id, Count = a.Assets!.Count() })
- .ToDictionary(x => x.Id, x => x.Count);
+ .ToDictionaryAsync(x => x.Id, x => x.Count, cancellationToken);
} else {
- assetCounts = context.Albums
+ assetCounts = await context.Albums
.Where(a => albumIds.Contains(a.Id))
.Select(a => new {
a.Id,
@@ -97,7 +97,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
(asset.Visibility == EVisibility.Private && asset.UploadedBy == userId)
))
})
- .ToDictionary(x => x.Id, x => x.Count);
+ .ToDictionaryAsync(x => x.Id, x => x.Count, cancellationToken);
}
var dtos = pagedAlbums.Select(a => {
@@ -109,9 +109,9 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
var orderMap = albumIds.Select((id, i) => (id, i)).ToDictionary(x => x.id, x => x.i);
return [.. dtos.OrderBy(d => orderMap.GetValueOrDefault(d.Id))];
}
-
+
///
- public void Save() => context.SaveChanges();
+ public Task SaveAsync(CancellationToken cancellationToken) => context.SaveChangesAsync(cancellationToken);
///
public async Task ExecuteInTransactionAsync(Func> operation) {
@@ -122,20 +122,21 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
}
///
- public Album? Find(Guid id) => context.Albums.FirstOrDefault(a => a.Id == id);
+ public Task FindAsync(Guid id, CancellationToken cancellationToken) =>
+ context.Albums.FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
///
- public Album? FindWithAssets(Guid id) => context.Albums
+ public Task FindWithAssetsAsync(Guid id, CancellationToken cancellationToken) => context.Albums
.AsSplitQuery()
.Include(a => a.Assets)
- .FirstOrDefault(a => a.Id == id);
+ .FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
///
- public Album? FindVisible(Guid id, Guid? userId, EAccessLevel accessLevel) {
- var album = context.Albums
+ public async Task FindVisibleAsync(Guid id, Guid? userId, EAccessLevel accessLevel, CancellationToken cancellationToken) {
+ var album = await context.Albums
.AsNoTracking()
.Include(a => a.PersonOwner)
- .FirstOrDefault(a => a.Id == id);
+ .FirstOrDefaultAsync(a => a.Id == id, cancellationToken);
if (album == null) return null;
@@ -143,8 +144,8 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
bool canSeeAlbum = accessLevel switch {
< EAccessLevel.Maintainer => album.Visibility <= EVisibility.Protected,
EAccessLevel.Maintainer when userId.HasValue => album.Visibility <= EVisibility.Protected
- || (album.PersonOwnerId.HasValue && context.PersonMaintainers.Any(pm =>
- pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value)),
+ || (album.PersonOwnerId.HasValue && await context.PersonMaintainers.AnyAsync(pm =>
+ pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value, cancellationToken)),
EAccessLevel.Maintainer => album.Visibility <= EVisibility.Protected,
_ => true
};
@@ -160,7 +161,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
EAccessLevel.Admin => assetsQuery,
EAccessLevel.Curator => assetsQuery.Where(a => a.DeletedAt == null || a.UploadedBy == userId),
EAccessLevel.Maintainer when userId.HasValue && album.PersonOwnerId.HasValue
- && context.PersonMaintainers.Any(pm => pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value)
+ && await context.PersonMaintainers.AnyAsync(pm => pm.UserId == userId.Value && pm.PersonId == album.PersonOwnerId.Value, cancellationToken)
=> assetsQuery.Where(a => a.DeletedAt == null),
_ => assetsQuery.Where(a => a.DeletedAt == null && (
a.Visibility == EVisibility.Public ||
@@ -169,7 +170,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
))
};
- album.VisibleAssetPreviews = assetsQuery
+ album.VisibleAssetPreviews = await assetsQuery
.OrderBy(a => a.OriginalFilename)
.Select(a => new AlbumAssetPreviewDto {
Id = a.Id,
@@ -182,46 +183,48 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
Visibility = accessLevel >= EAccessLevel.Maintainer ? a.Visibility : null,
DeletedAt = accessLevel >= EAccessLevel.Admin || a.UploadedBy == userId ? a.DeletedAt : null
})
- .ToList();
+ .ToListAsync(cancellationToken);
return album;
}
-
- ///
- public IEnumerable FindByPerson(Guid personId) => context.Albums.Where(a => a.PersonOwnerId == personId);
///
- public IEnumerable FindByPersonIds(IEnumerable personIds) =>
- context.Albums.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value));
+ public Task> FindByPersonAsync(Guid personId, CancellationToken cancellationToken) =>
+ context.Albums.Where(a => a.PersonOwnerId == personId).ToListAsync(cancellationToken);
///
- public int BulkSetVisibilityByPersonIds(IEnumerable personIds, EVisibility visibility) =>
+ public Task> FindByPersonIdsAsync(IEnumerable personIds, CancellationToken cancellationToken) =>
+ context.Albums.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value)).ToListAsync(cancellationToken);
+
+ ///
+ public Task BulkSetVisibilityByPersonIdsAsync(IEnumerable personIds, EVisibility visibility, CancellationToken cancellationToken) =>
context.Albums
.Where(a => a.PersonOwnerId != null && personIds.Contains(a.PersonOwnerId.Value))
- .ExecuteUpdate(s => s
+ .ExecuteUpdateAsync(s => s
.SetProperty(a => a.Visibility, visibility)
- .SetProperty(a => a.UpdatedAt, DateTime.UtcNow));
+ .SetProperty(a => a.UpdatedAt, DateTime.UtcNow), cancellationToken);
///
- public IEnumerable FindBulk(IEnumerable ids) => context.Albums.Where(a => ids.Contains(a.Id));
+ public Task> FindBulkAsync(IEnumerable ids, CancellationToken cancellationToken) =>
+ context.Albums.Where(a => ids.Contains(a.Id)).ToListAsync(cancellationToken);
///
- public IEnumerable FindByDateRange(DateTime from, DateTime to)
- => context.Albums.Where(x => x.CreatedAt >= from && x.UpdatedAt <= to);
+ public Task> FindByDateRangeAsync(DateTime from, DateTime to, CancellationToken cancellationToken)
+ => context.Albums.Where(x => x.CreatedAt >= from && x.UpdatedAt <= to).ToListAsync(cancellationToken);
///
- public void MergeAlbums(Guid destId, List sourceIds) {
- var dest = context.Albums
+ public async Task MergeAlbumsAsync(Guid destId, List sourceIds, CancellationToken cancellationToken) {
+ var dest = await context.Albums
.AsSplitQuery()
.Include(a => a.Assets)
- .FirstOrDefault(a => a.Id == destId);
+ .FirstOrDefaultAsync(a => a.Id == destId, cancellationToken);
if (dest == null) return;
- var sources = context.Albums
+ var sources = await context.Albums
.AsSplitQuery()
.Include(a => a.Assets)
.Where(a => sourceIds.Contains(a.Id))
- .ToList();
+ .ToListAsync(cancellationToken);
var existingAssetIds = dest.Assets?.Select(a => a.Id).ToHashSet() ?? [];
@@ -238,7 +241,7 @@ public class AlbumRepository(LactoseDbContext context) : IAlbumRepository {
///
public void Remove(Album album) => context.Albums.Remove(album);
-
+
///
public void Dispose() => context.Dispose();
}
diff --git a/Lactose/Repositories/IAlbumRepository.cs b/Lactose/Repositories/IAlbumRepository.cs
index 769f0da..d538fdb 100644
--- a/Lactose/Repositories/IAlbumRepository.cs
+++ b/Lactose/Repositories/IAlbumRepository.cs
@@ -10,41 +10,44 @@ namespace Lactose.Repositories;
///
public interface IAlbumRepository : IDisposable {
///
- /// Inserts a new album into the database
+ /// Inserts a new album into the database.
///
- ///
- public void Insert(Album album);
+ /// The album to insert.
+ void Insert(Album album);
///
- /// Updates an album in the database
+ /// Updates an album in the database.
///
- ///
- public void Update(Album album);
+ /// The album to update.
+ void Update(Album album);
///
/// Updates multiple albums in the database.
///
/// The albums to update.
- public void UpdateBulk(IEnumerable albums);
+ void UpdateBulk(IEnumerable albums);
///
/// Saves changes to the database.
///
- public void Save();
+ /// Token to cancel the operation.
+ Task SaveAsync(CancellationToken cancellationToken);
///
/// Finds an album by its ID.
///
/// The ID of the album.
+ /// Token to cancel the operation.
/// The album if found; otherwise, null.
- public Album? Find(Guid id);
+ Task FindAsync(Guid id, CancellationToken cancellationToken);
///
/// Finds an album by its ID with its assets loaded for collection replacement.
///
/// The ID of the album.
+ /// Token to cancel the operation.
/// The album with its asset collection loaded if found; otherwise, null.
- public Album? FindWithAssets(Guid id);
+ Task FindWithAssetsAsync(Guid id, CancellationToken cancellationToken);
///
/// Finds an album by its ID with assets filtered by the requesting user's access level.
@@ -52,45 +55,51 @@ public interface IAlbumRepository : IDisposable {
/// The ID of the album.
/// The requesting user's ID for visibility-scoped asset filtering.
/// The requesting user's access level.
+ /// Token to cancel the operation.
/// The album if found; otherwise, null.
- public Album? FindVisible(Guid id, Guid? userId, EAccessLevel accessLevel);
+ Task FindVisibleAsync(Guid id, Guid? userId, EAccessLevel accessLevel, CancellationToken cancellationToken);
///
/// Finds albums by the person's ID.
///
/// The ID of the person.
+ /// Token to cancel the operation.
/// A collection of albums associated with the specified person.
- public IEnumerable FindByPerson(Guid personId);
+ Task> FindByPersonAsync(Guid personId, CancellationToken cancellationToken);
///
/// Finds albums owned by any of the specified people.
///
/// The IDs of the people.
+ /// Token to cancel the operation.
/// A collection of albums associated with the specified people.
- public IEnumerable FindByPersonIds(IEnumerable personIds);
+ Task> FindByPersonIdsAsync(IEnumerable personIds, CancellationToken cancellationToken);
///
/// Bulk-updates the visibility of all albums owned by the specified people via a single SQL UPDATE.
///
/// The IDs of the people.
/// The target visibility level.
+ /// Token to cancel the operation.
/// The number of albums updated.
- public int BulkSetVisibilityByPersonIds(IEnumerable personIds, EVisibility visibility);
+ Task BulkSetVisibilityByPersonIdsAsync(IEnumerable personIds, EVisibility visibility, CancellationToken cancellationToken);
///
/// Finds multiple albums by their IDs.
///
/// The IDs of the albums.
+ /// Token to cancel the operation.
/// A collection of albums with the specified IDs.
- public IEnumerable FindBulk(IEnumerable ids);
+ Task> FindBulkAsync(IEnumerable ids, CancellationToken cancellationToken);
///
/// Finds albums within a specified date range.
///
/// The start date of the range.
/// The end date of the range.
+ /// Token to cancel the operation.
/// A collection of albums created or updated within the specified date range.
- public IEnumerable FindByDateRange(DateTime from, DateTime to);
+ Task> FindByDateRangeAsync(DateTime from, DateTime to, CancellationToken cancellationToken);
///
/// Merges multiple source albums into a destination album.
@@ -98,13 +107,14 @@ public interface IAlbumRepository : IDisposable {
///
/// The ID of the destination album.
/// The IDs of the source albums to merge.
- public void MergeAlbums(Guid destId, List sourceIds);
+ /// Token to cancel the operation.
+ Task MergeAlbumsAsync(Guid destId, List sourceIds, CancellationToken cancellationToken);
///
/// Removes an album from the database.
///
/// The album to remove.
- public void Remove(Album album);
+ void Remove(Album album);
///
/// Searches for albums based on a query string with optional sorting, filtering, and access-level scoping.
@@ -119,8 +129,9 @@ public interface IAlbumRepository : IDisposable {
/// The current user's access level. Regular users only see albums with visible assets.
/// Optional filter for albums containing assets uploaded by a specific user.
/// Optional filter for albums owned by a specific person.
+ /// Token to cancel the operation.
/// A list of album previews matching the search query.
- public IEnumerable SearchQuery(string query, int page = 0, int pageSize = PagedParametersDto.MaxPageSize, string? sortBy = null, bool sortAsc = false, bool unassigned = false, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User, Guid? uploadedBy = null, Guid? personOwnerId = null);
+ Task> SearchQueryAsync(string query, int page, int pageSize, string? sortBy, bool sortAsc, bool unassigned, Guid userId, EAccessLevel accessLevel, Guid? uploadedBy, Guid? personOwnerId, CancellationToken cancellationToken);
///
/// Executes the given operation inside an explicit transaction on the shared database context,
@@ -129,5 +140,5 @@ public interface IAlbumRepository : IDisposable {
/// The type returned by the operation.
/// The work to run inside the transaction.
/// The operation's result, or null if the operation returned null.
- public Task ExecuteInTransactionAsync(Func> operation);
-}
\ No newline at end of file
+ Task ExecuteInTransactionAsync(Func> operation);
+}