Updated UI. Fixed circle search performance.
This commit is contained in:
@@ -1,143 +1,206 @@
|
||||
using JSMR.Application.Circles.Queries.Search;
|
||||
using JSMR.Application.Common.Search;
|
||||
using JSMR.Domain.Entities;
|
||||
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
|
||||
public class CircleQuery
|
||||
{
|
||||
protected override IQueryable<CircleSearchItem> GetBaseQuery()
|
||||
public required Circle Circle { get; init; }
|
||||
}
|
||||
|
||||
public class CircleSearchProvider(AppDbContext context) : SearchProvider<CircleSearchItem, CircleSearchCriteria, CircleSortField, CircleQuery>, ICircleSearchProvider
|
||||
{
|
||||
protected override IQueryable<CircleQuery> GetBaseQuery()
|
||||
{
|
||||
// Project from Circles so we can use correlated subqueries per CircleId.
|
||||
var q =
|
||||
from c in context.Circles.AsNoTracking()
|
||||
select new CircleSearchItem
|
||||
select new CircleQuery
|
||||
{
|
||||
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()
|
||||
Circle = c
|
||||
};
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
protected override IQueryable<CircleSearchItem> ApplyFilters(IQueryable<CircleSearchItem> query, CircleSearchCriteria criteria)
|
||||
//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<CircleQuery> ApplyFilters(IQueryable<CircleQuery> 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));
|
||||
EF.Functions.Like(x.Circle.Name, term) ||
|
||||
EF.Functions.Like(x.Circle.MakerId, term));
|
||||
}
|
||||
|
||||
switch (criteria.Status)
|
||||
{
|
||||
case CircleStatus.NotBlacklisted:
|
||||
query = query.Where(x => !x.Blacklisted);
|
||||
query = query.Where(x => !x.Circle.Blacklisted);
|
||||
break;
|
||||
case CircleStatus.Favorited:
|
||||
query = query.Where(x => x.Favorite);
|
||||
query = query.Where(x => x.Circle.Favorite);
|
||||
break;
|
||||
case CircleStatus.Blacklisted:
|
||||
query = query.Where(x => x.Blacklisted);
|
||||
query = query.Where(x => x.Circle.Blacklisted);
|
||||
break;
|
||||
case CircleStatus.Spam:
|
||||
query = query.Where(x => x.Spam);
|
||||
query = query.Where(x => x.Circle.Spam);
|
||||
break;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
protected override Expression<Func<CircleSearchItem, object>> GetSortExpression(CircleSortField field) => field switch
|
||||
protected override Expression<Func<CircleQuery, object>> GetSortExpression(CircleSortField field) => field switch
|
||||
{
|
||||
CircleSortField.Favorite => x => !x.Favorite,
|
||||
CircleSortField.Blacklisted => x => !x.Blacklisted,
|
||||
CircleSortField.Spam => x => !x.Spam,
|
||||
_ => x => x.Name
|
||||
CircleSortField.Favorite => x => !x.Circle.Favorite,
|
||||
CircleSortField.Blacklisted => x => !x.Circle.Blacklisted,
|
||||
CircleSortField.Spam => x => !x.Circle.Spam,
|
||||
_ => x => x.Circle.Name
|
||||
};
|
||||
|
||||
protected override IEnumerable<(Expression<Func<CircleSearchItem, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
||||
protected override IEnumerable<(Expression<Func<CircleQuery, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
|
||||
{
|
||||
yield return (x => x.Name, SortDirection.Ascending);
|
||||
yield return (x => x.MakerId, SortDirection.Ascending);
|
||||
yield return (x => x.Circle.Name, SortDirection.Ascending);
|
||||
yield return (x => x.Circle.MakerId, SortDirection.Ascending);
|
||||
}
|
||||
|
||||
protected override IQueryable<CircleSearchItem> GetSelectQuery(IOrderedQueryable<CircleSearchItem> query)
|
||||
protected override IQueryable<CircleSearchItem> GetSelectQuery(IOrderedQueryable<CircleQuery> 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()
|
||||
//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
|
||||
CircleId = item.Circle.CircleId,
|
||||
Name = item.Circle.Name,
|
||||
MakerId = item.Circle.MakerId,
|
||||
Favorite = item.Circle.Favorite,
|
||||
Blacklisted = item.Circle.Blacklisted,
|
||||
Spam = item.Circle.Spam,
|
||||
// Aggregates
|
||||
Downloads = context.VoiceWorks
|
||||
.Where(v => v.CircleId == item.Circle.CircleId)
|
||||
.Select(v => (int?)v.Downloads) // make nullable for Sum over empty set
|
||||
.Sum() ?? 0,
|
||||
|
||||
Releases = context.VoiceWorks
|
||||
.Count(v => v.CircleId == item.Circle.CircleId && v.SalesDate != null),
|
||||
|
||||
Pending = context.VoiceWorks
|
||||
.Count(v => v.CircleId == item.Circle.CircleId && v.ExpectedDate != null),
|
||||
|
||||
FirstReleaseDate = context.VoiceWorks
|
||||
.Where(v => v.CircleId == item.Circle.CircleId)
|
||||
.Select(v => v.SalesDate)
|
||||
.Min(),
|
||||
|
||||
LatestReleaseDate = context.VoiceWorks
|
||||
.Where(v => v.CircleId == item.Circle.CircleId)
|
||||
.Select(v => v.SalesDate)
|
||||
.Max(),
|
||||
|
||||
// "Latest" by ProductId length, then value
|
||||
LatestProductId = context.VoiceWorks
|
||||
.Where(v => v.CircleId == item.Circle.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 == item.Circle.CircleId)
|
||||
.OrderByDescending(v => v.ProductId.Length)
|
||||
.ThenByDescending(v => v.ProductId)
|
||||
.Select(v => (bool?)v.HasImage)
|
||||
.FirstOrDefault(),
|
||||
|
||||
LatestVoiceWorkSalesDate = context.VoiceWorks
|
||||
.Where(v => v.CircleId == item.Circle.CircleId)
|
||||
.OrderByDescending(v => v.ProductId.Length)
|
||||
.ThenByDescending(v => v.ProductId)
|
||||
.Select(v => v.SalesDate)
|
||||
.FirstOrDefault(),
|
||||
//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
|
||||
};
|
||||
|
||||
return selected;
|
||||
|
||||
Reference in New Issue
Block a user