refactor: project PersonPreviewDto directly in repository instead of [NotMapped] entity property

Replace the TempData-like TotalVisibleAlbums [NotMapped] property on
Person with a direct DTO projection in PersonRepository.SearchQuery.
The repository now returns IEnumerable<PersonPreviewDto>, computing
the visibility-aware album count in the EF Core subquery and projecting
only the needed columns. This avoids polluting the entity model with a
context-dependent property.
This commit is contained in:
2026-07-12 17:18:17 +02:00
parent e333d0e9db
commit c77096b0f9
5 changed files with 19 additions and 25 deletions

View File

@@ -84,8 +84,7 @@ public class PersonController(
pagedSearch.SortAsc,
uid ?? default,
accessLevel
).Select(p => p.ToPersonPreviewDto())
.ToList();
).ToList();
return Ok(people);
}

View File

@@ -41,7 +41,7 @@ public static class PersonMapper {
ProfileCropX = person.ProfileCropX,
ProfileCropY = person.ProfileCropY,
ProfileCropZoom = person.ProfileCropZoom,
TotalAlbums = person.TotalVisibleAlbums
TotalAlbums = person.Albums?.Count ?? 0
};
private static AlbumPreviewDto ToAlbumPreviewDto(Album album) => new() {

View File

@@ -50,13 +50,6 @@ public class Person {
/// </summary>
public float? ProfileCropZoom { get; set; }
/// <summary>
/// Gets or sets the number of albums visible to the current user.
/// Populated by <see cref="Repositories.PersonRepository.SearchQuery"/> — do not rely on this outside that context.
/// </summary>
[NotMapped]
public int TotalVisibleAlbums { get; set; }
#region Navigation Properties
/// <summary>

View File

@@ -1,3 +1,4 @@
using Butter.Dtos.Person;
using Lactose.Models;
namespace Lactose.Repositories;
@@ -64,8 +65,8 @@ public interface IPersonRepository : IDisposable {
/// <param name="sortAsc">Whether to sort ascending. Default is <c>true</c>.</param>
/// <param name="userId">The current user's ID for access-level filtering.</param>
/// <param name="accessLevel">The current user's access level.</param>
/// <returns>A paginated list of people matching the query.</returns>
IEnumerable<Person> SearchQuery(string query, int page = 0, int pageSize = 30, string? sortBy = null, bool sortAsc = true, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User);
/// <returns>A paginated list of person previews matching the query.</returns>
IEnumerable<PersonPreviewDto> SearchQuery(string query, int page = 0, int pageSize = 30, string? sortBy = null, bool sortAsc = true, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User);
/// <summary>
/// Finds multiple people by their IDs.

View File

@@ -1,3 +1,4 @@
using Butter.Dtos.Person;
using Butter.Types;
using Lactose.Context;
using Lactose.Models;
@@ -58,7 +59,7 @@ public class PersonRepository(LactoseDbContext context) : IPersonRepository {
public void Remove(Person person) => context.People.Remove(person);
/// <inheritdoc />
public IEnumerable<Person> SearchQuery(string query, int page = 0, int pageSize = 30, string? sortBy = null, bool sortAsc = true, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User) {
public IEnumerable<PersonPreviewDto> SearchQuery(string query, int page = 0, int pageSize = 30, string? sortBy = null, bool sortAsc = true, Guid userId = default, EAccessLevel accessLevel = EAccessLevel.User) {
IQueryable<Person> peopleQuery = context.People;
if (!string.IsNullOrEmpty(query))
@@ -109,11 +110,16 @@ public class PersonRepository(LactoseDbContext context) : IPersonRepository {
return [];
// Then load the scalar data + visible album count via subquery
var peopleWithCounts = context.People
var dtos = context.People
.Where(p => personIds.Contains(p.Id))
.Select(p => new {
Person = p,
Count = accessLevel == EAccessLevel.User
.Select(p => new PersonPreviewDto {
Id = p.Id,
Name = p.Name,
ProfileAssetId = p.ProfileAssetId,
ProfileCropX = p.ProfileCropX,
ProfileCropY = p.ProfileCropY,
ProfileCropZoom = p.ProfileCropZoom,
TotalAlbums = accessLevel == EAccessLevel.User
? p.Albums!.Count(a =>
a.Assets != null && a.Assets.Any(asset => asset.DeletedAt == null && (
asset.IsPubliclyShared ||
@@ -124,14 +130,9 @@ public class PersonRepository(LactoseDbContext context) : IPersonRepository {
})
.ToList();
// Reorder to match the paginated order and set TotalVisibleAlbums
return personIds
.Select(id => peopleWithCounts.First(x => x.Person.Id == id))
.Select(x => {
x.Person.TotalVisibleAlbums = x.Count;
return x.Person;
})
.ToList();
// Reorder to match the paginated order
var orderMap = personIds.Select((id, i) => (id, i)).ToDictionary(x => x.id, x => x.i);
return [.. dtos.OrderBy(d => orderMap.GetValueOrDefault(d.Id))];
}
/// <inheritdoc />