Browse Source

use dapper in a few places (#29)

* use dapper in a few places

* use single dapper queries
pull/33/head
Jason Dove 6 years ago committed by GitHub
parent
commit
e2cea69f25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ErsatzTV.Core/AggregateModels/GenericIntegerId.cs
  2. 4
      ErsatzTV.Core/AggregateModels/MediaCollectionSummary.cs
  3. 4
      ErsatzTV.Core/AggregateModels/MediaItemSummary.cs
  4. 12
      ErsatzTV.Infrastructure/Data/Configurations/GenericIntegerIdConfiguration.cs
  5. 12
      ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionSummaryConfiguration.cs
  6. 12
      ErsatzTV.Infrastructure/Data/Configurations/MediaItemSummaryConfiguration.cs
  7. 108
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs
  8. 73
      ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs
  9. 32
      ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs
  10. 12
      ErsatzTV.Infrastructure/Data/TvContext.cs
  11. 1
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  12. 15
      ErsatzTV/Startup.cs

4
ErsatzTV.Core/AggregateModels/GenericIntegerId.cs

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
namespace ErsatzTV.Core.AggregateModels
{
public record GenericIntegerId(int Id);
}

4
ErsatzTV.Core/AggregateModels/MediaCollectionSummary.cs

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
namespace ErsatzTV.Core.AggregateModels
{
public record MediaCollectionSummary(int Id, string Name, int ItemCount, bool IsSimple);
}

4
ErsatzTV.Core/AggregateModels/MediaItemSummary.cs

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
namespace ErsatzTV.Core.AggregateModels
{
public record MediaItemSummary(int MediaItemId, string Title, string SortTitle, string Subtitle, string Poster);
}

12
ErsatzTV.Infrastructure/Data/Configurations/GenericIntegerIdConfiguration.cs

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
using ErsatzTV.Core.AggregateModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class GenericIntegerIdConfiguration : IEntityTypeConfiguration<GenericIntegerId>
{
public void Configure(EntityTypeBuilder<GenericIntegerId> builder) =>
builder.HasNoKey().ToView("No table or view exists for GenericIntegerId");
}
}

12
ErsatzTV.Infrastructure/Data/Configurations/MediaCollectionSummaryConfiguration.cs

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
using ErsatzTV.Core.AggregateModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MediaCollectionSummaryConfiguration : IEntityTypeConfiguration<MediaCollectionSummary>
{
public void Configure(EntityTypeBuilder<MediaCollectionSummary> builder) =>
builder.HasNoKey().ToView("No table or view exists for MediaCollectionSummary");
}
}

12
ErsatzTV.Infrastructure/Data/Configurations/MediaItemSummaryConfiguration.cs

@ -1,12 +0,0 @@ @@ -1,12 +0,0 @@
using ErsatzTV.Core.AggregateModels;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace ErsatzTV.Infrastructure.Data.Configurations
{
public class MediaItemSummaryConfiguration : IEntityTypeConfiguration<MediaItemSummary>
{
public void Configure(EntityTypeBuilder<MediaItemSummary> builder) =>
builder.HasNoKey().ToView("No table or view exists for MediaItemSummary");
}
}

