feat(instances): GetInstancesPagedAsync SQL-side filter/order/page

This commit is contained in:
Joseph Doherty
2026-10-22 06:06:27 -04:00
parent 6bcca4b344
commit bc1aca0ee6
3 changed files with 76 additions and 0 deletions
@@ -32,6 +32,18 @@ public interface ICentralUiRepository
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to a read-only list of matching instances.</returns>
Task<IReadOnlyList<Instance>> GetInstancesFilteredAsync(int? siteId = null, int? templateId = null, string? searchTerm = null, CancellationToken cancellationToken = default);
/// <summary>
/// Returns one page of instances with SQL-side filtering, ordered by unique name, plus the total
/// number of rows matching the filter (before paging).
/// </summary>
/// <param name="siteIds">Optional site IDs to restrict to; null means all sites, an empty collection matches nothing.</param>
/// <param name="templateId">Optional template ID to filter by.</param>
/// <param name="search">Optional unique-name prefix (StartsWith).</param>
/// <param name="skip">Rows to skip.</param>
/// <param name="take">Maximum rows to return.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that resolves to the page of rows and the total matching count.</returns>
Task<(IReadOnlyList<Instance> Rows, int Total)> GetInstancesPagedAsync(IReadOnlyCollection<int>? siteIds, int? templateId, string? search, int skip, int take, CancellationToken cancellationToken = default);
/// <summary>Returns the most recent deployment records up to the specified count.</summary>
/// <param name="count">Maximum number of records to return.</param>
/// <param name="cancellationToken">Cancellation token.</param>
@@ -86,6 +86,38 @@ public class CentralUiRepository : ICentralUiRepository
.ToListAsync(cancellationToken);
}
/// <inheritdoc />
public async Task<(IReadOnlyList<Instance> Rows, int Total)> GetInstancesPagedAsync(
IReadOnlyCollection<int>? siteIds,
int? templateId,
string? search,
int skip,
int take,
CancellationToken cancellationToken = default)
{
var query = _context.Instances.AsNoTracking().AsQueryable();
if (siteIds is not null)
{
var ids = siteIds.ToList();
query = query.Where(i => ids.Contains(i.SiteId));
}
if (templateId.HasValue)
query = query.Where(i => i.TemplateId == templateId.Value);
if (!string.IsNullOrWhiteSpace(search))
query = query.Where(i => i.UniqueName.StartsWith(search));
var total = await query.CountAsync(cancellationToken);
var rows = await query
.OrderBy(i => i.UniqueName)
.Skip(Math.Max(0, skip))
.Take(Math.Max(0, take))
.ToListAsync(cancellationToken);
return (rows, total);
}
/// <inheritdoc />
public async Task<IReadOnlyList<DeploymentRecord>> GetRecentDeploymentsAsync(int count, CancellationToken cancellationToken = default)
{
@@ -280,6 +280,38 @@ public class CentralUiRepositoryTests : IDisposable
Assert.Single(instances);
}
/// <summary>Verifies paged instances filter in SQL, order by name, page and report the total.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task GetInstancesPaged_FiltersOrdersAndPages()
{
var site1 = new Site("Site1", "S-001");
var site2 = new Site("Site2", "S-002");
var template = new Template("T1");
_context.Sites.AddRange(site1, site2);
_context.Templates.Add(template);
await _context.SaveChangesAsync();
_context.Instances.AddRange(
new Instance("Pump3") { SiteId = site1.Id, TemplateId = template.Id },
new Instance("Pump1") { SiteId = site1.Id, TemplateId = template.Id },
new Instance("Pump2") { SiteId = site2.Id, TemplateId = template.Id },
new Instance("XPump") { SiteId = site1.Id, TemplateId = template.Id });
await _context.SaveChangesAsync();
var (rows, total) = await _repository.GetInstancesPagedAsync(null, null, "Pump", 1, 2);
Assert.Equal(3, total);
Assert.Equal(new[] { "Pump2", "Pump3" }, rows.Select(r => r.UniqueName));
var (scoped, scopedTotal) = await _repository.GetInstancesPagedAsync(new[] { site1.Id }, template.Id, "Pump", 0, 10);
Assert.Equal(2, scopedTotal);
Assert.Equal(new[] { "Pump1", "Pump3" }, scoped.Select(r => r.UniqueName));
var (none, noneTotal) = await _repository.GetInstancesPagedAsync(Array.Empty<int>(), null, null, 0, 10);
Assert.Empty(none);
Assert.Equal(0, noneTotal);
}
/// <summary>Verifies that recent deployments are returned in reverse chronological order.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]