using JSMR.Application.Common.Search; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; namespace JSMR.Infrastructure.Common.Queries; public abstract class SearchProvider : ISearchProvider where TCriteria : new() where TSortField : struct, Enum { public async Task> SearchAsync(SearchOptions options, CancellationToken cancellationToken = default) { IQueryable baseQuery = GetBaseQuery(); IQueryable filteredQuery = ApplyFilters(baseQuery, options.Criteria); int total = await filteredQuery.CountAsync(cancellationToken); IOrderedQueryable orderedQuery = ApplySorting(filteredQuery, options.SortOptions); IQueryable selectQuery = GetSelectQuery(orderedQuery); TItem[] items = await selectQuery .Skip((options.PageNumber - 1) * options.PageSize) .Take(options.PageSize) .ToArrayAsync(cancellationToken); return new SearchResult() { Items = items, TotalItems = total }; } protected abstract IQueryable GetBaseQuery(); protected abstract IQueryable ApplyFilters(IQueryable query, TCriteria criteria); private IOrderedQueryable ApplySorting(IQueryable query, SortOption[] sortOptions) { IOrderedQueryable? 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 applyFirst(Expression> selector) => isDescending ? query.OrderByDescending(selector) : query.OrderBy(selector); IOrderedQueryable applyNext(Expression> selector) => isDescending ? ordered!.ThenByDescending(selector) : ordered!.ThenBy(selector); Expression> 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; } } protected abstract Expression> GetSortExpression(TSortField field); protected abstract IOrderedQueryable GetDefaultSortExpression(IQueryable query); //protected abstract (Expression> Selector, SortDirection Direction) GetDefaultSortExpression(); protected abstract IEnumerable<(Expression> Selector, SortDirection Dir)> GetDefaultSortChain(); protected abstract IQueryable GetSelectQuery(IOrderedQueryable query); }