150 lines
6.0 KiB
C#
150 lines
6.0 KiB
C#
using JSMR.Application.Circles.Queries.Search;
|
|
using JSMR.Infrastructure.Common.Queries;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Linq.Expressions;
|
|
|
|
namespace JSMR.Infrastructure.Data.Repositories.Circles;
|
|
|
|
public class CircleSearchProvider(AppDbContext context) : SearchProvider<CircleSearchItem, CircleSearchCriteria, CircleSortField, CircleSearchItem>, ICircleSearchProvider
|
|
{
|
|
protected override IQueryable<CircleSearchItem> GetBaseQuery()
|
|
{
|
|
// Project from Circles so we can use correlated subqueries per CircleId.
|
|
var q =
|
|
from c in context.Circles.AsNoTracking()
|
|
select new CircleSearchItem
|
|
{
|
|
CircleId = c.CircleId,
|
|
Name = c.Name,
|
|
MakerId = c.MakerId,
|
|
Favorite = c.Favorite,
|
|
Blacklisted = c.Blacklisted,
|
|
Spam = c.Spam,
|
|
|
|
// Aggregates
|
|
Downloads = context.VoiceWorks
|
|
.Where(v => v.CircleId == c.CircleId)
|
|
.Select(v => (int?)v.Downloads) // make nullable for Sum over empty set
|
|
.Sum() ?? 0,
|
|
|
|
Releases = context.VoiceWorks
|
|
.Count(v => v.CircleId == c.CircleId && v.SalesDate != null),
|
|
|
|
Pending = context.VoiceWorks
|
|
.Count(v => v.CircleId == c.CircleId && v.ExpectedDate != null),
|
|
|
|
FirstReleaseDate = context.VoiceWorks
|
|
.Where(v => v.CircleId == c.CircleId)
|
|
.Select(v => v.SalesDate)
|
|
.Min(),
|
|
|
|
LatestReleaseDate = context.VoiceWorks
|
|
.Where(v => v.CircleId == c.CircleId)
|
|
.Select(v => v.SalesDate)
|
|
.Max(),
|
|
|
|
// "Latest" by ProductId length, then value
|
|
LatestProductId = context.VoiceWorks
|
|
.Where(v => v.CircleId == c.CircleId)
|
|
.OrderByDescending(v => v.ProductId.Length)
|
|
.ThenByDescending(v => v.ProductId)
|
|
.Select(v => v.ProductId)
|
|
.FirstOrDefault(),
|
|
|
|
// If you want these two in base query too:
|
|
LatestVoiceWorkHasImage = context.VoiceWorks
|
|
.Where(v => v.CircleId == c.CircleId)
|
|
.OrderByDescending(v => v.ProductId.Length)
|
|
.ThenByDescending(v => v.ProductId)
|
|
.Select(v => (bool?)v.HasImage)
|
|
.FirstOrDefault(),
|
|
|
|
LatestVoiceWorkSalesDate = context.VoiceWorks
|
|
.Where(v => v.CircleId == c.CircleId)
|
|
.OrderByDescending(v => v.ProductId.Length)
|
|
.ThenByDescending(v => v.ProductId)
|
|
.Select(v => v.SalesDate)
|
|
.FirstOrDefault()
|
|
};
|
|
|
|
return q;
|
|
}
|
|
|
|
protected override IQueryable<CircleSearchItem> ApplyFilters(IQueryable<CircleSearchItem> query, CircleSearchCriteria criteria)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(criteria.Name))
|
|
{
|
|
var term = $"%{criteria.Name.Trim()}%";
|
|
|
|
query = query.Where(x =>
|
|
EF.Functions.Like(x.Name, term) ||
|
|
EF.Functions.Like(x.MakerId, term));
|
|
}
|
|
|
|
switch (criteria.Status)
|
|
{
|
|
case CircleStatus.NotBlacklisted:
|
|
query = query.Where(x => !x.Blacklisted);
|
|
break;
|
|
case CircleStatus.Favorited:
|
|
query = query.Where(x => x.Favorite);
|
|
break;
|
|
case CircleStatus.Blacklisted:
|
|
query = query.Where(x => x.Blacklisted);
|
|
break;
|
|
case CircleStatus.Spam:
|
|
query = query.Where(x => x.Spam);
|
|
break;
|
|
}
|
|
|
|
return query;
|
|
}
|
|
|
|
protected override Expression<Func<CircleSearchItem, object>> GetSortExpression(CircleSortField field) => field switch
|
|
{
|
|
//CircleSortField.MakerId => x => x.MakerId,
|
|
//CircleSortField.Downloads => x => x.Downloads,
|
|
//CircleSortField.Releases => x => x.Releases,
|
|
//CircleSortField.Pending => x => x.Pending,
|
|
//CircleSortField.FirstReleaseDate => x => x.FirstReleaseDate ?? DateTime.MinValue,
|
|
//CircleSortField.LatestReleaseDate => x => x.LatestReleaseDate ?? DateTime.MinValue,
|
|
CircleSortField.Favorite => x => x.Favorite,
|
|
CircleSortField.Blacklisted => x => x.Blacklisted,
|
|
CircleSortField.Spam => x => x.Spam,
|
|
_ => x => x.Name
|
|
};
|
|
|
|
protected override IOrderedQueryable<CircleSearchItem> GetDefaultSortExpression(IQueryable<CircleSearchItem> query)
|
|
=> query.OrderBy(x => x.Name).ThenBy(x => x.CircleId);
|
|
|
|
protected override IOrderedQueryable<CircleSearchItem> GetSelectQuery(IOrderedQueryable<CircleSearchItem> query)
|
|
{
|
|
// Join to VoiceWorks by LatestProductId to fill HasImage / SalesDate
|
|
var selected =
|
|
from item in query
|
|
join vw in context.VoiceWorks.AsNoTracking() on item.LatestProductId equals vw.ProductId into vws
|
|
from latest in vws.DefaultIfEmpty()
|
|
select new CircleSearchItem
|
|
{
|
|
CircleId = item.CircleId,
|
|
Name = item.Name,
|
|
MakerId = item.MakerId,
|
|
Favorite = item.Favorite,
|
|
Blacklisted = item.Blacklisted,
|
|
Spam = item.Spam,
|
|
Downloads = item.Downloads,
|
|
Releases = item.Releases,
|
|
Pending = item.Pending,
|
|
FirstReleaseDate = item.FirstReleaseDate,
|
|
LatestReleaseDate = item.LatestReleaseDate,
|
|
LatestProductId = item.LatestProductId,
|
|
LatestVoiceWorkHasImage = latest != null ? latest.HasImage : (bool?)null,
|
|
LatestVoiceWorkSalesDate = latest != null ? latest.SalesDate : null
|
|
};
|
|
|
|
// Preserve existing ordering; add stable tiebreaker
|
|
return selected.OrderBy(x => 0).ThenBy(x => x.Name).ThenBy(x => x.CircleId);
|
|
// NOTE: If your base class re-applies ordering after Select, you can just:
|
|
// return selected.OrderBy(x => x.Name).ThenBy(x => x.CircleId);
|
|
}
|
|
} |