113 lines
4.7 KiB
C#
113 lines
4.7 KiB
C#
using JSMR.Application.Common.Search;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Linq.Expressions;
|
|
|
|
namespace JSMR.Infrastructure.Common.Queries;
|
|
|
|
public abstract class SearchProvider<TItem, TCriteria, TSortField, TBaseQuery> : ISearchProvider<TItem, TCriteria, TSortField>
|
|
where TCriteria : new()
|
|
where TSortField : struct, Enum
|
|
{
|
|
protected abstract bool UseSelectIdQuery { get; }
|
|
|
|
public async Task<SearchResult<TItem>> SearchAsync(SearchOptions<TCriteria, TSortField> options, CancellationToken cancellationToken = default)
|
|
{
|
|
IQueryable<TBaseQuery> baseQuery = GetBaseQuery();
|
|
IQueryable<TBaseQuery> filteredQuery = ApplyFilters(baseQuery, options.Criteria);
|
|
|
|
int total = await filteredQuery.CountAsync(cancellationToken);
|
|
|
|
IOrderedQueryable<TBaseQuery> orderedQuery = ApplySorting(filteredQuery, options.SortOptions);
|
|
TItem[] items = await GetItemsAsync(options, orderedQuery, cancellationToken);
|
|
|
|
await PostLoadAsync(items, cancellationToken);
|
|
|
|
return new SearchResult<TItem>()
|
|
{
|
|
Items = items,
|
|
TotalItems = total
|
|
};
|
|
}
|
|
|
|
protected abstract IQueryable<TBaseQuery> GetBaseQuery();
|
|
protected abstract IQueryable<TBaseQuery> ApplyFilters(IQueryable<TBaseQuery> query, TCriteria criteria);
|
|
|
|
private IOrderedQueryable<TBaseQuery> ApplySorting(IQueryable<TBaseQuery> query, SortOption<TSortField>[] sortOptions)
|
|
{
|
|
IOrderedQueryable<TBaseQuery>? ordered = null;
|
|
|
|
for (int i = 0; i < sortOptions.Length; i++)
|
|
{
|
|
var (field, direction) = (sortOptions[i].Field, sortOptions[i].Direction);
|
|
bool isDescending = direction == SortDirection.Descending;
|
|
|
|
IOrderedQueryable<TBaseQuery> applyFirst(Expression<Func<TBaseQuery, object?>> selector) => isDescending ? query.OrderByDescending(selector) : query.OrderBy(selector);
|
|
IOrderedQueryable<TBaseQuery> applyNext(Expression<Func<TBaseQuery, object?>> selector) => isDescending ? ordered!.ThenByDescending(selector) : ordered!.ThenBy(selector);
|
|
|
|
Expression<Func<TBaseQuery, object?>> selector = GetSortExpression(field);
|
|
|
|
ordered = (i == 0) ? applyFirst(selector) : applyNext(selector);
|
|
}
|
|
|
|
//return ordered ?? GetDefaultSortExpression(query);
|
|
|
|
// Always add the default as the final tiebreaker
|
|
var chain = GetDefaultSortChain();
|
|
|
|
if (ordered is null)
|
|
{
|
|
using var e = chain.GetEnumerator();
|
|
if (!e.MoveNext()) throw new InvalidOperationException("No default sort provided.");
|
|
|
|
var (Selector, Dir) = e.Current;
|
|
var res = Dir == SortDirection.Descending
|
|
? query.OrderByDescending(Selector)
|
|
: query.OrderBy(Selector);
|
|
|
|
while (e.MoveNext())
|
|
res = e.Current.Dir == SortDirection.Descending
|
|
? res.ThenByDescending(e.Current.Selector)
|
|
: res.ThenBy(e.Current.Selector);
|
|
|
|
return res;
|
|
}
|
|
else
|
|
{
|
|
var res = ordered;
|
|
foreach (var (sel, dir) in chain)
|
|
res = dir == SortDirection.Descending ? res.ThenByDescending(sel) : res.ThenBy(sel);
|
|
return res;
|
|
}
|
|
}
|
|
|
|
private async Task<TItem[]> GetItemsAsync(SearchOptions<TCriteria, TSortField> options, IOrderedQueryable<TBaseQuery> orderedQuery, CancellationToken cancellationToken)
|
|
{
|
|
if (UseSelectIdQuery)
|
|
{
|
|
int[] ids = await GetSelectIdQuery(orderedQuery)
|
|
.Skip((options.PageNumber - 1) * options.PageSize)
|
|
.Take(options.PageSize)
|
|
.ToArrayAsync(cancellationToken);
|
|
|
|
Dictionary<int, TItem> items = await GetItems(ids);
|
|
|
|
return [.. ids.Select(uniqueId => items[uniqueId])];
|
|
}
|
|
else
|
|
{
|
|
IQueryable<TItem> selectQuery = GetSelectQuery(orderedQuery);
|
|
|
|
return await selectQuery
|
|
.Skip((options.PageNumber - 1) * options.PageSize)
|
|
.Take(options.PageSize)
|
|
.ToArrayAsync(cancellationToken);
|
|
}
|
|
}
|
|
|
|
protected abstract Expression<Func<TBaseQuery, object?>> GetSortExpression(TSortField field);
|
|
protected abstract IEnumerable<(Expression<Func<TBaseQuery, object>> Selector, SortDirection Dir)> GetDefaultSortChain();
|
|
protected abstract IQueryable<int> GetSelectIdQuery(IOrderedQueryable<TBaseQuery> query);
|
|
protected abstract IQueryable<TItem> GetSelectQuery(IOrderedQueryable<TBaseQuery> query);
|
|
protected abstract Task<Dictionary<int, TItem>> GetItems(int[] ids);
|
|
protected virtual Task PostLoadAsync(IList<TItem> items, CancellationToken cancellationToken) => Task.CompletedTask;
|
|
} |