using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using API.DTOs; using API.Entities; using API.Helpers; using API.Interfaces; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace API.Data { public class SeriesRepository : ISeriesRepository { private readonly DataContext _context; private readonly IMapper _mapper; private readonly ILogger _logger; public SeriesRepository(DataContext context, IMapper mapper, ILogger logger) { _context = context; _mapper = mapper; _logger = logger; } public void Add(Series series) { _context.Series.Add(series); } public void Update(Series series) { _context.Entry(series).State = EntityState.Modified; } public async Task SaveAllAsync() { return await _context.SaveChangesAsync() > 0; } public bool SaveAll() { return _context.SaveChanges() > 0; } public async Task GetSeriesByNameAsync(string name) { return await _context.Series.SingleOrDefaultAsync(x => x.Name == name); } public Series GetSeriesByName(string name) { return _context.Series.SingleOrDefault(x => x.Name == name); } public async Task> GetSeriesForLibraryIdAsync(int libraryId) { return await _context.Series .Where(s => s.LibraryId == libraryId) .OrderBy(s => s.SortName) .ToListAsync(); } public async Task> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId, UserParams userParams) { var sw = Stopwatch.StartNew(); // // // var query = QueryableExtensions.LeftJoin(_context.Series // // .Join(_context.AppUserProgresses, series => series.Id, progress => progress.SeriesId, // // (series, progress) => // // new { // // series.LibraryId, // // series.Id, // // series.Created, // // series.Name, // // series.Pages, // // series.Summary, // // series.CoverImage, // // series.OriginalName, // // series.SortName, // // progress.PagesRead // // }), _context.AppUserRating, series => series.Id, rating => rating.SeriesId, // // (series, rating) => // // new { // // series.LibraryId, // // series.Id, // // series.Created, // // series.Name, // // series.Pages, // // series.Summary, // // series.CoverImage, // // series.OriginalName, // // series.SortName, // // series.PagesRead, // // rating.Review, // // rating.Rating, // // rating.AppUserId // // }) // // .Where(s => s.LibraryId == libraryId && s.AppUserId == userId) // // .OrderBy(s => s.SortName) // // .Select(arg => new SeriesDto() // // { // // PagesRead = arg.PagesRead, // // UserRating = arg.Rating, // // UserReview = arg.Review, // // Summary = arg.Summary, // // Name = arg.Name, // // OriginalName = arg.OriginalName, // // SortName = arg.SortName, // // Id = arg.Id, // // Pages = arg.Pages, // // CoverImage = arg.CoverImage // // }) // // //.ProjectTo(_mapper.ConfigurationProvider) // // .AsNoTracking(); // // // // var query2 = _context.Series // // .Join(_context.AppUserProgresses, series => series.Id, progress => progress.SeriesId, // // (series, progress) => new { series, progress} // // /*new // // { // // series.LibraryId, // // series.Id, // // series.Created, // // series.Name, // // series.Pages, // // series.Summary, // // series.CoverImage, // // series.OriginalName, // // series.SortName, // // progress.PagesRead // // }*/) // // .LeftJoin(_context.AppUserRating, series => series.series.Id, rating => rating.SeriesId, // // (series, rating) => new {series.series, series.progress, rating} // // /* // // * new // // { // // series.LibraryId, // // series.Id, // // series.Name, // // series.Pages, // // series.Summary, // // series.CoverImage, // // series.OriginalName, // // series.SortName, // // series.PagesRead, // // rating.Review, // // rating.Rating, // // rating.AppUserId // // } // // */) // // // .Select(arg => new SeriesDto() // // // { // // // // Where is failing because this select changes what we select // // // PagesRead = arg.PagesRead, // // // UserRating = arg.Rating, // // // UserReview = arg.Review, // // // Summary = arg.Summary, // // // Name = arg.Name, // // // OriginalName = arg.OriginalName, // // // SortName = arg.SortName, // // // Id = arg.Id, // // // Pages = arg.Pages, // // // CoverImage = arg.CoverImage, // // // }) // .Select(arg => new SeriesDto() // { // // Where is failing because this select changes what we select // LibraryId = arg.series.LibraryId, // PagesRead = arg.progress.PagesRead, // UserRating = arg.rating.Rating, // UserReview = arg.rating.Review, // Summary = arg.series.Summary, // Name = arg.series.Name, // OriginalName = arg.series.OriginalName, // SortName = arg.series.SortName, // Id = arg.series.Id, // Pages = arg.series.Pages, // CoverImage = arg.series.CoverImage, // }) // // .Where(s => s.LibraryId == libraryId && s.AppUserId == userId) // // .OrderBy(s => s.SortName) // // .AsNoTracking(); // // var query = _context.Series // .FromSqlRaw(@"select S.*, Rating AS UserRating, Review AS UserReview from Series AS S // INNER JOIN AppUserProgresses AS AP ON AP.SeriesId = S.Id AND AP.AppUserId = {0} // LEFT OUTER JOIN AppUserRating AUR on AUR.SeriesId = S.Id AND AUR.AppUserId = {0} // WHERE (S.LibraryId = {1}) AND (AP.AppUserId = {0}) // ORDER BY S.SortName", userId, libraryId) // .Select(series => new SeriesDto() // { // // Where is failing because this select changes what we select // PagesRead = series.PagesRead, // UserRating = series.UserRating, // UserReview = arg.rating.Review, // Summary = arg.series.Summary, // Name = arg.series.Name, // OriginalName = arg.series.OriginalName, // SortName = arg.series.SortName, // Id = arg.series.Id, // Pages = arg.series.Pages, // CoverImage = arg.series.CoverImage, // }) // .AsNoTracking(); /* * select S.*, Rating AS UserRating, Review AS UserReview from Series AS S INNER JOIN AppUserProgresses AS AP ON AP.SeriesId = S.Id AND AP.AppUserId = 1 LEFT OUTER JOIN AppUserRating AUR on AUR.SeriesId = S.Id AND AUR.AppUserId = 1; */ //await AddSeriesModifiers(userId, series); // var userProgress = await _context.AppUserProgresses // .Where(p => p.AppUserId == userId && series.Select(s => s.Id).Contains(p.SeriesId) ) // .ToListAsync(); // // var userRatings = await _context.AppUserRating // .Where(r => r.AppUserId == userId && series.Select(s => s.Id).Contains(r.SeriesId)) // .ToListAsync(); // // foreach (var s in series) // { // s.PagesRead = userProgress.Where(p => p.SeriesId == s.Id).Sum(p => p.PagesRead); // var rating = userRatings.SingleOrDefault(r => r.SeriesId == s.Id); // if (rating == null) continue; // s.UserRating = rating.Rating; // s.UserReview = rating.Review; // } var query = _context.Series .Where(s => s.LibraryId == libraryId) .OrderBy(s => s.SortName) .ProjectTo(_mapper.ConfigurationProvider) .AsNoTracking(); _logger.LogDebug("Processed GetSeriesDtoForLibraryIdAsync in {ElapsedMilliseconds} milliseconds", sw.ElapsedMilliseconds); return await PagedList.CreateAsync(query, userParams.PageNumber, userParams.PageSize); } public async Task> SearchSeries(int[] libraryIds, string searchQuery) { var sw = Stopwatch.StartNew(); var series = await _context.Series .Where(s => libraryIds.Contains(s.LibraryId)) .Where(s => EF.Functions.Like(s.Name, $"%{searchQuery}%") || EF.Functions.Like(s.OriginalName, $"%{searchQuery}%")) .Include(s => s.Library) // NOTE: Is there a way to do this faster? .OrderBy(s => s.SortName) .AsNoTracking() .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); _logger.LogDebug("Processed SearchSeries in {ElapsedMilliseconds} milliseconds", sw.ElapsedMilliseconds); return series; } public async Task> GetVolumesDtoAsync(int seriesId, int userId) { var volumes = await _context.Volume .Where(vol => vol.SeriesId == seriesId) .Include(vol => vol.Chapters) .OrderBy(volume => volume.Number) .ProjectTo(_mapper.ConfigurationProvider) .AsNoTracking() .ToListAsync(); await AddVolumeModifiers(userId, volumes); return volumes; } public async Task> GetVolumes(int seriesId) { return await _context.Volume .Where(vol => vol.SeriesId == seriesId) .Include(vol => vol.Chapters) .ThenInclude(c => c.Files) .OrderBy(vol => vol.Number) .ToListAsync(); } public async Task GetSeriesDtoByIdAsync(int seriesId, int userId) { var series = await _context.Series.Where(x => x.Id == seriesId) .ProjectTo(_mapper.ConfigurationProvider) .SingleAsync(); var seriesList = new List() {series}; await AddSeriesModifiers(userId, seriesList); return seriesList[0]; } public async Task GetVolumeAsync(int volumeId) { return await _context.Volume .Include(vol => vol.Chapters) .ThenInclude(c => c.Files) .SingleOrDefaultAsync(vol => vol.Id == volumeId); } public async Task GetVolumeDtoAsync(int volumeId, int userId) { var volume = await _context.Volume .Where(vol => vol.Id == volumeId) .Include(vol => vol.Chapters) .ThenInclude(c => c.Files) .ProjectTo(_mapper.ConfigurationProvider) .SingleAsync(vol => vol.Id == volumeId); var volumeList = new List() {volume}; await AddVolumeModifiers(userId, volumeList); return volumeList[0]; } /// /// Returns all volumes that contain a seriesId in passed array. /// /// /// public async Task> GetVolumesForSeriesAsync(int[] seriesIds) { return await _context.Volume .Where(v => seriesIds.Contains(v.SeriesId)) .ToListAsync(); } public async Task DeleteSeriesAsync(int seriesId) { var series = await _context.Series.Where(s => s.Id == seriesId).SingleOrDefaultAsync(); _context.Series.Remove(series); return await _context.SaveChangesAsync() > 0; } public async Task GetVolumeByIdAsync(int volumeId) { return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId); } public async Task GetSeriesByIdAsync(int seriesId) { return await _context.Series .Include(s => s.Volumes) .Where(s => s.Id == seriesId) .SingleOrDefaultAsync(); } public async Task GetChapterIdsForSeriesAsync(int[] seriesIds) { var series = await _context.Series .Where(s => seriesIds.Contains(s.Id)) .Include(s => s.Volumes) .ThenInclude(v => v.Chapters) .ToListAsync(); // TODO: refactor this IList chapterIds = new List(); foreach (var s in series) { foreach (var v in s.Volumes) { foreach (var c in v.Chapters) { chapterIds.Add(c.Id); } } } return chapterIds.ToArray(); } public async Task AddSeriesModifiers(int userId, List series) { var userProgress = await _context.AppUserProgresses .Where(p => p.AppUserId == userId && series.Select(s => s.Id).Contains(p.SeriesId)) .ToListAsync(); var userRatings = await _context.AppUserRating .Where(r => r.AppUserId == userId && series.Select(s => s.Id).Contains(r.SeriesId)) .ToListAsync(); foreach (var s in series) { s.PagesRead = userProgress.Where(p => p.SeriesId == s.Id).Sum(p => p.PagesRead); var rating = userRatings.SingleOrDefault(r => r.SeriesId == s.Id); if (rating == null) continue; s.UserRating = rating.Rating; s.UserReview = rating.Review; } } private async Task AddVolumeModifiers(int userId, List volumes) { var userProgress = await _context.AppUserProgresses .Where(p => p.AppUserId == userId && volumes.Select(s => s.Id).Contains(p.VolumeId)) .AsNoTracking() .ToListAsync(); foreach (var v in volumes) { foreach (var c in v.Chapters) { c.PagesRead = userProgress.Where(p => p.ChapterId == c.Id).Sum(p => p.PagesRead); } v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead); } } } }