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:
@@ -84,8 +84,7 @@ public class PersonController(
|
||||
pagedSearch.SortAsc,
|
||||
uid ?? default,
|
||||
accessLevel
|
||||
).Select(p => p.ToPersonPreviewDto())
|
||||
.ToList();
|
||||
).ToList();
|
||||
return Ok(people);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 />
|
||||
|
||||
Reference in New Issue
Block a user