108
ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
@ -12,9 +14,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -12,9 +14,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class MediaCollectionRepository : IMediaCollectionRepository
{
private readonly IDbConnection _dbConnection;
private readonly TvContext _dbContext;
public MediaCollectionRepository(TvContext dbContext) => _dbContext = dbContext;
public MediaCollectionRepository(TvContext dbContext, IDbConnection dbConnection)
{
_dbContext = dbContext;
_dbConnection = dbConnection;
}
public async Task<SimpleMediaCollection> Add(SimpleMediaCollection collection)
{
@ -110,61 +117,80 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -110,61 +117,80 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
private async Task<List<TelevisionEpisodeMediaItem>> GetTelevisionShowItems(SimpleMediaCollection collection)
{
// TODO: would be nice to get the media items in one go, but ef...
List<int> showItemIds = await _dbContext.GenericIntegerIds.FromSqlRaw(
@"select tmi.Id
var parameters = new { CollectionId = collection.Id };
return await _dbConnection
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
TelevisionEpisodeMediaItem>(
@"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 = {0}",
collection.Id)
.Select(i => i.Id)
.ToListAsync();
return await _dbContext.TelevisionEpisodeMediaItems
.AsNoTracking()
.Include(e => e.Metadata)
.Where(mi => showItemIds.Contains(mi.Id))
.ToListAsync();
where s.SimpleMediaCollectionsId = @CollectionId",
(episode, statistics, metadata) =>
{
episode.Statistics = statistics;
episode.Metadata = metadata;
return episode;
},
parameters,
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
}
private async Task<List<TelevisionEpisodeMediaItem>> GetTelevisionSeasonItems(SimpleMediaCollection collection)
{
// TODO: would be nice to get the media items in one go, but ef...
List<int> seasonItemIds = await _dbContext.GenericIntegerIds.FromSqlRaw(
@"select tmi.Id
var parameters = new { CollectionId = collection.Id };
return await _dbConnection
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
TelevisionEpisodeMediaItem>(
@"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 = {0}",
collection.Id)
.Select(i => i.Id)
.ToListAsync();
return await _dbContext.TelevisionEpisodeMediaItems
.AsNoTracking()
.Include(e => e.Metadata)
.Where(mi => seasonItemIds.Contains(mi.Id))
.ToListAsync();
where s.SimpleMediaCollectionsId = @CollectionId",
(episode, statistics, metadata) =>
{
episode.Statistics = statistics;
episode.Metadata = metadata;
return episode;
},
parameters,
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
}
private async Task<List<TelevisionEpisodeMediaItem>> GetTelevisionEpisodeItems(SimpleMediaCollection collection)
{
// TODO: would be nice to get the media items in one go, but ef...
List<int> episodeItemIds = await _dbContext.GenericIntegerIds.FromSqlRaw(
@"select s.TelevisionEpisodesId as Id
from SimpleMediaCollectionEpisodes s
where s.SimpleMediaCollectionsId = {0}",
collection.Id)
.Select(i => i.Id)
.ToListAsync();
return await _dbContext.TelevisionEpisodeMediaItems
.AsNoTracking()
.Include(e => e.Metadata)
.Where(mi => episodeItemIds.Contains(mi.Id))
.ToListAsync();
var parameters = new { CollectionId = collection.Id };
return await _dbConnection
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
TelevisionEpisodeMediaItem>(
@"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",
(episode, statistics, metadata) =>
{
episode.Statistics = statistics;
episode.Metadata = metadata;
return episode;
},
parameters,
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
}
}
}

73
ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
@ -13,9 +15,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -13,9 +15,14 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
{
public class TelevisionRepository : ITelevisionRepository
{
private readonly IDbConnection _dbConnection;
private readonly TvContext _dbContext;
public TelevisionRepository(TvContext dbContext) => _dbContext = dbContext;
public TelevisionRepository(TvContext dbContext, IDbConnection dbConnection)
{
_dbContext = dbContext;
_dbConnection = dbConnection;
}
public async Task<bool> Update(TelevisionShow show)
{
@ -245,41 +252,53 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -245,41 +252,53 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public async Task<List<TelevisionEpisodeMediaItem>> GetShowItems(int televisionShowId)
{
// TODO: would be nice to get the media items in one go, but ef...
List<int> showItemIds = await _dbContext.GenericIntegerIds.FromSqlRaw(
@"select tmi.Id
var parameters = new { ShowId = televisionShowId };
return await _dbConnection
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
TelevisionEpisodeMediaItem>(
@"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 = {0}",
televisionShowId)
.Select(i => i.Id)
.ToListAsync();
return await _dbContext.TelevisionEpisodeMediaItems
.AsNoTracking()
.Include(e => e.Metadata)
.Where(mi => showItemIds.Contains(mi.Id))
.ToListAsync();
where ts.Id = @ShowId",
(episode, statistics, metadata) =>
{
episode.Statistics = statistics;
episode.Metadata = metadata;
return episode;
},
parameters,
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
}
public async Task<List<TelevisionEpisodeMediaItem>> GetSeasonItems(int televisionSeasonId)
{
// TODO: would be nice to get the media items in one go, but ef...
List<int> seasonItemIds = await _dbContext.GenericIntegerIds.FromSqlRaw(
@"select tmi.Id
var parameters = new { SeasonId = televisionSeasonId };
return await _dbConnection
.QueryAsync<TelevisionEpisodeMediaItem, MediaItemStatistics, TelevisionEpisodeMetadata,
TelevisionEpisodeMediaItem>(
@"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 = {0}",
televisionSeasonId)
.Select(i => i.Id)
.ToListAsync();
return await _dbContext.TelevisionEpisodeMediaItems
.AsNoTracking()
.Include(e => e.Metadata)
.Where(mi => seasonItemIds.Contains(mi.Id))
.ToListAsync();
where tsn.Id = @SeasonId",
(episode, statistics, metadata) =>
{
episode.Statistics = statistics;
episode.Metadata = metadata;
return episode;
},
parameters,
splitOn: "AudioCodec,TelevisionEpisodeId").Map(result => result.ToList());
}
private async Task<Either<BaseError, TelevisionSeason>> AddSeason(

32
ErsatzTV.Infrastructure/Data/SqliteTypeHandlers.cs

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
using System;
using System.Data;
using Dapper;
namespace ErsatzTV.Infrastructure.Data
{
public abstract class SqliteTypeHandler<T> : SqlMapper.TypeHandler<T>
{
// Parameters are converted by Microsoft.Data.Sqlite
public override void SetValue(IDbDataParameter parameter, T value)
=> parameter.Value = value;
}
public class DateTimeOffsetHandler : SqliteTypeHandler<DateTimeOffset>
{
public override DateTimeOffset Parse(object value)
=> DateTimeOffset.Parse((string)value);
}
public class GuidHandler : SqliteTypeHandler<Guid>
{
public override Guid Parse(object value)
=> Guid.Parse((string)value);
}
public class TimeSpanHandler : SqliteTypeHandler<TimeSpan>
{
public override TimeSpan Parse(object value)
=> TimeSpan.Parse((string)value);
}
}

12
ErsatzTV.Infrastructure/Data/TvContext.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using ErsatzTV.Core.AggregateModels;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -34,11 +33,6 @@ namespace ErsatzTV.Infrastructure.Data @@ -34,11 +33,6 @@ namespace ErsatzTV.Infrastructure.Data
public DbSet<TelevisionShowMetadata> TelevisionShowMetadata { get; set; }
public DbSet<TelevisionSeason> TelevisionSeasons { get; set; }
// support raw sql queries
public DbSet<MediaCollectionSummary> MediaCollectionSummaries { get; set; }
public DbSet<GenericIntegerId> GenericIntegerIds { get; set; }
public DbSet<MediaItemSummary> MediaItemSummaries { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.UseLoggerFactory(_loggerFactory);
@ -46,10 +40,6 @@ namespace ErsatzTV.Infrastructure.Data @@ -46,10 +40,6 @@ namespace ErsatzTV.Infrastructure.Data
{
base.OnModelCreating(builder);
builder.Ignore<MediaCollectionSummary>();
builder.Ignore<GenericIntegerId>();
builder.Ignore<MediaItemSummary>();
builder.ApplyConfigurationsFromAssembly(typeof(TvContext).Assembly);
}
}

1
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.78" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>

15
ErsatzTV/Startup.cs

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
using System;
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;
@ -27,6 +29,7 @@ using FluentValidation.AspNetCore; @@ -27,6 +29,7 @@ using FluentValidation.AspNetCore;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -109,17 +112,23 @@ namespace ErsatzTV @@ -109,17 +112,23 @@ namespace ErsatzTV
// string xmltvPath = Path.Combine(appDataFolder, "xmltv.xml");
// Log.Logger.Information("XMLTV is at {XmltvPath}", xmltvPath);
var connectionString = $"Data Source={FileSystemLayout.DatabasePath}";
services.AddDbContext<TvContext>(
options => options.UseSqlite(
$"Data Source={FileSystemLayout.DatabasePath}",
connectionString,
o =>
{
o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
o.MigrationsAssembly("ErsatzTV.Infrastructure");
}));
services.AddDbContext<LogContext>(
options => options.UseSqlite($"Data Source={FileSystemLayout.LogDatabasePath}"));
services.AddDbContext<LogContext>(options => options.UseSqlite(connectionString));
services.AddTransient<IDbConnection>(_ => new SqliteConnection(connectionString));
SqlMapper.AddTypeHandler(new DateTimeOffsetHandler());
SqlMapper.AddTypeHandler(new GuidHandler());
SqlMapper.AddTypeHandler(new TimeSpanHandler());
services.AddMediatR(typeof(GetAllChannels).Assembly);

Loading…
Cancel
Save