using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using API.Data.Misc; using API.Data.Repositories; using API.Entities; using API.Entities.Enums; using API.Entities.Scrobble; using Microsoft.EntityFrameworkCore; namespace API.Extensions.QueryExtensions; public static class QueryableExtensions { public static Task GetUserAgeRestriction(this DbSet queryable, int userId) { if (userId < 1) { return Task.FromResult(new AgeRestriction() { AgeRating = AgeRating.NotApplicable, IncludeUnknowns = true }); } return queryable .AsNoTracking() .Where(u => u.Id == userId) .Select(u => new AgeRestriction(){ AgeRating = u.AgeRestriction, IncludeUnknowns = u.AgeRestrictionIncludeUnknowns }) .SingleAsync(); } /// /// Applies restriction based on if the Library has restrictions (like include in search) /// /// /// /// public static IQueryable IsRestricted(this IQueryable query, QueryContext context) { if (context.HasFlag(QueryContext.None)) return query; if (context.HasFlag(QueryContext.Dashboard)) { query = query.Where(l => l.IncludeInDashboard); } if (context.HasFlag(QueryContext.Recommended)) { query = query.Where(l => l.IncludeInRecommended); } if (context.HasFlag(QueryContext.Search)) { query = query.Where(l => l.IncludeInSearch); } return query; } /// /// Returns all libraries for a given user /// /// /// /// /// public static IQueryable GetUserLibraries(this IQueryable library, int userId, QueryContext queryContext = QueryContext.None) { return library .Include(l => l.AppUsers) .Where(lib => lib.AppUsers.Any(user => user.Id == userId)) .IsRestricted(queryContext) .AsNoTracking() .AsSplitQuery() .Select(lib => lib.Id); } /// /// Returns all libraries for a given user and library type /// /// /// /// /// public static IQueryable GetUserLibrariesByType(this IQueryable library, int userId, LibraryType type, QueryContext queryContext = QueryContext.None) { return library .Include(l => l.AppUsers) .Where(lib => lib.AppUsers.Any(user => user.Id == userId)) .Where(lib => lib.Type == type) .IsRestricted(queryContext) .AsNoTracking() .AsSplitQuery() .Select(lib => lib.Id); } public static IEnumerable Range(this DateTime startDate, int numberOfDays) => Enumerable.Range(0, numberOfDays).Select(e => startDate.AddDays(e)); public static IQueryable WhereIf(this IQueryable queryable, bool condition, Expression> predicate) { return condition ? queryable.Where(predicate) : queryable; } public static IQueryable WhereLike(this IQueryable queryable, bool condition, Expression> propertySelector, string searchQuery) where T : class { if (!condition || string.IsNullOrEmpty(searchQuery)) return queryable; var method = typeof(DbFunctionsExtensions).GetMethod(nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string) }); var dbFunctions = typeof(EF).GetMethod(nameof(EF.Functions))?.Invoke(null, null); var searchExpression = Expression.Constant($"%{searchQuery}%"); var likeExpression = Expression.Call(method, Expression.Constant(dbFunctions), propertySelector.Body, searchExpression); var lambda = Expression.Lambda>(likeExpression, propertySelector.Parameters[0]); return queryable.Where(lambda); } /// /// Performs a WhereLike that ORs multiple fields /// /// /// /// /// /// /// public static IQueryable WhereLike(this IQueryable queryable, bool condition, List>> propertySelectors, string searchQuery) where T : class { if (!condition || string.IsNullOrEmpty(searchQuery)) return queryable; var method = typeof(DbFunctionsExtensions).GetMethod(nameof(DbFunctionsExtensions.Like), new[] { typeof(DbFunctions), typeof(string), typeof(string) }); var dbFunctions = typeof(EF).GetMethod(nameof(EF.Functions))?.Invoke(null, null); var searchExpression = Expression.Constant($"%{searchQuery}%"); Expression orExpression = null; foreach (var propertySelector in propertySelectors) { var likeExpression = Expression.Call(method, Expression.Constant(dbFunctions), propertySelector.Body, searchExpression); var lambda = Expression.Lambda>(likeExpression, propertySelector.Parameters[0]); orExpression = orExpression == null ? lambda.Body : Expression.OrElse(orExpression, lambda.Body); } if (orExpression == null) { throw new ArgumentNullException(nameof(orExpression)); } var combinedLambda = Expression.Lambda>(orExpression, propertySelectors[0].Parameters[0]); return queryable.Where(combinedLambda); } public static IQueryable SortBy(this IQueryable query, ScrobbleEventSortField sort, bool isDesc = false) { if (isDesc) { return sort switch { ScrobbleEventSortField.None => query, ScrobbleEventSortField.Created => query.OrderByDescending(s => s.Created), ScrobbleEventSortField.LastModified => query.OrderByDescending(s => s.LastModified), ScrobbleEventSortField.Type => query.OrderByDescending(s => s.ScrobbleEventType), ScrobbleEventSortField.Series => query.OrderByDescending(s => s.Series.NormalizedName), ScrobbleEventSortField.IsProcessed => query.OrderByDescending(s => s.IsProcessed), _ => query }; } return sort switch { ScrobbleEventSortField.None => query, ScrobbleEventSortField.Created => query.OrderBy(s => s.Created), ScrobbleEventSortField.LastModified => query.OrderBy(s => s.LastModified), ScrobbleEventSortField.Type => query.OrderBy(s => s.ScrobbleEventType), ScrobbleEventSortField.Series => query.OrderBy(s => s.Series.NormalizedName), ScrobbleEventSortField.IsProcessed => query.OrderBy(s => s.IsProcessed), _ => query }; } }