diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs index e8bf06f68..8dfa21a68 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs @@ -14,8 +14,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories { public class MediaCollectionRepository : IMediaCollectionRepository { - private readonly TvContext _dbContext; private readonly IDbConnection _dbConnection; + private readonly TvContext _dbContext; public MediaCollectionRepository(TvContext dbContext, IDbConnection dbConnection) { @@ -118,54 +118,79 @@ namespace ErsatzTV.Infrastructure.Data.Repositories private async Task> GetTelevisionShowItems(SimpleMediaCollection collection) { var parameters = new { CollectionId = collection.Id }; - IEnumerable showItemIds = await _dbConnection.QueryAsync( - @"select tmi.Id + return await _dbConnection + .QueryAsync( + @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, +mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio, +mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width, +tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle from TelevisionEpisodes tmi +inner join MediaItems mi on tmi.Id = mi.Id +inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId inner join TelevisionShows ts on ts.Id = tsn.TelevisionShowId inner join SimpleMediaCollectionShows s on s.TelevisionShowsId = ts.Id where s.SimpleMediaCollectionsId = @CollectionId", - parameters); - - return await _dbContext.TelevisionEpisodeMediaItems - .AsNoTracking() - .Include(e => e.Metadata) - .Where(mi => showItemIds.Contains(mi.Id)) - .ToListAsync(); + (episode, statistics, metadata) => + { + episode.Statistics = statistics; + episode.Metadata = metadata; + return episode; + }, + parameters, + splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList()); } private async Task> GetTelevisionSeasonItems(SimpleMediaCollection collection) { var parameters = new { CollectionId = collection.Id }; - IEnumerable seasonItemIds = await _dbConnection.QueryAsync( - @"select tmi.Id + return await _dbConnection + .QueryAsync( + @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, +mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio, +mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width, +tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle from TelevisionEpisodes tmi +inner join MediaItems mi on tmi.Id = mi.Id +inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId inner join SimpleMediaCollectionSeasons s on s.TelevisionSeasonsId = tsn.Id where s.SimpleMediaCollectionsId = @CollectionId", - parameters); - - return await _dbContext.TelevisionEpisodeMediaItems - .AsNoTracking() - .Include(e => e.Metadata) - .Where(mi => seasonItemIds.Contains(mi.Id)) - .ToListAsync(); + (episode, statistics, metadata) => + { + episode.Statistics = statistics; + episode.Metadata = metadata; + return episode; + }, + parameters, + splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList()); } private async Task> GetTelevisionEpisodeItems(SimpleMediaCollection collection) { var parameters = new { CollectionId = collection.Id }; - IEnumerable episodeItemIds = await _dbConnection.QueryAsync( - @"select s.TelevisionEpisodesId as Id -from SimpleMediaCollectionEpisodes s + return await _dbConnection + .QueryAsync( + @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, +mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio, +mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width, +tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle +from TelevisionEpisodes tmi +inner join MediaItems mi on tmi.Id = mi.Id +inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id +inner join SimpleMediaCollectionEpisodes s on s.TelevisionEpisodesId = tmi.Id where s.SimpleMediaCollectionsId = @CollectionId", - parameters); - - return await _dbContext.TelevisionEpisodeMediaItems - .AsNoTracking() - .Include(e => e.Metadata) - .Where(mi => episodeItemIds.Contains(mi.Id)) - .ToListAsync(); + (episode, statistics, metadata) => + { + episode.Statistics = statistics; + episode.Metadata = metadata; + return episode; + }, + parameters, + splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList()); } } } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs index 580ac99f5..ce2894ec0 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs @@ -15,8 +15,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories { public class TelevisionRepository : ITelevisionRepository { - private readonly TvContext _dbContext; private readonly IDbConnection _dbConnection; + private readonly TvContext _dbContext; public TelevisionRepository(TvContext dbContext, IDbConnection dbConnection) { @@ -253,36 +253,52 @@ namespace ErsatzTV.Infrastructure.Data.Repositories public async Task> GetShowItems(int televisionShowId) { var parameters = new { ShowId = televisionShowId }; - IEnumerable showItemIds = await _dbConnection.QueryAsync( - @"select tmi.Id + return await _dbConnection + .QueryAsync( + @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, +mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio, +mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width, +tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle from TelevisionEpisodes tmi +inner join MediaItems mi on tmi.Id = mi.Id +inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId inner join TelevisionShows ts on ts.Id = tsn.TelevisionShowId where ts.Id = @ShowId", - parameters); - - return await _dbContext.TelevisionEpisodeMediaItems - .AsNoTracking() - .Include(e => e.Metadata) - .Where(mi => showItemIds.Contains(mi.Id)) - .ToListAsync(); + (episode, statistics, metadata) => + { + episode.Statistics = statistics; + episode.Metadata = metadata; + return episode; + }, + parameters, + splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList()); } public async Task> GetSeasonItems(int televisionSeasonId) { var parameters = new { SeasonId = televisionSeasonId }; - IEnumerable seasonItemIds = await _dbConnection.QueryAsync( - @"select tmi.Id + return await _dbConnection + .QueryAsync( + @"select tmi.Id, tmi.SeasonId, mi.MediaSourceId, mi.LastWriteTime, mi.Path, mi.Poster, mi.PosterLastWriteTime, +mi.Statistics_AudioCodec as AudioCodec, mi.Statistics_DisplayAspectRatio as DisplayAspectRatio, mi.Statistics_Duration as Duration, mi.Statistics_Height as Height, mi.Statistics_LastWriteTime as LastWriteTime, mi.Statistics_SampleAspectRatio as SampleAspectRatio, +mi.Statistics_VideoCodec as VideoCodec, mi.Statistics_VideoScanType as VideoScanType, mi.Statistics_Width as Width, +tem.TelevisionEpisodeId, tem.Id, tem.Season, tem.Episode, tem.Plot, tem.Aired, tem.Source, tem.LastWriteTime, tem.Title, tem.SortTitle from TelevisionEpisodes tmi +inner join MediaItems mi on tmi.Id = mi.Id +inner join TelevisionEpisodeMetadata tem on tem.TelevisionEpisodeId = tmi.Id inner join TelevisionSeasons tsn on tsn.Id = tmi.SeasonId where tsn.Id = @SeasonId", - parameters); - - return await _dbContext.TelevisionEpisodeMediaItems - .AsNoTracking() - .Include(e => e.Metadata) - .Where(mi => seasonItemIds.Contains(mi.Id)) - .ToListAsync(); + (episode, statistics, metadata) => + { + episode.Statistics = statistics; + episode.Metadata = metadata; + return episode; + }, + parameters, + splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList()); } private async Task> AddSeason( diff --git a/ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs b/ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs new file mode 100644 index 000000000..c9ac2fb99 --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs @@ -0,0 +1,32 @@ +using System; +using System.Data; +using Dapper; + +namespace ErsatzTV.Infrastructure.Data +{ + public abstract class SqliteTypeHandler : SqlMapper.TypeHandler + { + // Parameters are converted by Microsoft.Data.Sqlite + public override void SetValue(IDbDataParameter parameter, T value) + => parameter.Value = value; + } + + public class DateTimeOffsetHandler : SqliteTypeHandler + { + public override DateTimeOffset Parse(object value) + => DateTimeOffset.Parse((string)value); + } + + public class GuidHandler : SqliteTypeHandler + { + public override Guid Parse(object value) + => Guid.Parse((string)value); + } + + public class TimeSpanHandler : SqliteTypeHandler + { + public override TimeSpan Parse(object value) + => TimeSpan.Parse((string)value); + } + +} diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index ccff660fb..9e75db1e2 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -3,6 +3,7 @@ using System.Data; using System.IO; using System.Reflection; using System.Threading.Channels; +using Dapper; using ErsatzTV.Application; using ErsatzTV.Application.Channels.Queries; using ErsatzTV.Core; @@ -125,7 +126,10 @@ namespace ErsatzTV services.AddDbContext(options => options.UseSqlite(connectionString)); services.AddTransient(_ => new SqliteConnection(connectionString)); - + SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); + SqlMapper.AddTypeHandler(new GuidHandler()); + SqlMapper.AddTypeHandler(new TimeSpanHandler()); + services.AddMediatR(typeof(GetAllChannels).Assembly); services.AddRefitClient()