mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-18 21:34:28 -04:00
Swap to dapper with lazyload
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.ArtistStats
|
||||
{
|
||||
@@ -13,6 +16,8 @@ namespace NzbDrone.Core.ArtistStats
|
||||
|
||||
public class ArtistStatisticsRepository : IArtistStatisticsRepository
|
||||
{
|
||||
private const string _selectTemplate = "SELECT /**select**/ FROM Tracks /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
public ArtistStatisticsRepository(IMainDatabase database)
|
||||
@@ -22,57 +27,41 @@ namespace NzbDrone.Core.ArtistStats
|
||||
|
||||
public List<AlbumStatistics> ArtistStatistics()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.AddParameter("currentDate", DateTime.UtcNow);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(GetSelectClause());
|
||||
sb.AppendLine("AND Albums.ReleaseDate < @currentDate");
|
||||
sb.AppendLine(GetGroupByClause());
|
||||
var queryText = sb.ToString();
|
||||
|
||||
return mapper.Query<AlbumStatistics>(queryText);
|
||||
var time = DateTime.UtcNow;
|
||||
return Query(Builder().Where<Album>(x => x.ReleaseDate < time));
|
||||
}
|
||||
|
||||
public List<AlbumStatistics> ArtistStatistics(int artistId)
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.AddParameter("currentDate", DateTime.UtcNow);
|
||||
mapper.AddParameter("artistId", artistId);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(GetSelectClause());
|
||||
sb.AppendLine("AND Artists.Id = @artistId");
|
||||
sb.AppendLine("AND Albums.ReleaseDate < @currentDate");
|
||||
sb.AppendLine(GetGroupByClause());
|
||||
var queryText = sb.ToString();
|
||||
|
||||
return mapper.Query<AlbumStatistics>(queryText);
|
||||
var time = DateTime.UtcNow;
|
||||
return Query(Builder().Where<Album>(x => x.ReleaseDate < time)
|
||||
.Where<Artist>(x => x.Id == artistId));
|
||||
}
|
||||
|
||||
private string GetSelectClause()
|
||||
private List<AlbumStatistics> Query(SqlBuilder builder)
|
||||
{
|
||||
return @"SELECT
|
||||
Artists.Id AS ArtistId,
|
||||
var sql = builder.AddTemplate(_selectTemplate).LogQuery();
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
return conn.Query<AlbumStatistics>(sql.RawSql, sql.Parameters).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private SqlBuilder Builder() => new SqlBuilder()
|
||||
.Select(@"Artists.Id AS ArtistId,
|
||||
Albums.Id AS AlbumId,
|
||||
SUM(COALESCE(TrackFiles.Size, 0)) AS SizeOnDisk,
|
||||
COUNT(Tracks.Id) AS TotalTrackCount,
|
||||
SUM(CASE WHEN Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS AvailableTrackCount,
|
||||
SUM(CASE WHEN Albums.Monitored = 1 OR Tracks.TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount,
|
||||
SUM(CASE WHEN TrackFiles.Id IS NULL THEN 0 ELSE 1 END) AS TrackFileCount
|
||||
FROM Tracks
|
||||
JOIN AlbumReleases ON Tracks.AlbumReleaseId = AlbumReleases.Id
|
||||
JOIN Albums ON AlbumReleases.AlbumId = Albums.Id
|
||||
JOIN Artists on Albums.ArtistMetadataId = Artists.ArtistMetadataId
|
||||
LEFT OUTER JOIN TrackFiles ON Tracks.TrackFileId = TrackFiles.Id
|
||||
WHERE AlbumReleases.Monitored = 1";
|
||||
}
|
||||
|
||||
private string GetGroupByClause()
|
||||
{
|
||||
return "GROUP BY Artists.Id, Albums.Id";
|
||||
}
|
||||
SUM(CASE WHEN TrackFiles.Id IS NULL THEN 0 ELSE 1 END) AS TrackFileCount")
|
||||
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
|
||||
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||
.LeftJoin<Track, TrackFile>((t, f) => t.TrackFileId == f.Id)
|
||||
.Where<AlbumRelease>(x => x.Monitored == true)
|
||||
.GroupBy<Artist>(x => x.Id)
|
||||
.GroupBy<Album>(x => x.Id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ namespace NzbDrone.Core.Authentication
|
||||
|
||||
public User FindUser(string username)
|
||||
{
|
||||
return Query.Where(u => u.Username == username).SingleOrDefault();
|
||||
return Query(x => x.Username == username).SingleOrDefault();
|
||||
}
|
||||
|
||||
public User FindUser(Guid identifier)
|
||||
{
|
||||
return Query.Where(u => u.Identifier == identifier).SingleOrDefault();
|
||||
return Query(x => x.Identifier == identifier).SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,12 @@ namespace NzbDrone.Core.Backup
|
||||
|
||||
public void BackupDatabase(IDatabase database, string targetDirectory)
|
||||
{
|
||||
var sourceConnectionString = database.GetDataMapper().ConnectionString;
|
||||
var sourceConnectionString = "";
|
||||
using (var db = database.OpenConnection())
|
||||
{
|
||||
sourceConnectionString = db.ConnectionString;
|
||||
}
|
||||
|
||||
var backupConnectionStringBuilder = new SQLiteConnectionStringBuilder(sourceConnectionString);
|
||||
|
||||
backupConnectionStringBuilder.DataSource = Path.Combine(targetDirectory, Path.GetFileName(backupConnectionStringBuilder.DataSource));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Marr.Data.QGen;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
@@ -22,26 +22,24 @@ namespace NzbDrone.Core.Blacklisting
|
||||
|
||||
public List<Blacklist> BlacklistedByTitle(int artistId, string sourceTitle)
|
||||
{
|
||||
return Query.Where(e => e.ArtistId == artistId)
|
||||
.AndWhere(e => e.SourceTitle.Contains(sourceTitle));
|
||||
return Query(e => e.ArtistId == artistId && e.SourceTitle.Contains(sourceTitle));
|
||||
}
|
||||
|
||||
public List<Blacklist> BlacklistedByTorrentInfoHash(int artistId, string torrentInfoHash)
|
||||
{
|
||||
return Query.Where(e => e.ArtistId == artistId)
|
||||
.AndWhere(e => e.TorrentInfoHash.Contains(torrentInfoHash));
|
||||
return Query(e => e.ArtistId == artistId && e.TorrentInfoHash.Contains(torrentInfoHash));
|
||||
}
|
||||
|
||||
public List<Blacklist> BlacklistedByArtist(int artistId)
|
||||
{
|
||||
return Query.Where(b => b.ArtistId == artistId);
|
||||
return Query(b => b.ArtistId == artistId);
|
||||
}
|
||||
|
||||
protected override SortBuilder<Blacklist> GetPagedQuery(QueryBuilder<Blacklist> query, PagingSpec<Blacklist> pagingSpec)
|
||||
{
|
||||
var baseQuery = query.Join<Blacklist, Artist>(JoinType.Inner, h => h.Artist, (h, s) => h.ArtistId == s.Id);
|
||||
|
||||
return base.GetPagedQuery(baseQuery, pagingSpec);
|
||||
}
|
||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join<Blacklist, Artist>((b, m) => b.ArtistId == m.Id);
|
||||
protected override IEnumerable<Blacklist> PagedQuery(SqlBuilder builder) => _database.QueryJoined<Blacklist, Artist>(builder, (bl, artist) =>
|
||||
{
|
||||
bl.Artist = artist;
|
||||
return bl;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace NzbDrone.Core.Configuration
|
||||
bool AnalyticsEnabled { get; }
|
||||
string LogLevel { get; }
|
||||
string ConsoleLogLevel { get; }
|
||||
bool LogSql { get; }
|
||||
int LogRotate { get; }
|
||||
bool FilterSentryEvents { get; }
|
||||
string Branch { get; }
|
||||
string ApiKey { get; }
|
||||
@@ -177,6 +179,8 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string LogLevel => GetValue("LogLevel", "info");
|
||||
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
|
||||
public bool LogSql => GetValueBoolean("LogSql", false, persist: false);
|
||||
public int LogRotate => GetValueInt("LogRotate", 50, persist: false);
|
||||
public bool FilterSentryEvents => GetValueBoolean("FilterSentryEvents", true, persist: false);
|
||||
public string SslCertPath => GetValue("SslCertPath", "");
|
||||
public string SslCertPassword => GetValue("SslCertPassword", "");
|
||||
@@ -204,9 +208,9 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public string UpdateScriptPath => GetValue("UpdateScriptPath", "", false);
|
||||
|
||||
public int GetValueInt(string key, int defaultValue)
|
||||
public int GetValueInt(string key, int defaultValue, bool persist = true)
|
||||
{
|
||||
return Convert.ToInt32(GetValue(key, defaultValue));
|
||||
return Convert.ToInt32(GetValue(key, defaultValue, persist));
|
||||
}
|
||||
|
||||
public bool GetValueBoolean(string key, bool defaultValue, bool persist = true)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace NzbDrone.Core.Configuration
|
||||
|
||||
public Config Get(string key)
|
||||
{
|
||||
return Query.Where(c => c.Key == key).SingleOrDefault();
|
||||
return Query(c => c.Key == key).SingleOrDefault();
|
||||
}
|
||||
|
||||
public Config Upsert(string key, string value)
|
||||
|
||||
@@ -3,10 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Marr.Data;
|
||||
using Marr.Data.QGen;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
@@ -17,59 +17,77 @@ namespace NzbDrone.Core.Datastore
|
||||
IEnumerable<TModel> All();
|
||||
int Count();
|
||||
TModel Get(int id);
|
||||
IEnumerable<TModel> Get(IEnumerable<int> ids);
|
||||
TModel SingleOrDefault();
|
||||
TModel Insert(TModel model);
|
||||
TModel Update(TModel model);
|
||||
TModel Upsert(TModel model);
|
||||
void Delete(int id);
|
||||
void SetFields(TModel model, params Expression<Func<TModel, object>>[] properties);
|
||||
void Delete(TModel model);
|
||||
void Delete(int id);
|
||||
IEnumerable<TModel> Get(IEnumerable<int> ids);
|
||||
void InsertMany(IList<TModel> model);
|
||||
void UpdateMany(IList<TModel> model);
|
||||
void SetFields(IList<TModel> models, params Expression<Func<TModel, object>>[] properties);
|
||||
void DeleteMany(List<TModel> model);
|
||||
void DeleteMany(IEnumerable<int> ids);
|
||||
void Purge(bool vacuum = false);
|
||||
bool HasItems();
|
||||
void DeleteMany(IEnumerable<int> ids);
|
||||
void SetFields(TModel model, params Expression<Func<TModel, object>>[] properties);
|
||||
void SetFields(IEnumerable<TModel> models, params Expression<Func<TModel, object>>[] properties);
|
||||
TModel Single();
|
||||
TModel SingleOrDefault();
|
||||
PagingSpec<TModel> GetPaged(PagingSpec<TModel> pagingSpec);
|
||||
}
|
||||
|
||||
public class BasicRepository<TModel> : IBasicRepository<TModel>
|
||||
where TModel : ModelBase, new()
|
||||
{
|
||||
private readonly IDatabase _database;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly PropertyInfo _keyProperty;
|
||||
private readonly List<PropertyInfo> _properties;
|
||||
private readonly string _updateSql;
|
||||
private readonly string _insertSql;
|
||||
|
||||
protected IDataMapper DataMapper => _database.GetDataMapper();
|
||||
protected readonly IDatabase _database;
|
||||
protected readonly string _table;
|
||||
|
||||
public BasicRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||
{
|
||||
_database = database;
|
||||
_eventAggregator = eventAggregator;
|
||||
|
||||
var type = typeof(TModel);
|
||||
|
||||
_table = TableMapping.Mapper.TableNameMapping(type);
|
||||
_keyProperty = type.GetProperty(nameof(ModelBase.Id));
|
||||
|
||||
var excluded = TableMapping.Mapper.ExcludeProperties(type).Select(x => x.Name).ToList();
|
||||
excluded.Add(_keyProperty.Name);
|
||||
_properties = type.GetProperties().Where(x => x.IsMappableProperty() && !excluded.Contains(x.Name)).ToList();
|
||||
|
||||
_insertSql = GetInsertSql();
|
||||
_updateSql = GetUpdateSql(_properties);
|
||||
}
|
||||
|
||||
protected virtual QueryBuilder<TModel> Query => DataMapper.Query<TModel>();
|
||||
protected virtual SqlBuilder Builder() => new SqlBuilder();
|
||||
|
||||
protected void Delete(Expression<Func<TModel, bool>> filter)
|
||||
{
|
||||
DataMapper.Delete(filter);
|
||||
}
|
||||
protected virtual List<TModel> Query(SqlBuilder builder) => _database.Query<TModel>(builder).ToList();
|
||||
|
||||
public IEnumerable<TModel> All()
|
||||
{
|
||||
return Query.ToList();
|
||||
}
|
||||
protected List<TModel> Query(Expression<Func<TModel, bool>> where) => Query(Builder().Where(where));
|
||||
|
||||
public int Count()
|
||||
{
|
||||
return DataMapper.Query<TModel>().GetRowCount();
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
return conn.ExecuteScalar<int>($"SELECT COUNT(*) FROM {_table}");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TModel> All()
|
||||
{
|
||||
return Query(Builder());
|
||||
}
|
||||
|
||||
public TModel Get(int id)
|
||||
{
|
||||
var model = Query.Where(c => c.Id == id).SingleOrDefault();
|
||||
var model = Query(x => x.Id == id).FirstOrDefault();
|
||||
|
||||
if (model == null)
|
||||
{
|
||||
@@ -81,13 +99,16 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
public IEnumerable<TModel> Get(IEnumerable<int> ids)
|
||||
{
|
||||
var idList = ids.ToList();
|
||||
var query = string.Format("[t0].[Id] IN ({0})", string.Join(",", idList));
|
||||
var result = Query.Where(query).ToList();
|
||||
|
||||
if (result.Count != idList.Count())
|
||||
if (!ids.Any())
|
||||
{
|
||||
throw new ApplicationException($"Expected query to return {idList.Count} rows but returned {result.Count}");
|
||||
return new List<TModel>();
|
||||
}
|
||||
|
||||
var result = Query(x => ids.Contains(x.Id));
|
||||
|
||||
if (result.Count != ids.Count())
|
||||
{
|
||||
throw new ApplicationException($"Expected query to return {ids.Count()} rows but returned {result.Count}");
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -110,13 +131,74 @@ namespace NzbDrone.Core.Datastore
|
||||
throw new InvalidOperationException("Can't insert model with existing ID " + model.Id);
|
||||
}
|
||||
|
||||
DataMapper.Insert(model);
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
model = Insert(conn, null, model);
|
||||
}
|
||||
|
||||
ModelCreated(model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private string GetInsertSql()
|
||||
{
|
||||
var sbColumnList = new StringBuilder(null);
|
||||
for (var i = 0; i < _properties.Count; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
sbColumnList.AppendFormat("\"{0}\"", property.Name);
|
||||
if (i < _properties.Count - 1)
|
||||
{
|
||||
sbColumnList.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
var sbParameterList = new StringBuilder(null);
|
||||
for (var i = 0; i < _properties.Count; i++)
|
||||
{
|
||||
var property = _properties[i];
|
||||
sbParameterList.AppendFormat("@{0}", property.Name);
|
||||
if (i < _properties.Count - 1)
|
||||
{
|
||||
sbParameterList.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
return $"INSERT INTO {_table} ({sbColumnList.ToString()}) VALUES ({sbParameterList.ToString()}); SELECT last_insert_rowid() id";
|
||||
}
|
||||
|
||||
private TModel Insert(IDbConnection connection, IDbTransaction transaction, TModel model)
|
||||
{
|
||||
SqlBuilderExtensions.LogQuery(_insertSql, model);
|
||||
var multi = connection.QueryMultiple(_insertSql, model, transaction);
|
||||
var id = (int)multi.Read().First().id;
|
||||
_keyProperty.SetValue(model, id);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
public void InsertMany(IList<TModel> models)
|
||||
{
|
||||
if (models.Any(x => x.Id != 0))
|
||||
{
|
||||
throw new InvalidOperationException("Can't insert model with existing ID != 0");
|
||||
}
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
using (IDbTransaction tran = conn.BeginTransaction(IsolationLevel.ReadCommitted))
|
||||
{
|
||||
foreach (var model in models)
|
||||
{
|
||||
Insert(conn, tran, model);
|
||||
}
|
||||
|
||||
tran.Commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TModel Update(TModel model)
|
||||
{
|
||||
if (model.Id == 0)
|
||||
@@ -124,52 +206,59 @@ namespace NzbDrone.Core.Datastore
|
||||
throw new InvalidOperationException("Can't update model with ID 0");
|
||||
}
|
||||
|
||||
DataMapper.Update(model, c => c.Id == model.Id);
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
UpdateFields(conn, null, model, _properties);
|
||||
}
|
||||
|
||||
ModelUpdated(model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
public void UpdateMany(IList<TModel> models)
|
||||
{
|
||||
if (models.Any(x => x.Id == 0))
|
||||
{
|
||||
throw new InvalidOperationException("Can't update model with ID 0");
|
||||
}
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
UpdateFields(conn, null, models, _properties);
|
||||
}
|
||||
}
|
||||
|
||||
protected void Delete(Expression<Func<TModel, bool>> where)
|
||||
{
|
||||
Delete(Builder().Where<TModel>(where));
|
||||
}
|
||||
|
||||
protected void Delete(SqlBuilder builder)
|
||||
{
|
||||
var sql = builder.AddDeleteTemplate(typeof(TModel)).LogQuery();
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
conn.Execute(sql.RawSql, sql.Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(TModel model)
|
||||
{
|
||||
Delete(model.Id);
|
||||
}
|
||||
|
||||
public void InsertMany(IList<TModel> models)
|
||||
public void Delete(int id)
|
||||
{
|
||||
using (var unitOfWork = new UnitOfWork(() => DataMapper))
|
||||
{
|
||||
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
foreach (var model in models)
|
||||
{
|
||||
unitOfWork.DB.Insert(model);
|
||||
}
|
||||
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
Delete(x => x.Id == id);
|
||||
}
|
||||
|
||||
public void UpdateMany(IList<TModel> models)
|
||||
public void DeleteMany(IEnumerable<int> ids)
|
||||
{
|
||||
using (var unitOfWork = new UnitOfWork(() => DataMapper))
|
||||
if (ids.Any())
|
||||
{
|
||||
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
foreach (var model in models)
|
||||
{
|
||||
var localModel = model;
|
||||
|
||||
if (model.Id == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Can't update model with ID 0");
|
||||
}
|
||||
|
||||
unitOfWork.DB.Update(model, c => c.Id == localModel.Id);
|
||||
}
|
||||
|
||||
unitOfWork.Commit();
|
||||
Delete(x => ids.Contains(x.Id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,31 +279,13 @@ namespace NzbDrone.Core.Datastore
|
||||
return model;
|
||||
}
|
||||
|
||||
public void Delete(int id)
|
||||
{
|
||||
DataMapper.Delete<TModel>(c => c.Id == id);
|
||||
}
|
||||
|
||||
public void DeleteMany(IEnumerable<int> ids)
|
||||
{
|
||||
using (var unitOfWork = new UnitOfWork(() => DataMapper))
|
||||
{
|
||||
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
var localId = id;
|
||||
|
||||
unitOfWork.DB.Delete<TModel>(c => c.Id == localId);
|
||||
}
|
||||
|
||||
unitOfWork.Commit();
|
||||
}
|
||||
}
|
||||
|
||||
public void Purge(bool vacuum = false)
|
||||
{
|
||||
DataMapper.Delete<TModel>(c => c.Id > -1);
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
conn.Execute($"DELETE FROM [{_table}]");
|
||||
}
|
||||
|
||||
if (vacuum)
|
||||
{
|
||||
Vacuum();
|
||||
@@ -235,67 +306,126 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
if (model.Id == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Attempted to updated model without ID");
|
||||
throw new InvalidOperationException("Attempted to update model without ID");
|
||||
}
|
||||
|
||||
DataMapper.Update<TModel>()
|
||||
.Where(c => c.Id == model.Id)
|
||||
.ColumnsIncluding(properties)
|
||||
.Entity(model)
|
||||
.Execute();
|
||||
var propertiesToUpdate = properties.Select(x => x.GetMemberName()).ToList();
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
UpdateFields(conn, null, model, propertiesToUpdate);
|
||||
}
|
||||
|
||||
ModelUpdated(model);
|
||||
}
|
||||
|
||||
public void SetFields(IEnumerable<TModel> models, params Expression<Func<TModel, object>>[] properties)
|
||||
public void SetFields(IList<TModel> models, params Expression<Func<TModel, object>>[] properties)
|
||||
{
|
||||
using (var unitOfWork = new UnitOfWork(() => DataMapper))
|
||||
if (models.Any(x => x.Id == 0))
|
||||
{
|
||||
unitOfWork.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
throw new InvalidOperationException("Attempted to update model without ID");
|
||||
}
|
||||
|
||||
foreach (var model in models)
|
||||
{
|
||||
if (model.Id == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Can't update model with ID 0");
|
||||
}
|
||||
var propertiesToUpdate = properties.Select(x => x.GetMemberName()).ToList();
|
||||
|
||||
unitOfWork.DB.Update<TModel>()
|
||||
.Where(c => c.Id == model.Id)
|
||||
.ColumnsIncluding(properties)
|
||||
.Entity(model)
|
||||
.Execute();
|
||||
}
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
UpdateFields(conn, null, models, propertiesToUpdate);
|
||||
}
|
||||
|
||||
unitOfWork.Commit();
|
||||
foreach (var model in models)
|
||||
{
|
||||
ModelUpdated(model);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUpdateSql(List<PropertyInfo> propertiesToUpdate)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat("UPDATE {0} SET ", _table);
|
||||
|
||||
for (var i = 0; i < propertiesToUpdate.Count; i++)
|
||||
{
|
||||
var property = propertiesToUpdate[i];
|
||||
sb.AppendFormat("\"{0}\" = @{1}", property.Name, property.Name);
|
||||
if (i < propertiesToUpdate.Count - 1)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append($" WHERE \"{_keyProperty.Name}\" = @{_keyProperty.Name}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void UpdateFields(IDbConnection connection, IDbTransaction transaction, TModel model, List<PropertyInfo> propertiesToUpdate)
|
||||
{
|
||||
var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate);
|
||||
|
||||
SqlBuilderExtensions.LogQuery(sql, model);
|
||||
|
||||
connection.Execute(sql, model, transaction: transaction);
|
||||
}
|
||||
|
||||
private void UpdateFields(IDbConnection connection, IDbTransaction transaction, IList<TModel> models, List<PropertyInfo> propertiesToUpdate)
|
||||
{
|
||||
var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate);
|
||||
|
||||
// SqlBuilderExtensions.LogQuery(sql, models);
|
||||
connection.Execute(sql, models, transaction: transaction);
|
||||
}
|
||||
|
||||
protected virtual SqlBuilder PagedBuilder() => Builder();
|
||||
protected virtual IEnumerable<TModel> PagedQuery(SqlBuilder sql) => Query(sql);
|
||||
|
||||
public virtual PagingSpec<TModel> GetPaged(PagingSpec<TModel> pagingSpec)
|
||||
{
|
||||
pagingSpec.Records = GetPagedQuery(Query, pagingSpec).ToList();
|
||||
pagingSpec.TotalRecords = GetPagedQuery(Query, pagingSpec).GetRowCount();
|
||||
pagingSpec.Records = GetPagedRecords(PagedBuilder(), pagingSpec, PagedQuery);
|
||||
pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder().SelectCount(), pagingSpec);
|
||||
|
||||
return pagingSpec;
|
||||
}
|
||||
|
||||
protected virtual SortBuilder<TModel> GetPagedQuery(QueryBuilder<TModel> query, PagingSpec<TModel> pagingSpec)
|
||||
private void AddFilters(SqlBuilder builder, PagingSpec<TModel> pagingSpec)
|
||||
{
|
||||
var filterExpressions = pagingSpec.FilterExpressions;
|
||||
var sortQuery = query.Where(filterExpressions.FirstOrDefault());
|
||||
var filters = pagingSpec.FilterExpressions;
|
||||
|
||||
if (filterExpressions.Count > 1)
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
// Start at the second item for the AndWhere clauses
|
||||
for (var i = 1; i < filterExpressions.Count; i++)
|
||||
{
|
||||
sortQuery.AndWhere(filterExpressions[i]);
|
||||
}
|
||||
builder.Where<TModel>(filter);
|
||||
}
|
||||
}
|
||||
|
||||
protected List<TModel> GetPagedRecords(SqlBuilder builder, PagingSpec<TModel> pagingSpec, Func<SqlBuilder, IEnumerable<TModel>> queryFunc)
|
||||
{
|
||||
AddFilters(builder, pagingSpec);
|
||||
|
||||
var sortDirection = pagingSpec.SortDirection == SortDirection.Descending ? "DESC" : "ASC";
|
||||
var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize;
|
||||
builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}");
|
||||
|
||||
return queryFunc(builder).ToList();
|
||||
}
|
||||
|
||||
protected int GetPagedRecordCount(SqlBuilder builder, PagingSpec<TModel> pagingSpec, string template = null)
|
||||
{
|
||||
AddFilters(builder, pagingSpec);
|
||||
|
||||
SqlBuilder.Template sql;
|
||||
if (template != null)
|
||||
{
|
||||
sql = builder.AddTemplate(template).LogQuery();
|
||||
}
|
||||
else
|
||||
{
|
||||
sql = builder.AddPageCountTemplate(typeof(TModel));
|
||||
}
|
||||
|
||||
return sortQuery.OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection())
|
||||
.Skip(pagingSpec.PagingOffset())
|
||||
.Take(pagingSpec.PageSize);
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
return conn.ExecuteScalar<int>(sql.RawSql, sql.Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ModelCreated(TModel model)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class BooleanIntConverter : IConverter
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
var val = (long)context.DbValue;
|
||||
|
||||
switch (val)
|
||||
{
|
||||
case 1:
|
||||
return true;
|
||||
case 0:
|
||||
return false;
|
||||
default:
|
||||
throw new ConversionException(string.Format("The BooleanCharConverter could not convert the value '{0}' to a Boolean.", context.DbValue));
|
||||
}
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
var val = (bool?)clrValue;
|
||||
|
||||
switch (val)
|
||||
{
|
||||
case true:
|
||||
return 1;
|
||||
case false:
|
||||
return 0;
|
||||
default:
|
||||
return DBNull.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public Type DbType => typeof(int);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,28 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Reflection;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class CommandConverter : EmbeddedDocumentConverter
|
||||
public class CommandConverter : EmbeddedDocumentConverter<Command>
|
||||
{
|
||||
public override object FromDB(ConverterContext context)
|
||||
public override Command Parse(object value)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var stringValue = (string)context.DbValue;
|
||||
var stringValue = (string)value;
|
||||
|
||||
if (stringValue.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ordinal = context.DataRecord.GetOrdinal("Name");
|
||||
var contract = context.DataRecord.GetString(ordinal);
|
||||
string contract;
|
||||
using (JsonDocument body = JsonDocument.Parse(stringValue))
|
||||
{
|
||||
contract = body.RootElement.GetProperty("name").GetString();
|
||||
}
|
||||
|
||||
var impType = typeof(Command).Assembly.FindTypeByName(contract + "Command");
|
||||
|
||||
if (impType == null)
|
||||
@@ -32,7 +30,12 @@ namespace NzbDrone.Core.Datastore.Converters
|
||||
throw new CommandNotFoundException(contract);
|
||||
}
|
||||
|
||||
return Json.Deserialize(stringValue, impType);
|
||||
return (Command)JsonSerializer.Deserialize(stringValue, impType, SerializerSettings);
|
||||
}
|
||||
|
||||
public override void SetValue(IDbDataParameter parameter, Command value)
|
||||
{
|
||||
parameter.Value = value == null ? null : JsonSerializer.Serialize(value, SerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class DoubleConverter : IConverter
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
if (context.DbValue is double)
|
||||
{
|
||||
return context.DbValue;
|
||||
}
|
||||
|
||||
return Convert.ToDouble(context.DbValue);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
if (dbValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
if (dbValue is double)
|
||||
{
|
||||
return dbValue;
|
||||
}
|
||||
|
||||
return Convert.ToDouble(dbValue);
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
return clrValue;
|
||||
}
|
||||
|
||||
public Type DbType { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,50 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Dapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class EmbeddedDocumentConverter : IConverter
|
||||
public class EmbeddedDocumentConverter<T> : SqlMapper.TypeHandler<T>
|
||||
{
|
||||
private readonly JsonSerializerSettings _serializerSetting;
|
||||
protected readonly JsonSerializerOptions SerializerSettings;
|
||||
|
||||
public EmbeddedDocumentConverter(params JsonConverter[] converters)
|
||||
public EmbeddedDocumentConverter()
|
||||
{
|
||||
_serializerSetting = new JsonSerializerSettings
|
||||
var serializerSettings = new JsonSerializerOptions
|
||||
{
|
||||
DateTimeZoneHandling = DateTimeZoneHandling.Utc,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
Formatting = Formatting.Indented,
|
||||
DefaultValueHandling = DefaultValueHandling.Include,
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
AllowTrailingCommas = true,
|
||||
IgnoreNullValues = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
_serializerSetting.Converters.Add(new StringEnumConverter { NamingStrategy = new CamelCaseNamingStrategy() });
|
||||
_serializerSetting.Converters.Add(new VersionConverter());
|
||||
serializerSettings.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, true));
|
||||
serializerSettings.Converters.Add(new TimeSpanConverter());
|
||||
serializerSettings.Converters.Add(new UtcConverter());
|
||||
|
||||
SerializerSettings = serializerSettings;
|
||||
}
|
||||
|
||||
public EmbeddedDocumentConverter(params JsonConverter[] converters)
|
||||
: this()
|
||||
{
|
||||
foreach (var converter in converters)
|
||||
{
|
||||
_serializerSetting.Converters.Add(converter);
|
||||
SerializerSettings.Converters.Add(converter);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual object FromDB(ConverterContext context)
|
||||
public override void SetValue(IDbDataParameter parameter, T value)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
var stringValue = (string)context.DbValue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(stringValue))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeObject(stringValue, context.ColumnMap.FieldType, _serializerSetting);
|
||||
parameter.Value = JsonSerializer.Serialize(value, SerializerSettings);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override T Parse(object value)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
return JsonSerializer.Deserialize<T>((string)value, SerializerSettings);
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (clrValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
return JsonConvert.SerializeObject(clrValue, _serializerSetting);
|
||||
}
|
||||
|
||||
public Type DbType => typeof(string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class EnumIntConverter : IConverter
|
||||
{
|
||||
public Type DbType => typeof(int);
|
||||
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue != null && context.DbValue != DBNull.Value)
|
||||
{
|
||||
return Enum.ToObject(context.ColumnMap.FieldType, (long)context.DbValue);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue != null)
|
||||
{
|
||||
return (int)clrValue;
|
||||
}
|
||||
|
||||
return DBNull.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,24 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using System;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class GuidConverter : IConverter
|
||||
public class GuidConverter : SqlMapper.TypeHandler<Guid>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override Guid Parse(object value)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
if (value == null)
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
var value = (string)context.DbValue;
|
||||
|
||||
return new Guid(value);
|
||||
return new Guid((string)value);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override void SetValue(IDbDataParameter parameter, Guid value)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
parameter.Value = value.ToString();
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue == null)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
var value = clrValue;
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
public Type DbType => typeof(string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class Int32Converter : IConverter
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return DBNull.Value;
|
||||
}
|
||||
|
||||
if (context.DbValue is int)
|
||||
{
|
||||
return context.DbValue;
|
||||
}
|
||||
|
||||
return Convert.ToInt32(context.DbValue);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
return clrValue;
|
||||
}
|
||||
|
||||
public Type DbType { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,25 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Disk;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class OsPathConverter : IConverter
|
||||
public class OsPathConverter : SqlMapper.TypeHandler<OsPath>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override void SetValue(IDbDataParameter parameter, OsPath value)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
parameter.Value = value.FullPath;
|
||||
}
|
||||
|
||||
public override OsPath Parse(object value)
|
||||
{
|
||||
if (value == null || value is DBNull)
|
||||
{
|
||||
return DBNull.Value;
|
||||
return new OsPath(null);
|
||||
}
|
||||
|
||||
var value = (string)context.DbValue;
|
||||
|
||||
return new OsPath(value);
|
||||
return new OsPath((string)value);
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
var value = (OsPath)clrValue;
|
||||
|
||||
return value.FullPath;
|
||||
}
|
||||
|
||||
public Type DbType => typeof(string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,21 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class PrimaryAlbumTypeIntConverter : JsonConverter, IConverter
|
||||
public class PrimaryAlbumTypeIntConverter : JsonConverter<PrimaryAlbumType>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override PrimaryAlbumType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return PrimaryAlbumType.Album;
|
||||
}
|
||||
|
||||
var val = Convert.ToInt32(context.DbValue);
|
||||
|
||||
return (PrimaryAlbumType)val;
|
||||
var item = reader.GetInt32();
|
||||
return (PrimaryAlbumType)item;
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override void Write(Utf8JsonWriter writer, PrimaryAlbumType value, JsonSerializerOptions options)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue == DBNull.Value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (clrValue as PrimaryAlbumType == null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempted to save an album type that isn't really an album type");
|
||||
}
|
||||
|
||||
var primType = (PrimaryAlbumType)clrValue;
|
||||
return (int)primType;
|
||||
}
|
||||
|
||||
public Type DbType => typeof(int);
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(PrimaryAlbumType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var item = reader.Value;
|
||||
return (PrimaryAlbumType)Convert.ToInt32(item);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(ToDB(value));
|
||||
writer.WriteNumberValue((int)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,22 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using NzbDrone.Common.Reflection;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class ProviderSettingConverter : EmbeddedDocumentConverter
|
||||
public class ProviderSettingConverter : EmbeddedDocumentConverter<IProviderConfig>
|
||||
{
|
||||
public override object FromDB(ConverterContext context)
|
||||
public override IProviderConfig Parse(object value)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return NullConfig.Instance;
|
||||
}
|
||||
// We can't deserialize based on another column, happens in ProviderRepository instead
|
||||
return null;
|
||||
}
|
||||
|
||||
var stringValue = (string)context.DbValue;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(stringValue))
|
||||
{
|
||||
return NullConfig.Instance;
|
||||
}
|
||||
|
||||
var ordinal = context.DataRecord.GetOrdinal("ConfigContract");
|
||||
var contract = context.DataRecord.GetString(ordinal);
|
||||
|
||||
var impType = typeof(IProviderConfig).Assembly.FindTypeByName(contract);
|
||||
|
||||
if (impType == null)
|
||||
{
|
||||
throw new ConfigContractNotFoundException(contract);
|
||||
}
|
||||
|
||||
return Json.Deserialize(stringValue, impType);
|
||||
public override void SetValue(IDbDataParameter parameter, IProviderConfig value)
|
||||
{
|
||||
// Cast to object to get all properties written out
|
||||
// https://github.com/dotnet/corefx/issues/38650
|
||||
parameter.Value = JsonSerializer.Serialize((object)value, SerializerSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,36 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class QualityIntConverter : JsonConverter, IConverter
|
||||
public class QualityIntConverter : JsonConverter<Quality>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override Quality Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return Quality.Unknown;
|
||||
}
|
||||
|
||||
var val = Convert.ToInt32(context.DbValue);
|
||||
|
||||
return (Quality)val;
|
||||
var item = reader.GetInt32();
|
||||
return (Quality)item;
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override void Write(Utf8JsonWriter writer, Quality value, JsonSerializerOptions options)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
writer.WriteNumberValue((int)value);
|
||||
}
|
||||
}
|
||||
|
||||
public class DapperQualityIntConverter : SqlMapper.TypeHandler<Quality>
|
||||
{
|
||||
public override void SetValue(IDbDataParameter parameter, Quality value)
|
||||
{
|
||||
parameter.Value = value == null ? 0 : (int)value;
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
public override Quality Parse(object value)
|
||||
{
|
||||
if (clrValue == DBNull.Value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (clrValue as Quality == null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempted to save a quality that isn't really a quality");
|
||||
}
|
||||
|
||||
var quality = clrValue as Quality;
|
||||
return (int)quality;
|
||||
}
|
||||
|
||||
public Type DbType => typeof(int);
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(Quality);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var item = reader.Value;
|
||||
return (Quality)Convert.ToInt32(item);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(ToDB(value));
|
||||
return (Quality)Convert.ToInt32(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,21 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class ReleaseStatusIntConverter : JsonConverter, IConverter
|
||||
public class ReleaseStatusIntConverter : JsonConverter<ReleaseStatus>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override ReleaseStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return ReleaseStatus.Official;
|
||||
}
|
||||
|
||||
var val = Convert.ToInt32(context.DbValue);
|
||||
|
||||
return (ReleaseStatus)val;
|
||||
var item = reader.GetInt32();
|
||||
return (ReleaseStatus)item;
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override void Write(Utf8JsonWriter writer, ReleaseStatus value, JsonSerializerOptions options)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue == DBNull.Value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (clrValue as ReleaseStatus == null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempted to save a release status that isn't really a release status");
|
||||
}
|
||||
|
||||
var releaseStatus = (ReleaseStatus)clrValue;
|
||||
return (int)releaseStatus;
|
||||
}
|
||||
|
||||
public Type DbType => typeof(int);
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(ReleaseStatus);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var item = reader.Value;
|
||||
return (ReleaseStatus)Convert.ToInt32(item);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(ToDB(value));
|
||||
writer.WriteNumberValue((int)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,21 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class SecondaryAlbumTypeIntConverter : JsonConverter, IConverter
|
||||
public class SecondaryAlbumTypeIntConverter : JsonConverter<SecondaryAlbumType>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override SecondaryAlbumType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return SecondaryAlbumType.Studio;
|
||||
}
|
||||
|
||||
var val = Convert.ToInt32(context.DbValue);
|
||||
|
||||
return (SecondaryAlbumType)val;
|
||||
var item = reader.GetInt32();
|
||||
return (SecondaryAlbumType)item;
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override void Write(Utf8JsonWriter writer, SecondaryAlbumType value, JsonSerializerOptions options)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue == DBNull.Value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (clrValue as SecondaryAlbumType == null)
|
||||
{
|
||||
throw new InvalidOperationException("Attempted to save an album type that isn't really an album type");
|
||||
}
|
||||
|
||||
var secType = (SecondaryAlbumType)clrValue;
|
||||
return (int)secType;
|
||||
}
|
||||
|
||||
public Type DbType => typeof(int);
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(SecondaryAlbumType);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader,
|
||||
Type objectType,
|
||||
object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
var item = reader.Value;
|
||||
return (SecondaryAlbumType)Convert.ToInt32(item);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(ToDB(value));
|
||||
writer.WriteNumberValue((int)value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,19 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class TimeSpanConverter : IConverter
|
||||
public class TimeSpanConverter : JsonConverter<TimeSpan>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (context.DbValue == DBNull.Value)
|
||||
{
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
if (context.DbValue is TimeSpan)
|
||||
{
|
||||
return context.DbValue;
|
||||
}
|
||||
|
||||
return TimeSpan.Parse(context.DbValue.ToString(), CultureInfo.InvariantCulture);
|
||||
return TimeSpan.Parse(reader.GetString());
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
{
|
||||
if (clrValue.ToString().IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ((TimeSpan)clrValue).ToString("c", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public Type DbType { get; private set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
using System;
|
||||
using Marr.Data.Converters;
|
||||
using Marr.Data.Mapping;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Dapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Converters
|
||||
{
|
||||
public class UtcConverter : IConverter
|
||||
public class DapperUtcConverter : SqlMapper.TypeHandler<DateTime>
|
||||
{
|
||||
public object FromDB(ConverterContext context)
|
||||
public override void SetValue(IDbDataParameter parameter, DateTime value)
|
||||
{
|
||||
return context.DbValue;
|
||||
parameter.Value = value.ToUniversalTime();
|
||||
}
|
||||
|
||||
public object FromDB(ColumnMap map, object dbValue)
|
||||
public override DateTime Parse(object value)
|
||||
{
|
||||
return FromDB(new ConverterContext { ColumnMap = map, DbValue = dbValue });
|
||||
return DateTime.SpecifyKind((DateTime)value, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
|
||||
public class UtcConverter : JsonConverter<DateTime>
|
||||
{
|
||||
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return DateTime.Parse(reader.GetString());
|
||||
}
|
||||
|
||||
public object ToDB(object clrValue)
|
||||
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
|
||||
{
|
||||
if (clrValue == DBNull.Value)
|
||||
{
|
||||
return clrValue;
|
||||
}
|
||||
|
||||
var dateTime = (DateTime)clrValue;
|
||||
return dateTime.ToUniversalTime();
|
||||
writer.WriteStringValue(value.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"));
|
||||
}
|
||||
|
||||
public Type DbType => typeof(DateTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Marr.Data;
|
||||
using System;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
|
||||
@@ -7,7 +8,7 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public interface IDatabase
|
||||
{
|
||||
IDataMapper GetDataMapper();
|
||||
IDbConnection OpenConnection();
|
||||
Version Version { get; }
|
||||
int Migration { get; }
|
||||
void Vacuum();
|
||||
@@ -16,17 +17,17 @@ namespace NzbDrone.Core.Datastore
|
||||
public class Database : IDatabase
|
||||
{
|
||||
private readonly string _databaseName;
|
||||
private readonly Func<IDataMapper> _datamapperFactory;
|
||||
private readonly Func<IDbConnection> _datamapperFactory;
|
||||
|
||||
private readonly Logger _logger = NzbDroneLogger.GetLogger(typeof(Database));
|
||||
|
||||
public Database(string databaseName, Func<IDataMapper> datamapperFactory)
|
||||
public Database(string databaseName, Func<IDbConnection> datamapperFactory)
|
||||
{
|
||||
_databaseName = databaseName;
|
||||
_datamapperFactory = datamapperFactory;
|
||||
}
|
||||
|
||||
public IDataMapper GetDataMapper()
|
||||
public IDbConnection OpenConnection()
|
||||
{
|
||||
return _datamapperFactory();
|
||||
}
|
||||
@@ -35,8 +36,11 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
get
|
||||
{
|
||||
var version = _datamapperFactory().ExecuteScalar("SELECT sqlite_version()").ToString();
|
||||
return new Version(version);
|
||||
using (var db = _datamapperFactory())
|
||||
{
|
||||
var version = db.QueryFirstOrDefault<string>("SELECT sqlite_version()");
|
||||
return new Version(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +48,10 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
get
|
||||
{
|
||||
var migration = _datamapperFactory()
|
||||
.ExecuteScalar("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1").ToString();
|
||||
return Convert.ToInt32(migration);
|
||||
using (var db = _datamapperFactory())
|
||||
{
|
||||
return db.QueryFirstOrDefault<int>("SELECT version from VersionInfo ORDER BY version DESC LIMIT 1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +60,11 @@ namespace NzbDrone.Core.Datastore
|
||||
try
|
||||
{
|
||||
_logger.Info("Vacuuming {0} database", _databaseName);
|
||||
_datamapperFactory().ExecuteNonQuery("Vacuum;");
|
||||
using (var db = _datamapperFactory())
|
||||
{
|
||||
db.Execute("Vacuum;");
|
||||
}
|
||||
|
||||
_logger.Info("{0} database compressed", _databaseName);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Data.SQLite;
|
||||
using Marr.Data;
|
||||
using Marr.Data.Reflection;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -30,7 +28,6 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
InitializeEnvironment();
|
||||
|
||||
MapRepository.Instance.ReflectionStrategy = new SimpleReflectionStrategy();
|
||||
TableMapping.Map();
|
||||
}
|
||||
|
||||
@@ -99,12 +96,11 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
var db = new Database(migrationContext.MigrationType.ToString(), () =>
|
||||
{
|
||||
var dataMapper = new DataMapper(SQLiteFactory.Instance, connectionString)
|
||||
{
|
||||
SqlMode = SqlModes.Text,
|
||||
};
|
||||
var conn = SQLiteFactory.Instance.CreateConnection();
|
||||
conn.ConnectionString = connectionString;
|
||||
conn.Open();
|
||||
|
||||
return dataMapper;
|
||||
return conn;
|
||||
});
|
||||
|
||||
return db;
|
||||
@@ -123,7 +119,7 @@ namespace NzbDrone.Core.Datastore
|
||||
|
||||
if (OsInfo.IsOsx)
|
||||
{
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Lidarr/Lidarr/wiki/FAQ#i-use-lidarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Sonarr/Sonarr/wiki/FAQ#i-use-sonarr-on-a-mac-and-it-suddenly-stopped-working-what-happened", e, fileName);
|
||||
}
|
||||
|
||||
throw new CorruptDatabaseException("Database file: {0} is corrupt, restore from backup if available. See: https://github.com/Lidarr/Lidarr/wiki/FAQ#i-am-getting-an-error-database-disk-image-is-malformed", e, fileName);
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/* This class was copied from Mehfuz's LinqExtender project, which is available from github.
|
||||
* http://mehfuzh.github.com/LinqExtender/
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
/// <summary>
|
||||
/// Expression visitor
|
||||
/// </summary>
|
||||
public class ExpressionVisitor
|
||||
{
|
||||
/// <summary>
|
||||
/// Visits expression and delegates call to different to branch.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression Visit(Expression expression)
|
||||
{
|
||||
if (expression == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (expression.NodeType)
|
||||
{
|
||||
case ExpressionType.Lambda:
|
||||
return VisitLamda((LambdaExpression)expression);
|
||||
case ExpressionType.ArrayLength:
|
||||
case ExpressionType.Convert:
|
||||
case ExpressionType.ConvertChecked:
|
||||
case ExpressionType.Negate:
|
||||
case ExpressionType.UnaryPlus:
|
||||
case ExpressionType.NegateChecked:
|
||||
case ExpressionType.Not:
|
||||
case ExpressionType.Quote:
|
||||
case ExpressionType.TypeAs:
|
||||
return VisitUnary((UnaryExpression)expression);
|
||||
case ExpressionType.Add:
|
||||
case ExpressionType.AddChecked:
|
||||
case ExpressionType.And:
|
||||
case ExpressionType.AndAlso:
|
||||
case ExpressionType.ArrayIndex:
|
||||
case ExpressionType.Coalesce:
|
||||
case ExpressionType.Divide:
|
||||
case ExpressionType.Equal:
|
||||
case ExpressionType.ExclusiveOr:
|
||||
case ExpressionType.GreaterThan:
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
case ExpressionType.LeftShift:
|
||||
case ExpressionType.LessThan:
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
case ExpressionType.Modulo:
|
||||
case ExpressionType.Multiply:
|
||||
case ExpressionType.MultiplyChecked:
|
||||
case ExpressionType.NotEqual:
|
||||
case ExpressionType.Or:
|
||||
case ExpressionType.OrElse:
|
||||
case ExpressionType.Power:
|
||||
case ExpressionType.RightShift:
|
||||
case ExpressionType.Subtract:
|
||||
case ExpressionType.SubtractChecked:
|
||||
return VisitBinary((BinaryExpression)expression);
|
||||
case ExpressionType.Call:
|
||||
return VisitMethodCall((MethodCallExpression)expression);
|
||||
case ExpressionType.Constant:
|
||||
return VisitConstant((ConstantExpression)expression);
|
||||
case ExpressionType.MemberAccess:
|
||||
return VisitMemberAccess((MemberExpression)expression);
|
||||
case ExpressionType.Parameter:
|
||||
return VisitParameter((ParameterExpression)expression);
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException("expression", expression.NodeType.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the constance expression. To be implemented by user.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitConstant(ConstantExpression expression)
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the memeber access expression. To be implemented by user.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitMemberAccess(MemberExpression expression)
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the method call expression. To be implemented by user.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitMethodCall(MethodCallExpression expression)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the binary expression.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitBinary(BinaryExpression expression)
|
||||
{
|
||||
Visit(expression.Left);
|
||||
Visit(expression.Right);
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the unary expression.
|
||||
/// </summary>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitUnary(UnaryExpression expression)
|
||||
{
|
||||
Visit(expression.Operand);
|
||||
return expression;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Visits the lamda expression.
|
||||
/// </summary>
|
||||
/// <param name="lambdaExpression"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual Expression VisitLamda(LambdaExpression lambdaExpression)
|
||||
{
|
||||
Visit(lambdaExpression.Body);
|
||||
return lambdaExpression;
|
||||
}
|
||||
|
||||
private Expression VisitParameter(ParameterExpression expression)
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Dapper;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public static class SqlBuilderExtensions
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(SqlBuilderExtensions));
|
||||
|
||||
public static bool LogSql { get; set; }
|
||||
|
||||
public static SqlBuilder Select(this SqlBuilder builder, params Type[] types)
|
||||
{
|
||||
return builder.Select(types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", "));
|
||||
}
|
||||
|
||||
public static SqlBuilder SelectCount(this SqlBuilder builder)
|
||||
{
|
||||
return builder.Select("COUNT(*)");
|
||||
}
|
||||
|
||||
public static SqlBuilder SelectCountDistinct<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
|
||||
{
|
||||
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
|
||||
var propName = property.GetMemberName().Name;
|
||||
return builder.Select($"COUNT(DISTINCT \"{table}\".\"{propName}\")");
|
||||
}
|
||||
|
||||
public static SqlBuilder Where<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||
{
|
||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
||||
|
||||
return builder.Where(wb.ToString(), wb.Parameters);
|
||||
}
|
||||
|
||||
public static SqlBuilder OrWhere<TModel>(this SqlBuilder builder, Expression<Func<TModel, bool>> filter)
|
||||
{
|
||||
var wb = new WhereBuilder(filter, true, builder.Sequence);
|
||||
|
||||
return builder.OrWhere(wb.ToString(), wb.Parameters);
|
||||
}
|
||||
|
||||
public static SqlBuilder Join<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||
{
|
||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
||||
|
||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||
|
||||
return builder.Join($"{rightTable} ON {wb.ToString()}");
|
||||
}
|
||||
|
||||
public static SqlBuilder LeftJoin<TLeft, TRight>(this SqlBuilder builder, Expression<Func<TLeft, TRight, bool>> filter)
|
||||
{
|
||||
var wb = new WhereBuilder(filter, false, builder.Sequence);
|
||||
|
||||
var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight));
|
||||
|
||||
return builder.LeftJoin($"{rightTable} ON {wb.ToString()}");
|
||||
}
|
||||
|
||||
public static SqlBuilder GroupBy<TModel>(this SqlBuilder builder, Expression<Func<TModel, object>> property)
|
||||
{
|
||||
var table = TableMapping.Mapper.TableNameMapping(typeof(TModel));
|
||||
var propName = property.GetMemberName().Name;
|
||||
return builder.GroupBy($"{table}.{propName}");
|
||||
}
|
||||
|
||||
public static SqlBuilder.Template AddSelectTemplate(this SqlBuilder builder, Type type)
|
||||
{
|
||||
return builder.AddTemplate(TableMapping.Mapper.SelectTemplate(type)).LogQuery();
|
||||
}
|
||||
|
||||
public static SqlBuilder.Template AddPageCountTemplate(this SqlBuilder builder, Type type)
|
||||
{
|
||||
return builder.AddTemplate(TableMapping.Mapper.PageCountTemplate(type)).LogQuery();
|
||||
}
|
||||
|
||||
public static SqlBuilder.Template AddDeleteTemplate(this SqlBuilder builder, Type type)
|
||||
{
|
||||
return builder.AddTemplate(TableMapping.Mapper.DeleteTemplate(type)).LogQuery();
|
||||
}
|
||||
|
||||
public static SqlBuilder.Template LogQuery(this SqlBuilder.Template template)
|
||||
{
|
||||
if (LogSql)
|
||||
{
|
||||
LogQuery(template.RawSql, (DynamicParameters)template.Parameters);
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
public static void LogQuery(string sql, object parameters)
|
||||
{
|
||||
if (LogSql)
|
||||
{
|
||||
LogQuery(sql, new DynamicParameters(parameters));
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogQuery(string sql, DynamicParameters parameters)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==== Begin Query Trace ====");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("QUERY TEXT:");
|
||||
sb.AppendLine(sql);
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("PARAMETERS:");
|
||||
foreach (var p in parameters.ToDictionary())
|
||||
{
|
||||
var val = (p.Value is string) ? string.Format("\"{0}\"", p.Value) : p.Value;
|
||||
sb.AppendFormat("{0} = [{1}]", p.Key, val.ToJson() ?? "NULL").AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("==== End Query Trace ====");
|
||||
sb.AppendLine();
|
||||
|
||||
Logger.Trace(sb.ToString());
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> ToDictionary(this DynamicParameters dynamicParams)
|
||||
{
|
||||
var argsDictionary = new Dictionary<string, object>();
|
||||
var iLookup = (SqlMapper.IParameterLookup)dynamicParams;
|
||||
|
||||
foreach (var paramName in dynamicParams.ParameterNames)
|
||||
{
|
||||
var value = iLookup[paramName];
|
||||
argsDictionary.Add(paramName, value);
|
||||
}
|
||||
|
||||
var templates = dynamicParams.GetType().GetField("templates", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (templates != null)
|
||||
{
|
||||
if (templates.GetValue(dynamicParams) is List<object> list)
|
||||
{
|
||||
foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList()))
|
||||
{
|
||||
objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return argsDictionary;
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> GetPropertyValuePairs(this object obj)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var pairs = type.GetProperties().Where(x => x.IsMappableProperty())
|
||||
.DistinctBy(propertyInfo => propertyInfo.Name)
|
||||
.ToDictionary(
|
||||
propertyInfo => propertyInfo.Name,
|
||||
propertyInfo => propertyInfo.GetValue(obj, null));
|
||||
return pairs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,24 @@
|
||||
using System.Reflection;
|
||||
using Marr.Data;
|
||||
using Marr.Data.Mapping;
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Reflection;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Extensions
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public static class MappingExtensions
|
||||
{
|
||||
public static ColumnMapBuilder<T> MapResultSet<T>(this FluentMappings.MappingsFluentEntity<T> mapBuilder)
|
||||
where T : ResultSet, new()
|
||||
public static PropertyInfo GetMemberName<T, TChild>(this Expression<Func<T, TChild>> member)
|
||||
{
|
||||
return mapBuilder
|
||||
.Columns
|
||||
.AutoMapPropertiesWhere(IsMappableProperty);
|
||||
if (!(member.Body is MemberExpression memberExpression))
|
||||
{
|
||||
memberExpression = (member.Body as UnaryExpression).Operand as MemberExpression;
|
||||
}
|
||||
|
||||
return (PropertyInfo)memberExpression.Member;
|
||||
}
|
||||
|
||||
public static ColumnMapBuilder<T> RegisterDefinition<T>(this FluentMappings.MappingsFluentEntity<T> mapBuilder, string tableName = null)
|
||||
where T : ProviderDefinition, new()
|
||||
{
|
||||
return RegisterModel(mapBuilder, tableName).Ignore(c => c.ImplementationName);
|
||||
}
|
||||
|
||||
public static ColumnMapBuilder<T> RegisterModel<T>(this FluentMappings.MappingsFluentEntity<T> mapBuilder, string tableName = null)
|
||||
where T : ModelBase, new()
|
||||
{
|
||||
return mapBuilder.Table.MapTable(tableName)
|
||||
.Columns
|
||||
.AutoMapPropertiesWhere(IsMappableProperty)
|
||||
.PrefixAltNames(string.Format("{0}_", typeof(T).Name))
|
||||
.For(c => c.Id)
|
||||
.SetPrimaryKey()
|
||||
.SetReturnValue()
|
||||
.SetAutoIncrement();
|
||||
}
|
||||
|
||||
public static RelationshipBuilder<T> AutoMapChildModels<T>(this ColumnMapBuilder<T> mapBuilder)
|
||||
{
|
||||
return mapBuilder.Relationships.AutoMapPropertiesWhere(m =>
|
||||
m.MemberType == MemberTypes.Property &&
|
||||
typeof(ModelBase).IsAssignableFrom(((PropertyInfo)m).PropertyType));
|
||||
}
|
||||
|
||||
public static bool IsMappableProperty(MemberInfo memberInfo)
|
||||
public static bool IsMappableProperty(this MemberInfo memberInfo)
|
||||
{
|
||||
var propertyInfo = memberInfo as PropertyInfo;
|
||||
|
||||
@@ -56,7 +32,11 @@ namespace NzbDrone.Core.Datastore.Extensions
|
||||
return false;
|
||||
}
|
||||
|
||||
if (propertyInfo.PropertyType.IsSimpleType() || MapRepository.Instance.TypeConverters.ContainsKey(propertyInfo.PropertyType))
|
||||
// This is a bit of a hack but is the only way to see if a type has a handler set in Dapper
|
||||
#pragma warning disable 618
|
||||
SqlMapper.LookupDbType(propertyInfo.PropertyType, "", false, out var handler);
|
||||
#pragma warning restore 618
|
||||
if (propertyInfo.PropertyType.IsSimpleType() || handler != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Extensions
|
||||
{
|
||||
public static class PagingSpecExtensions
|
||||
{
|
||||
public static Expression<Func<TModel, object>> OrderByClause<TModel>(this PagingSpec<TModel> pagingSpec)
|
||||
{
|
||||
return CreateExpression<TModel>(pagingSpec.SortKey);
|
||||
}
|
||||
|
||||
public static int PagingOffset<TModel>(this PagingSpec<TModel> pagingSpec)
|
||||
{
|
||||
return (pagingSpec.Page - 1) * pagingSpec.PageSize;
|
||||
}
|
||||
|
||||
public static Marr.Data.QGen.SortDirection ToSortDirection<TModel>(this PagingSpec<TModel> pagingSpec)
|
||||
{
|
||||
if (pagingSpec.SortDirection == SortDirection.Descending)
|
||||
{
|
||||
return Marr.Data.QGen.SortDirection.Desc;
|
||||
}
|
||||
|
||||
return Marr.Data.QGen.SortDirection.Asc;
|
||||
}
|
||||
|
||||
private static Expression<Func<TModel, object>> CreateExpression<TModel>(string propertyName)
|
||||
{
|
||||
Type type = typeof(TModel);
|
||||
ParameterExpression parameterExpression = Expression.Parameter(type, "x");
|
||||
Expression expressionBody = parameterExpression;
|
||||
|
||||
var splitPropertyName = propertyName.Split('.').ToList();
|
||||
|
||||
foreach (var property in splitPropertyName)
|
||||
{
|
||||
expressionBody = Expression.Property(expressionBody, property);
|
||||
}
|
||||
|
||||
expressionBody = Expression.Convert(expressionBody, typeof(object));
|
||||
return Expression.Lambda<Func<TModel, object>>(expressionBody, parameterExpression);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Marr.Data;
|
||||
using Marr.Data.Mapping;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Extensions
|
||||
{
|
||||
public static class RelationshipExtensions
|
||||
{
|
||||
public static RelationshipBuilder<TParent> HasOne<TParent, TChild>(this RelationshipBuilder<TParent> relationshipBuilder, Expression<Func<TParent, LazyLoaded<TChild>>> portalExpression, Func<TParent, int> childIdSelector)
|
||||
where TParent : ModelBase
|
||||
where TChild : ModelBase
|
||||
{
|
||||
return relationshipBuilder.For(portalExpression.GetMemberName())
|
||||
.LazyLoad(
|
||||
condition: parent => childIdSelector(parent) > 0,
|
||||
query: (db, parent) =>
|
||||
{
|
||||
var id = childIdSelector(parent);
|
||||
return db.Query<TChild>().Where(c => c.Id == id).SingleOrDefault();
|
||||
});
|
||||
}
|
||||
|
||||
public static RelationshipBuilder<TParent> Relationship<TParent>(this ColumnMapBuilder<TParent> mapBuilder)
|
||||
{
|
||||
return mapBuilder.Relationships.MapProperties<TParent>();
|
||||
}
|
||||
|
||||
private static string GetMemberName<T, TMember>(this Expression<Func<T, TMember>> member)
|
||||
{
|
||||
var expression = member.Body as MemberExpression;
|
||||
|
||||
if (expression == null)
|
||||
{
|
||||
expression = (MemberExpression)((UnaryExpression)member.Body).Operand;
|
||||
}
|
||||
|
||||
return expression.Member.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public static class SqlMapperExtensions
|
||||
{
|
||||
public static IEnumerable<T> Query<T>(this IDatabase db, string sql, object param = null)
|
||||
{
|
||||
using (var conn = db.OpenConnection())
|
||||
{
|
||||
var items = SqlMapper.Query<T>(conn, sql, param);
|
||||
if (TableMapping.Mapper.LazyLoadList.TryGetValue(typeof(T), out var lazyProperties))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
ApplyLazyLoad(db, item, lazyProperties);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
|
||||
{
|
||||
TReturn mapWithLazy(TFirst first, TSecond second)
|
||||
{
|
||||
ApplyLazyLoad(db, first);
|
||||
ApplyLazyLoad(db, second);
|
||||
return map(first, second);
|
||||
}
|
||||
|
||||
IEnumerable<TReturn> result = null;
|
||||
using (var conn = db.OpenConnection())
|
||||
{
|
||||
result = SqlMapper.Query<TFirst, TSecond, TReturn>(conn, sql, mapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
|
||||
{
|
||||
TReturn mapWithLazy(TFirst first, TSecond second, TThird third)
|
||||
{
|
||||
ApplyLazyLoad(db, first);
|
||||
ApplyLazyLoad(db, second);
|
||||
ApplyLazyLoad(db, third);
|
||||
return map(first, second, third);
|
||||
}
|
||||
|
||||
IEnumerable<TReturn> result = null;
|
||||
using (var conn = db.OpenConnection())
|
||||
{
|
||||
result = SqlMapper.Query<TFirst, TSecond, TThird, TReturn>(conn, sql, mapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
|
||||
{
|
||||
TReturn mapWithLazy(TFirst first, TSecond second, TThird third, TFourth fourth)
|
||||
{
|
||||
ApplyLazyLoad(db, first);
|
||||
ApplyLazyLoad(db, second);
|
||||
ApplyLazyLoad(db, third);
|
||||
ApplyLazyLoad(db, fourth);
|
||||
return map(first, second, third, fourth);
|
||||
}
|
||||
|
||||
IEnumerable<TReturn> result = null;
|
||||
using (var conn = db.OpenConnection())
|
||||
{
|
||||
result = SqlMapper.Query<TFirst, TSecond, TThird, TFourth, TReturn>(conn, sql, mapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDatabase db, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
|
||||
{
|
||||
TReturn mapWithLazy(TFirst first, TSecond second, TThird third, TFourth fourth, TFifth fifth)
|
||||
{
|
||||
ApplyLazyLoad(db, first);
|
||||
ApplyLazyLoad(db, second);
|
||||
ApplyLazyLoad(db, third);
|
||||
ApplyLazyLoad(db, fourth);
|
||||
ApplyLazyLoad(db, fifth);
|
||||
return map(first, second, third, fourth, fifth);
|
||||
}
|
||||
|
||||
IEnumerable<TReturn> result = null;
|
||||
using (var conn = db.OpenConnection())
|
||||
{
|
||||
result = SqlMapper.Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(conn, sql, mapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> Query<T>(this IDatabase db, SqlBuilder builder)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var sql = builder.Select(type).AddSelectTemplate(type);
|
||||
|
||||
return db.Query<T>(sql.RawSql, sql.Parameters);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> QueryJoined<T, T2>(this IDatabase db, SqlBuilder builder, Func<T, T2, T> mapper)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var sql = builder.Select(type, typeof(T2)).AddSelectTemplate(type);
|
||||
|
||||
return db.Query(sql.RawSql, mapper, sql.Parameters);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> QueryJoined<T, T2, T3>(this IDatabase db, SqlBuilder builder, Func<T, T2, T3, T> mapper)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var sql = builder.Select(type, typeof(T2), typeof(T3)).AddSelectTemplate(type);
|
||||
|
||||
return db.Query(sql.RawSql, mapper, sql.Parameters);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> QueryJoined<T, T2, T3, T4>(this IDatabase db, SqlBuilder builder, Func<T, T2, T3, T4, T> mapper)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var sql = builder.Select(type, typeof(T2), typeof(T3), typeof(T4)).AddSelectTemplate(type);
|
||||
|
||||
return db.Query(sql.RawSql, mapper, sql.Parameters);
|
||||
}
|
||||
|
||||
public static IEnumerable<T> QueryJoined<T, T2, T3, T4, T5>(this IDatabase db, SqlBuilder builder, Func<T, T2, T3, T4, T5, T> mapper)
|
||||
{
|
||||
var type = typeof(T);
|
||||
var sql = builder.Select(type, typeof(T2), typeof(T3), typeof(T4), typeof(T5)).AddSelectTemplate(type);
|
||||
|
||||
return db.Query(sql.RawSql, mapper, sql.Parameters);
|
||||
}
|
||||
|
||||
private static void ApplyLazyLoad<TModel>(IDatabase db, TModel model)
|
||||
{
|
||||
if (TableMapping.Mapper.LazyLoadList.TryGetValue(typeof(TModel), out var lazyProperties))
|
||||
{
|
||||
ApplyLazyLoad(db, model, lazyProperties);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyLazyLoad<TModel>(IDatabase db, TModel model, List<LazyLoadedProperty> lazyProperties)
|
||||
{
|
||||
if (model == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var lazyProperty in lazyProperties)
|
||||
{
|
||||
var lazy = (ILazyLoaded)lazyProperty.LazyLoad.Clone();
|
||||
lazy.Prepare(db, model);
|
||||
lazyProperty.Property.SetValue(model, lazy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Marr.Data;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public class LazyList<T> : LazyLoaded<List<T>>
|
||||
{
|
||||
public LazyList()
|
||||
: this(new List<T>())
|
||||
{
|
||||
}
|
||||
|
||||
public LazyList(IEnumerable<T> items)
|
||||
: base(new List<T>(items))
|
||||
{
|
||||
}
|
||||
|
||||
public static implicit operator LazyList<T>(List<T> val)
|
||||
{
|
||||
return new LazyList<T>(val);
|
||||
}
|
||||
|
||||
public static implicit operator List<T>(LazyList<T> lazy)
|
||||
{
|
||||
return lazy.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public interface ILazyLoaded : ICloneable
|
||||
{
|
||||
bool IsLoaded { get; }
|
||||
void Prepare(IDatabase database, object parent);
|
||||
void LazyLoad();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows a field to be lazy loaded.
|
||||
/// </summary>
|
||||
/// <typeparam name="TChild"></typeparam>
|
||||
public class LazyLoaded<TChild> : ILazyLoaded
|
||||
{
|
||||
protected TChild _value;
|
||||
|
||||
public LazyLoaded()
|
||||
{
|
||||
}
|
||||
|
||||
public LazyLoaded(TChild val)
|
||||
{
|
||||
_value = val;
|
||||
IsLoaded = true;
|
||||
}
|
||||
|
||||
public TChild Value
|
||||
{
|
||||
get
|
||||
{
|
||||
LazyLoad();
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoaded { get; protected set; }
|
||||
|
||||
public static implicit operator LazyLoaded<TChild>(TChild val)
|
||||
{
|
||||
return new LazyLoaded<TChild>(val);
|
||||
}
|
||||
|
||||
public static implicit operator TChild(LazyLoaded<TChild> lazy)
|
||||
{
|
||||
return lazy.Value;
|
||||
}
|
||||
|
||||
public virtual void Prepare(IDatabase database, object parent)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void LazyLoad()
|
||||
{
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return MemberwiseClone();
|
||||
}
|
||||
|
||||
public bool ShouldSerializeValue()
|
||||
{
|
||||
return IsLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the lazy loading proxy.
|
||||
/// </summary>
|
||||
/// <typeparam name="TParent">The parent entity that contains the lazy loaded entity.</typeparam>
|
||||
/// <typeparam name="TChild">The child entity that is being lazy loaded.</typeparam>
|
||||
internal class LazyLoaded<TParent, TChild> : LazyLoaded<TChild>
|
||||
{
|
||||
private readonly Func<IDatabase, TParent, TChild> _query;
|
||||
private readonly Func<TParent, bool> _condition;
|
||||
|
||||
private IDatabase _database;
|
||||
private TParent _parent;
|
||||
|
||||
public LazyLoaded(TChild val)
|
||||
: base(val)
|
||||
{
|
||||
_value = val;
|
||||
IsLoaded = true;
|
||||
}
|
||||
|
||||
internal LazyLoaded(Func<IDatabase, TParent, TChild> query, Func<TParent, bool> condition = null)
|
||||
{
|
||||
_query = query;
|
||||
_condition = condition;
|
||||
}
|
||||
|
||||
public static implicit operator LazyLoaded<TParent, TChild>(TChild val)
|
||||
{
|
||||
return new LazyLoaded<TParent, TChild>(val);
|
||||
}
|
||||
|
||||
public static implicit operator TChild(LazyLoaded<TParent, TChild> lazy)
|
||||
{
|
||||
return lazy.Value;
|
||||
}
|
||||
|
||||
public override void Prepare(IDatabase database, object parent)
|
||||
{
|
||||
_database = database;
|
||||
_parent = (TParent)parent;
|
||||
}
|
||||
|
||||
public override void LazyLoad()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
{
|
||||
if (_condition != null && _condition(_parent))
|
||||
{
|
||||
_value = _query(_database, _parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_value = default;
|
||||
}
|
||||
|
||||
IsLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Marr.Data;
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
@@ -16,9 +16,9 @@ namespace NzbDrone.Core.Datastore
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public IDataMapper GetDataMapper()
|
||||
public IDbConnection OpenConnection()
|
||||
{
|
||||
return _database.GetDataMapper();
|
||||
return _database.OpenConnection();
|
||||
}
|
||||
|
||||
public Version Version => _database.Version;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Marr.Data;
|
||||
using System;
|
||||
using System.Data;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
@@ -16,9 +16,9 @@ namespace NzbDrone.Core.Datastore
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public IDataMapper GetDataMapper()
|
||||
public IDbConnection OpenConnection()
|
||||
{
|
||||
return _database.GetDataMapper();
|
||||
return _database.OpenConnection();
|
||||
}
|
||||
|
||||
public Version Version => _database.Version;
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public class SqlBuilder
|
||||
{
|
||||
private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();
|
||||
|
||||
public int Sequence { get; private set; }
|
||||
|
||||
public Template AddTemplate(string sql, dynamic parameters = null) =>
|
||||
new Template(this, sql, parameters);
|
||||
|
||||
public SqlBuilder Intersect(string sql, dynamic parameters = null) =>
|
||||
AddClause("intersect", sql, parameters, "\nINTERSECT\n ", "\n ", "\n", false);
|
||||
|
||||
public SqlBuilder InnerJoin(string sql, dynamic parameters = null) =>
|
||||
AddClause("innerjoin", sql, parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n", false);
|
||||
|
||||
public SqlBuilder LeftJoin(string sql, dynamic parameters = null) =>
|
||||
AddClause("leftjoin", sql, parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n", false);
|
||||
|
||||
public SqlBuilder RightJoin(string sql, dynamic parameters = null) =>
|
||||
AddClause("rightjoin", sql, parameters, "\nRIGHT JOIN ", "\nRIGHT JOIN ", "\n", false);
|
||||
|
||||
public SqlBuilder Where(string sql, dynamic parameters = null) =>
|
||||
AddClause("where", sql, parameters, " AND ", "WHERE ", "\n", false);
|
||||
|
||||
public SqlBuilder OrWhere(string sql, dynamic parameters = null) =>
|
||||
AddClause("where", sql, parameters, " OR ", "WHERE ", "\n", true);
|
||||
|
||||
public SqlBuilder OrderBy(string sql, dynamic parameters = null) =>
|
||||
AddClause("orderby", sql, parameters, " , ", "ORDER BY ", "\n", false);
|
||||
|
||||
public SqlBuilder Select(string sql, dynamic parameters = null) =>
|
||||
AddClause("select", sql, parameters, " , ", "", "\n", false);
|
||||
|
||||
public SqlBuilder AddParameters(dynamic parameters) =>
|
||||
AddClause("--parameters", "", parameters, "", "", "", false);
|
||||
|
||||
public SqlBuilder Join(string sql, dynamic parameters = null) =>
|
||||
AddClause("join", sql, parameters, "\nJOIN ", "\nJOIN ", "\n", false);
|
||||
|
||||
public SqlBuilder GroupBy(string sql, dynamic parameters = null) =>
|
||||
AddClause("groupby", sql, parameters, " , ", "\nGROUP BY ", "\n", false);
|
||||
|
||||
public SqlBuilder Having(string sql, dynamic parameters = null) =>
|
||||
AddClause("having", sql, parameters, "\nAND ", "HAVING ", "\n", false);
|
||||
|
||||
protected SqlBuilder AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false)
|
||||
{
|
||||
if (!_data.TryGetValue(name, out var clauses))
|
||||
{
|
||||
clauses = new Clauses(joiner, prefix, postfix);
|
||||
_data[name] = clauses;
|
||||
}
|
||||
|
||||
clauses.Add(new Clause { Sql = sql, Parameters = parameters, IsInclusive = isInclusive });
|
||||
Sequence++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public class Template
|
||||
{
|
||||
private static readonly Regex _regex = new Regex(@"\/\*\*.+?\*\*\/", RegexOptions.Compiled | RegexOptions.Multiline);
|
||||
|
||||
private readonly string _sql;
|
||||
private readonly SqlBuilder _builder;
|
||||
private readonly object _initParams;
|
||||
|
||||
private int _dataSeq = -1; // Unresolved
|
||||
private string _rawSql;
|
||||
private object _parameters;
|
||||
|
||||
public Template(SqlBuilder builder, string sql, dynamic parameters)
|
||||
{
|
||||
_initParams = parameters;
|
||||
_sql = sql;
|
||||
_builder = builder;
|
||||
}
|
||||
|
||||
public string RawSql
|
||||
{
|
||||
get
|
||||
{
|
||||
ResolveSql();
|
||||
return _rawSql;
|
||||
}
|
||||
}
|
||||
|
||||
public object Parameters
|
||||
{
|
||||
get
|
||||
{
|
||||
ResolveSql();
|
||||
return _parameters;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveSql()
|
||||
{
|
||||
if (_dataSeq != _builder.Sequence)
|
||||
{
|
||||
var p = new DynamicParameters(_initParams);
|
||||
|
||||
_rawSql = _sql;
|
||||
|
||||
foreach (var pair in _builder._data)
|
||||
{
|
||||
_rawSql = _rawSql.Replace("/**" + pair.Key + "**/", pair.Value.ResolveClauses(p));
|
||||
}
|
||||
|
||||
_parameters = p;
|
||||
|
||||
// replace all that is left with empty
|
||||
_rawSql = _regex.Replace(_rawSql, "");
|
||||
|
||||
_dataSeq = _builder.Sequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Clause
|
||||
{
|
||||
public string Sql { get; set; }
|
||||
public object Parameters { get; set; }
|
||||
public bool IsInclusive { get; set; }
|
||||
}
|
||||
|
||||
private class Clauses : List<Clause>
|
||||
{
|
||||
private readonly string _joiner;
|
||||
private readonly string _prefix;
|
||||
private readonly string _postfix;
|
||||
|
||||
public Clauses(string joiner, string prefix = "", string postfix = "")
|
||||
{
|
||||
_joiner = joiner;
|
||||
_prefix = prefix;
|
||||
_postfix = postfix;
|
||||
}
|
||||
|
||||
public string ResolveClauses(DynamicParameters p)
|
||||
{
|
||||
foreach (var item in this)
|
||||
{
|
||||
p.AddDynamicParams(item.Parameters);
|
||||
}
|
||||
|
||||
return this.Any(a => a.IsInclusive)
|
||||
? _prefix +
|
||||
string.Join(_joiner,
|
||||
this.Where(a => !a.IsInclusive)
|
||||
.Select(c => c.Sql)
|
||||
.Union(new[]
|
||||
{
|
||||
" ( " +
|
||||
string.Join(" OR ", this.Where(a => a.IsInclusive).Select(c => c.Sql).ToArray()) +
|
||||
" ) "
|
||||
}).ToArray()) + _postfix
|
||||
: _prefix + string.Join(_joiner, this.Select(c => c.Sql).ToArray()) + _postfix;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Dapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public class TableMapper
|
||||
{
|
||||
public TableMapper()
|
||||
{
|
||||
IgnoreList = new Dictionary<Type, List<PropertyInfo>>();
|
||||
LazyLoadList = new Dictionary<Type, List<LazyLoadedProperty>>();
|
||||
TableMap = new Dictionary<Type, string>();
|
||||
}
|
||||
|
||||
public Dictionary<Type, List<PropertyInfo>> IgnoreList { get; set; }
|
||||
public Dictionary<Type, List<LazyLoadedProperty>> LazyLoadList { get; set; }
|
||||
public Dictionary<Type, string> TableMap { get; set; }
|
||||
|
||||
public ColumnMapper<TEntity> Entity<TEntity>(string tableName)
|
||||
where TEntity : ModelBase
|
||||
{
|
||||
var type = typeof(TEntity);
|
||||
TableMap.Add(type, tableName);
|
||||
|
||||
if (IgnoreList.TryGetValue(type, out var list))
|
||||
{
|
||||
return new ColumnMapper<TEntity>(list, LazyLoadList[type]);
|
||||
}
|
||||
|
||||
IgnoreList[type] = new List<PropertyInfo>();
|
||||
LazyLoadList[type] = new List<LazyLoadedProperty>();
|
||||
return new ColumnMapper<TEntity>(IgnoreList[type], LazyLoadList[type]);
|
||||
}
|
||||
|
||||
public List<PropertyInfo> ExcludeProperties(Type x)
|
||||
{
|
||||
return IgnoreList.ContainsKey(x) ? IgnoreList[x] : new List<PropertyInfo>();
|
||||
}
|
||||
|
||||
public string TableNameMapping(Type x)
|
||||
{
|
||||
return TableMap.ContainsKey(x) ? TableMap[x] : null;
|
||||
}
|
||||
|
||||
public string SelectTemplate(Type x)
|
||||
{
|
||||
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||
}
|
||||
|
||||
public string DeleteTemplate(Type x)
|
||||
{
|
||||
return $"DELETE FROM {TableMap[x]} /**where**/";
|
||||
}
|
||||
|
||||
public string PageCountTemplate(Type x)
|
||||
{
|
||||
return $"SELECT /**select**/ FROM {TableMap[x]} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/";
|
||||
}
|
||||
}
|
||||
|
||||
public class LazyLoadedProperty
|
||||
{
|
||||
public PropertyInfo Property { get; set; }
|
||||
public ILazyLoaded LazyLoad { get; set; }
|
||||
}
|
||||
|
||||
public class ColumnMapper<T>
|
||||
where T : ModelBase
|
||||
{
|
||||
private readonly List<PropertyInfo> _ignoreList;
|
||||
private readonly List<LazyLoadedProperty> _lazyLoadList;
|
||||
|
||||
public ColumnMapper(List<PropertyInfo> ignoreList, List<LazyLoadedProperty> lazyLoadList)
|
||||
{
|
||||
_ignoreList = ignoreList;
|
||||
_lazyLoadList = lazyLoadList;
|
||||
}
|
||||
|
||||
public ColumnMapper<T> AutoMapPropertiesWhere(Func<PropertyInfo, bool> predicate)
|
||||
{
|
||||
var properties = typeof(T).GetProperties();
|
||||
_ignoreList.AddRange(properties.Where(x => !predicate(x)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapper<T> RegisterModel()
|
||||
{
|
||||
return AutoMapPropertiesWhere(x => x.IsMappableProperty());
|
||||
}
|
||||
|
||||
public ColumnMapper<T> Ignore(Expression<Func<T, object>> property)
|
||||
{
|
||||
_ignoreList.Add(property.GetMemberName());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapper<T> LazyLoad<TChild>(Expression<Func<T, LazyLoaded<TChild>>> property, Func<IDatabase, T, TChild> query, Func<T, bool> condition)
|
||||
{
|
||||
var lazyLoad = new LazyLoaded<T, TChild>(query, condition);
|
||||
|
||||
var item = new LazyLoadedProperty
|
||||
{
|
||||
Property = property.GetMemberName(),
|
||||
LazyLoad = lazyLoad
|
||||
};
|
||||
|
||||
_lazyLoadList.Add(item);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColumnMapper<T> HasOne<TChild>(Expression<Func<T, LazyLoaded<TChild>>> portalExpression, Func<T, int> childIdSelector)
|
||||
where TChild : ModelBase
|
||||
{
|
||||
return LazyLoad(portalExpression,
|
||||
(db, parent) =>
|
||||
{
|
||||
var id = childIdSelector(parent);
|
||||
return db.Query<TChild>(new SqlBuilder().Where<TChild>(x => x.Id == id)).SingleOrDefault();
|
||||
},
|
||||
parent => childIdSelector(parent) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Marr.Data;
|
||||
using Marr.Data.Mapping;
|
||||
using Marr.Data.QGen;
|
||||
using NzbDrone.Common.Disk;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Reflection;
|
||||
using NzbDrone.Core.ArtistStats;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Blacklisting;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.CustomFilters;
|
||||
using NzbDrone.Core.Datastore.Converters;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.Extras.Lyrics;
|
||||
@@ -39,38 +34,47 @@ using NzbDrone.Core.RemotePathMappings;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Tags;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using static Dapper.SqlMapper;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public static class TableMapping
|
||||
{
|
||||
private static readonly FluentMappings Mapper = new FluentMappings(true);
|
||||
static TableMapping()
|
||||
{
|
||||
Mapper = new TableMapper();
|
||||
}
|
||||
|
||||
public static TableMapper Mapper { get; private set; }
|
||||
|
||||
public static void Map()
|
||||
{
|
||||
RegisterMappers();
|
||||
|
||||
Mapper.Entity<Config>().RegisterModel("Config");
|
||||
Mapper.Entity<Config>("Config").RegisterModel();
|
||||
|
||||
Mapper.Entity<RootFolder>().RegisterModel("RootFolders")
|
||||
Mapper.Entity<RootFolder>("RootFolders").RegisterModel()
|
||||
.Ignore(r => r.Accessible)
|
||||
.Ignore(r => r.FreeSpace)
|
||||
.Ignore(r => r.TotalSpace);
|
||||
|
||||
Mapper.Entity<ScheduledTask>().RegisterModel("ScheduledTasks");
|
||||
Mapper.Entity<ScheduledTask>("ScheduledTasks").RegisterModel();
|
||||
|
||||
Mapper.Entity<IndexerDefinition>().RegisterDefinition("Indexers")
|
||||
Mapper.Entity<IndexerDefinition>("Indexers").RegisterModel()
|
||||
.Ignore(x => x.ImplementationName)
|
||||
.Ignore(i => i.Enable)
|
||||
.Ignore(i => i.Protocol)
|
||||
.Ignore(i => i.SupportsRss)
|
||||
.Ignore(i => i.SupportsSearch)
|
||||
.Ignore(d => d.Tags);
|
||||
|
||||
Mapper.Entity<ImportListDefinition>().RegisterDefinition("ImportLists")
|
||||
Mapper.Entity<ImportListDefinition>("ImportLists").RegisterModel()
|
||||
.Ignore(x => x.ImplementationName)
|
||||
.Ignore(i => i.Enable)
|
||||
.Ignore(i => i.ListType);
|
||||
|
||||
Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications")
|
||||
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()
|
||||
.Ignore(x => x.ImplementationName)
|
||||
.Ignore(i => i.SupportsOnGrab)
|
||||
.Ignore(i => i.SupportsOnReleaseImport)
|
||||
.Ignore(i => i.SupportsOnUpgrade)
|
||||
@@ -80,118 +84,110 @@ namespace NzbDrone.Core.Datastore
|
||||
.Ignore(i => i.SupportsOnImportFailure)
|
||||
.Ignore(i => i.SupportsOnTrackRetag);
|
||||
|
||||
Mapper.Entity<MetadataDefinition>().RegisterDefinition("Metadata")
|
||||
Mapper.Entity<MetadataDefinition>("Metadata").RegisterModel()
|
||||
.Ignore(x => x.ImplementationName)
|
||||
.Ignore(d => d.Tags);
|
||||
|
||||
Mapper.Entity<DownloadClientDefinition>().RegisterDefinition("DownloadClients")
|
||||
Mapper.Entity<DownloadClientDefinition>("DownloadClients").RegisterModel()
|
||||
.Ignore(x => x.ImplementationName)
|
||||
.Ignore(d => d.Protocol)
|
||||
.Ignore(d => d.Tags);
|
||||
|
||||
Mapper.Entity<History.History>().RegisterModel("History")
|
||||
.AutoMapChildModels();
|
||||
Mapper.Entity<History.History>("History").RegisterModel();
|
||||
|
||||
Mapper.Entity<Artist>().RegisterModel("Artists")
|
||||
Mapper.Entity<Artist>("Artists")
|
||||
.Ignore(s => s.RootFolderPath)
|
||||
.Ignore(s => s.Name)
|
||||
.Ignore(s => s.ForeignArtistId)
|
||||
.Relationship()
|
||||
.HasOne(a => a.Metadata, a => a.ArtistMetadataId)
|
||||
.HasOne(a => a.QualityProfile, a => a.QualityProfileId)
|
||||
.HasOne(s => s.MetadataProfile, s => s.MetadataProfileId)
|
||||
.For(a => a.Albums)
|
||||
.LazyLoad(condition: a => a.Id > 0, query: (db, a) => db.Query<Album>().Where(rg => rg.ArtistMetadataId == a.Id).ToList());
|
||||
.LazyLoad(a => a.Albums, (db, a) => db.Query<Album>(new SqlBuilder().Where<Album>(rg => rg.ArtistMetadataId == a.Id)).ToList(), a => a.Id > 0);
|
||||
|
||||
Mapper.Entity<ArtistMetadata>().RegisterModel("ArtistMetadata");
|
||||
Mapper.Entity<ArtistMetadata>("ArtistMetadata").RegisterModel();
|
||||
|
||||
Mapper.Entity<Album>().RegisterModel("Albums")
|
||||
.Ignore(r => r.ArtistId)
|
||||
.Relationship()
|
||||
Mapper.Entity<Album>("Albums").RegisterModel()
|
||||
.Ignore(x => x.ArtistId)
|
||||
.HasOne(r => r.ArtistMetadata, r => r.ArtistMetadataId)
|
||||
.For(rg => rg.AlbumReleases)
|
||||
.LazyLoad(condition: rg => rg.Id > 0, query: (db, rg) => db.Query<AlbumRelease>().Where(r => r.AlbumId == rg.Id).ToList())
|
||||
.For(rg => rg.Artist)
|
||||
.LazyLoad(condition: rg => rg.ArtistMetadataId > 0,
|
||||
query: (db, rg) => db.Query<Artist>()
|
||||
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Where(a => a.ArtistMetadataId == rg.ArtistMetadataId).SingleOrDefault());
|
||||
.LazyLoad(a => a.AlbumReleases, (db, album) => db.Query<AlbumRelease>(new SqlBuilder().Where<AlbumRelease>(r => r.AlbumId == album.Id)).ToList(), a => a.Id > 0)
|
||||
.LazyLoad(a => a.Artist,
|
||||
(db, album) => ArtistRepository.Query(db,
|
||||
new SqlBuilder()
|
||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Where<Artist>(a => a.ArtistMetadataId == album.ArtistMetadataId)).SingleOrDefault(),
|
||||
a => a.ArtistMetadataId > 0);
|
||||
|
||||
Mapper.Entity<AlbumRelease>().RegisterModel("AlbumReleases")
|
||||
.Relationship()
|
||||
Mapper.Entity<AlbumRelease>("AlbumReleases").RegisterModel()
|
||||
.HasOne(r => r.Album, r => r.AlbumId)
|
||||
.For(r => r.Tracks)
|
||||
.LazyLoad(condition: r => r.Id > 0, query: (db, r) => db.Query<Track>().Where(t => t.AlbumReleaseId == r.Id).ToList());
|
||||
.LazyLoad(x => x.Tracks, (db, release) => db.Query<Track>(new SqlBuilder().Where<Track>(t => t.AlbumReleaseId == release.Id)).ToList(), r => r.Id > 0);
|
||||
|
||||
Mapper.Entity<Track>().RegisterModel("Tracks")
|
||||
Mapper.Entity<Track>("Tracks").RegisterModel()
|
||||
.Ignore(t => t.HasFile)
|
||||
.Ignore(t => t.AlbumId)
|
||||
.Ignore(t => t.Album)
|
||||
.Relationship()
|
||||
.HasOne(track => track.AlbumRelease, track => track.AlbumReleaseId)
|
||||
.HasOne(track => track.ArtistMetadata, track => track.ArtistMetadataId)
|
||||
.For(track => track.TrackFile)
|
||||
.LazyLoad(condition: track => track.TrackFileId > 0,
|
||||
query: (db, track) => db.Query<TrackFile>()
|
||||
.Join<TrackFile, Track>(JoinType.Inner, t => t.Tracks, (t, x) => t.Id == x.TrackFileId)
|
||||
.Join<TrackFile, Album>(JoinType.Inner, t => t.Album, (t, a) => t.AlbumId == a.Id)
|
||||
.Join<TrackFile, Artist>(JoinType.Inner, t => t.Artist, (t, a) => t.Album.Value.ArtistMetadataId == a.ArtistMetadataId)
|
||||
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Where(t => t.Id == track.TrackFileId)
|
||||
.SingleOrDefault())
|
||||
.For(t => t.Artist)
|
||||
.LazyLoad(condition: t => t.AlbumReleaseId > 0, query: (db, t) => db.Query<Artist>()
|
||||
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Join<Artist, Album>(JoinType.Inner, a => a.Albums, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Join<Album, AlbumRelease>(JoinType.Inner, a => a.AlbumReleases, (l, r) => l.Id == r.AlbumId)
|
||||
.Where<AlbumRelease>(r => r.Id == t.AlbumReleaseId)
|
||||
.SingleOrDefault());
|
||||
.LazyLoad(t => t.TrackFile,
|
||||
(db, track) => MediaFileRepository.Query(db,
|
||||
new SqlBuilder()
|
||||
.Join<TrackFile, Track>((l, r) => l.Id == r.TrackFileId)
|
||||
.Join<TrackFile, Album>((l, r) => l.AlbumId == r.Id)
|
||||
.Join<Album, Artist>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Join<Artist, ArtistMetadata>((l, r) => l.ArtistMetadataId == r.Id)
|
||||
.Where<TrackFile>(t => t.Id == track.TrackFileId)).SingleOrDefault(),
|
||||
t => t.TrackFileId > 0)
|
||||
.LazyLoad(x => x.Artist,
|
||||
(db, t) => ArtistRepository.Query(db,
|
||||
new SqlBuilder()
|
||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Join<Album, AlbumRelease>((l, r) => l.Id == r.AlbumId)
|
||||
.Where<AlbumRelease>(r => r.Id == t.AlbumReleaseId)).SingleOrDefault(),
|
||||
t => t.Id > 0);
|
||||
|
||||
Mapper.Entity<TrackFile>().RegisterModel("TrackFiles")
|
||||
.Relationship()
|
||||
Mapper.Entity<TrackFile>("TrackFiles").RegisterModel()
|
||||
.HasOne(f => f.Album, f => f.AlbumId)
|
||||
.For(f => f.Tracks)
|
||||
.LazyLoad(condition: f => f.Id > 0, query: (db, f) => db.Query<Track>()
|
||||
.Where(x => x.TrackFileId == f.Id)
|
||||
.ToList())
|
||||
.For(t => t.Artist)
|
||||
.LazyLoad(condition: f => f.Id > 0, query: (db, f) => db.Query<Artist>()
|
||||
.Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Join<Artist, Album>(JoinType.Inner, a => a.Albums, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Where<Album>(r => r.Id == f.AlbumId)
|
||||
.SingleOrDefault());
|
||||
.LazyLoad(x => x.Tracks, (db, file) => db.Query<Track>(new SqlBuilder().Where<Track>(t => t.TrackFileId == file.Id)).ToList(), x => x.Id > 0)
|
||||
.LazyLoad(x => x.Artist,
|
||||
(db, f) => ArtistRepository.Query(db,
|
||||
new SqlBuilder()
|
||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Join<Artist, Album>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Where<Album>(a => a.Id == f.AlbumId)).SingleOrDefault(),
|
||||
t => t.Id > 0);
|
||||
|
||||
Mapper.Entity<QualityDefinition>().RegisterModel("QualityDefinitions")
|
||||
Mapper.Entity<QualityDefinition>("QualityDefinitions").RegisterModel()
|
||||
.Ignore(d => d.GroupName)
|
||||
.Ignore(d => d.GroupWeight)
|
||||
.Ignore(d => d.Weight);
|
||||
|
||||
Mapper.Entity<QualityProfile>().RegisterModel("QualityProfiles");
|
||||
Mapper.Entity<MetadataProfile>().RegisterModel("MetadataProfiles");
|
||||
Mapper.Entity<Log>().RegisterModel("Logs");
|
||||
Mapper.Entity<NamingConfig>().RegisterModel("NamingConfig");
|
||||
Mapper.Entity<AlbumStatistics>().MapResultSet();
|
||||
Mapper.Entity<Blacklist>().RegisterModel("Blacklist");
|
||||
Mapper.Entity<MetadataFile>().RegisterModel("MetadataFiles");
|
||||
Mapper.Entity<LyricFile>().RegisterModel("LyricFiles");
|
||||
Mapper.Entity<OtherExtraFile>().RegisterModel("ExtraFiles");
|
||||
Mapper.Entity<QualityProfile>("QualityProfiles").RegisterModel();
|
||||
Mapper.Entity<MetadataProfile>("MetadataProfiles").RegisterModel();
|
||||
Mapper.Entity<Log>("Logs").RegisterModel();
|
||||
Mapper.Entity<NamingConfig>("NamingConfig").RegisterModel();
|
||||
|
||||
Mapper.Entity<PendingRelease>().RegisterModel("PendingReleases")
|
||||
Mapper.Entity<Blacklist>("Blacklist").RegisterModel();
|
||||
Mapper.Entity<MetadataFile>("MetadataFiles").RegisterModel();
|
||||
Mapper.Entity<LyricFile>("LyricFiles").RegisterModel();
|
||||
Mapper.Entity<OtherExtraFile>("ExtraFiles").RegisterModel();
|
||||
|
||||
Mapper.Entity<PendingRelease>("PendingReleases").RegisterModel()
|
||||
.Ignore(e => e.RemoteAlbum);
|
||||
|
||||
Mapper.Entity<RemotePathMapping>().RegisterModel("RemotePathMappings");
|
||||
Mapper.Entity<Tag>().RegisterModel("Tags");
|
||||
Mapper.Entity<ReleaseProfile>().RegisterModel("ReleaseProfiles");
|
||||
Mapper.Entity<RemotePathMapping>("RemotePathMappings").RegisterModel();
|
||||
Mapper.Entity<Tag>("Tags").RegisterModel();
|
||||
Mapper.Entity<ReleaseProfile>("ReleaseProfiles").RegisterModel();
|
||||
|
||||
Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles");
|
||||
Mapper.Entity<User>().RegisterModel("Users");
|
||||
Mapper.Entity<CommandModel>().RegisterModel("Commands")
|
||||
.Ignore(c => c.Message);
|
||||
Mapper.Entity<DelayProfile>("DelayProfiles").RegisterModel();
|
||||
Mapper.Entity<User>("Users").RegisterModel();
|
||||
Mapper.Entity<CommandModel>("Commands").RegisterModel()
|
||||
.Ignore(c => c.Message);
|
||||
|
||||
Mapper.Entity<IndexerStatus>().RegisterModel("IndexerStatus");
|
||||
Mapper.Entity<DownloadClientStatus>().RegisterModel("DownloadClientStatus");
|
||||
Mapper.Entity<ImportListStatus>().RegisterModel("ImportListStatus");
|
||||
Mapper.Entity<IndexerStatus>("IndexerStatus").RegisterModel();
|
||||
Mapper.Entity<DownloadClientStatus>("DownloadClientStatus").RegisterModel();
|
||||
Mapper.Entity<ImportListStatus>("ImportListStatus").RegisterModel();
|
||||
|
||||
Mapper.Entity<CustomFilter>().RegisterModel("CustomFilters");
|
||||
Mapper.Entity<ImportListExclusion>().RegisterModel("ImportListExclusions");
|
||||
Mapper.Entity<CustomFilter>("CustomFilters").RegisterModel();
|
||||
Mapper.Entity<ImportListExclusion>("ImportListExclusions").RegisterModel();
|
||||
}
|
||||
|
||||
private static void RegisterMappers()
|
||||
@@ -199,40 +195,40 @@ namespace NzbDrone.Core.Datastore
|
||||
RegisterEmbeddedConverter();
|
||||
RegisterProviderSettingConverter();
|
||||
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(int), new Int32Converter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(double), new DoubleConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(DateTime), new UtcConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(bool), new BooleanIntConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(Enum), new EnumIntConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(Quality), new QualityIntConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<QualityProfileQualityItem>), new EmbeddedDocumentConverter(new QualityIntConverter()));
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(QualityModel), new EmbeddedDocumentConverter(new QualityIntConverter()));
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(Dictionary<string, string>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<int>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<KeyValuePair<string, int>>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<string>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<ProfilePrimaryAlbumTypeItem>), new EmbeddedDocumentConverter(new PrimaryAlbumTypeIntConverter()));
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<ProfileSecondaryAlbumTypeItem>), new EmbeddedDocumentConverter(new SecondaryAlbumTypeIntConverter()));
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(List<ProfileReleaseStatusItem>), new EmbeddedDocumentConverter(new ReleaseStatusIntConverter()));
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedAlbumInfo), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(ParsedTrackInfo), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(ReleaseInfo), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<int>), new EmbeddedDocumentConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(Guid), new GuidConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(Command), new CommandConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(TimeSpan), new TimeSpanConverter());
|
||||
MapRepository.Instance.RegisterTypeConverter(typeof(TimeSpan?), new TimeSpanConverter());
|
||||
SqlMapper.RemoveTypeMap(typeof(DateTime));
|
||||
SqlMapper.AddTypeHandler(new DapperUtcConverter());
|
||||
SqlMapper.AddTypeHandler(new DapperQualityIntConverter());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<QualityProfileQualityItem>>(new QualityIntConverter()));
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<QualityModel>(new QualityIntConverter()));
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<Dictionary<string, string>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<IDictionary<string, string>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<int>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<KeyValuePair<string, int>>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<KeyValuePair<string, int>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<string>>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfilePrimaryAlbumTypeItem>>(new PrimaryAlbumTypeIntConverter()));
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileSecondaryAlbumTypeItem>>(new SecondaryAlbumTypeIntConverter()));
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<List<ProfileReleaseStatusItem>>(new ReleaseStatusIntConverter()));
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ParsedAlbumInfo>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ParsedTrackInfo>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<ReleaseInfo>());
|
||||
SqlMapper.AddTypeHandler(new EmbeddedDocumentConverter<HashSet<int>>());
|
||||
SqlMapper.AddTypeHandler(new OsPathConverter());
|
||||
SqlMapper.RemoveTypeMap(typeof(Guid));
|
||||
SqlMapper.RemoveTypeMap(typeof(Guid?));
|
||||
SqlMapper.AddTypeHandler(new GuidConverter());
|
||||
SqlMapper.AddTypeHandler(new CommandConverter());
|
||||
}
|
||||
|
||||
private static void RegisterProviderSettingConverter()
|
||||
{
|
||||
var settingTypes = typeof(IProviderConfig).Assembly.ImplementationsOf<IProviderConfig>();
|
||||
var settingTypes = typeof(IProviderConfig).Assembly.ImplementationsOf<IProviderConfig>()
|
||||
.Where(x => !x.ContainsGenericParameters);
|
||||
|
||||
var providerSettingConverter = new ProviderSettingConverter();
|
||||
foreach (var embeddedType in settingTypes)
|
||||
{
|
||||
MapRepository.Instance.RegisterTypeConverter(embeddedType, providerSettingConverter);
|
||||
SqlMapper.AddTypeHandler(embeddedType, providerSettingConverter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,16 +236,24 @@ namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
var embeddedTypes = typeof(IEmbeddedDocument).Assembly.ImplementationsOf<IEmbeddedDocument>();
|
||||
|
||||
var embeddedConvertor = new EmbeddedDocumentConverter();
|
||||
var embeddedConverterDefinition = typeof(EmbeddedDocumentConverter<>).GetGenericTypeDefinition();
|
||||
var genericListDefinition = typeof(List<>).GetGenericTypeDefinition();
|
||||
|
||||
foreach (var embeddedType in embeddedTypes)
|
||||
{
|
||||
var embeddedListType = genericListDefinition.MakeGenericType(embeddedType);
|
||||
|
||||
MapRepository.Instance.RegisterTypeConverter(embeddedType, embeddedConvertor);
|
||||
MapRepository.Instance.RegisterTypeConverter(embeddedListType, embeddedConvertor);
|
||||
RegisterEmbeddedConverter(embeddedType, embeddedConverterDefinition);
|
||||
RegisterEmbeddedConverter(embeddedListType, embeddedConverterDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RegisterEmbeddedConverter(Type embeddedType, Type embeddedConverterDefinition)
|
||||
{
|
||||
var embeddedConverterType = embeddedConverterDefinition.MakeGenericType(embeddedType);
|
||||
var converter = (ITypeHandler)Activator.CreateInstance(embeddedConverterType);
|
||||
|
||||
SqlMapper.AddTypeHandler(embeddedType, converter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Datastore
|
||||
{
|
||||
public class WhereBuilder : ExpressionVisitor
|
||||
{
|
||||
protected StringBuilder _sb;
|
||||
|
||||
private const DbType EnumerableMultiParameter = (DbType)(-1);
|
||||
private readonly string _paramNamePrefix;
|
||||
private readonly bool _requireConcreteValue = false;
|
||||
private int _paramCount = 0;
|
||||
private bool _gotConcreteValue = false;
|
||||
|
||||
public WhereBuilder(Expression filter, bool requireConcreteValue, int seq)
|
||||
{
|
||||
_paramNamePrefix = string.Format("Clause{0}", seq + 1);
|
||||
_requireConcreteValue = requireConcreteValue;
|
||||
_sb = new StringBuilder();
|
||||
|
||||
Parameters = new DynamicParameters();
|
||||
|
||||
if (filter != null)
|
||||
{
|
||||
Visit(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public DynamicParameters Parameters { get; private set; }
|
||||
|
||||
private string AddParameter(object value, DbType? dbType = null)
|
||||
{
|
||||
_gotConcreteValue = true;
|
||||
_paramCount++;
|
||||
var name = _paramNamePrefix + "_P" + _paramCount;
|
||||
Parameters.Add(name, value, dbType);
|
||||
return '@' + name;
|
||||
}
|
||||
|
||||
protected override Expression VisitBinary(BinaryExpression expression)
|
||||
{
|
||||
_sb.Append("(");
|
||||
|
||||
Visit(expression.Left);
|
||||
|
||||
_sb.AppendFormat(" {0} ", Decode(expression));
|
||||
|
||||
Visit(expression.Right);
|
||||
|
||||
_sb.Append(")");
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
protected override Expression VisitMethodCall(MethodCallExpression expression)
|
||||
{
|
||||
var method = expression.Method.Name;
|
||||
|
||||
switch (expression.Method.Name)
|
||||
{
|
||||
case "Contains":
|
||||
ParseContainsExpression(expression);
|
||||
break;
|
||||
|
||||
case "StartsWith":
|
||||
ParseStartsWith(expression);
|
||||
break;
|
||||
|
||||
case "EndsWith":
|
||||
ParseEndsWith(expression);
|
||||
break;
|
||||
|
||||
default:
|
||||
var msg = string.Format("'{0}' expressions are not yet implemented in the where clause expression tree parser.", method);
|
||||
throw new NotImplementedException(msg);
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
protected override Expression VisitMemberAccess(MemberExpression expression)
|
||||
{
|
||||
var tableName = expression?.Expression?.Type != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null;
|
||||
var gotValue = TryGetRightValue(expression, out var value);
|
||||
|
||||
// Only use the SQL condition if the expression didn't resolve to an actual value
|
||||
if (tableName != null && !gotValue)
|
||||
{
|
||||
_sb.Append($"\"{tableName}\".\"{expression.Member.Name}\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
// string is IEnumerable<Char> but we don't want to pick up that case
|
||||
var type = value.GetType();
|
||||
var typeInfo = type.GetTypeInfo();
|
||||
var isEnumerable =
|
||||
type != typeof(string) && (
|
||||
typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) ||
|
||||
(typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>)));
|
||||
|
||||
var paramName = isEnumerable ? AddParameter(value, EnumerableMultiParameter) : AddParameter(value);
|
||||
_sb.Append(paramName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gotConcreteValue = true;
|
||||
_sb.Append("NULL");
|
||||
}
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
protected override Expression VisitConstant(ConstantExpression expression)
|
||||
{
|
||||
if (expression.Value != null)
|
||||
{
|
||||
var paramName = AddParameter(expression.Value);
|
||||
_sb.Append(paramName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gotConcreteValue = true;
|
||||
_sb.Append("NULL");
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
private bool TryGetConstantValue(Expression expression, out object result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (expression is ConstantExpression constExp)
|
||||
{
|
||||
result = constExp.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetPropertyValue(MemberExpression expression, out object result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
if (expression.Expression is MemberExpression nested)
|
||||
{
|
||||
// Value is passed in as a property on a parent entity
|
||||
var container = (nested.Expression as ConstantExpression)?.Value;
|
||||
|
||||
if (container == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var entity = GetFieldValue(container, nested.Member);
|
||||
result = GetFieldValue(entity, expression.Member);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetVariableValue(MemberExpression expression, out object result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
// Value is passed in as a variable
|
||||
if (expression.Expression is ConstantExpression nested)
|
||||
{
|
||||
result = GetFieldValue(nested.Value, expression.Member);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetRightValue(Expression expression, out object value)
|
||||
{
|
||||
value = null;
|
||||
|
||||
if (TryGetConstantValue(expression, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var memberExp = expression as MemberExpression;
|
||||
|
||||
if (TryGetPropertyValue(memberExp, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetVariableValue(memberExp, out value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private object GetFieldValue(object entity, MemberInfo member)
|
||||
{
|
||||
if (member.MemberType == MemberTypes.Field)
|
||||
{
|
||||
return (member as FieldInfo).GetValue(entity);
|
||||
}
|
||||
|
||||
if (member.MemberType == MemberTypes.Property)
|
||||
{
|
||||
return (member as PropertyInfo).GetValue(entity);
|
||||
}
|
||||
|
||||
throw new ArgumentException(string.Format("WhereBuilder could not get the value for {0}.{1}.", entity.GetType().Name, member.Name));
|
||||
}
|
||||
|
||||
private bool IsNullVariable(Expression expression)
|
||||
{
|
||||
if (expression.NodeType == ExpressionType.Constant &&
|
||||
TryGetConstantValue(expression, out var constResult) &&
|
||||
constResult == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (expression.NodeType == ExpressionType.MemberAccess &&
|
||||
expression is MemberExpression member &&
|
||||
((TryGetPropertyValue(member, out var result) && result == null) ||
|
||||
(TryGetVariableValue(member, out result) && result == null)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string Decode(BinaryExpression expression)
|
||||
{
|
||||
if (IsNullVariable(expression.Right))
|
||||
{
|
||||
switch (expression.NodeType)
|
||||
{
|
||||
case ExpressionType.Equal: return "IS";
|
||||
case ExpressionType.NotEqual: return "IS NOT";
|
||||
}
|
||||
}
|
||||
|
||||
switch (expression.NodeType)
|
||||
{
|
||||
case ExpressionType.AndAlso: return "AND";
|
||||
case ExpressionType.And: return "AND";
|
||||
case ExpressionType.Equal: return "=";
|
||||
case ExpressionType.GreaterThan: return ">";
|
||||
case ExpressionType.GreaterThanOrEqual: return ">=";
|
||||
case ExpressionType.LessThan: return "<";
|
||||
case ExpressionType.LessThanOrEqual: return "<=";
|
||||
case ExpressionType.NotEqual: return "<>";
|
||||
case ExpressionType.OrElse: return "OR";
|
||||
case ExpressionType.Or: return "OR";
|
||||
default: throw new NotSupportedException(string.Format("{0} statement is not supported", expression.NodeType.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseContainsExpression(MethodCallExpression expression)
|
||||
{
|
||||
var list = expression.Object;
|
||||
|
||||
if (list != null && (list.Type == typeof(string) || list.Type == typeof(List<string>)))
|
||||
{
|
||||
ParseStringContains(expression);
|
||||
return;
|
||||
}
|
||||
|
||||
ParseEnumerableContains(expression);
|
||||
}
|
||||
|
||||
private void ParseEnumerableContains(MethodCallExpression body)
|
||||
{
|
||||
// Fish out the list and the item to compare
|
||||
// It's in a different form for arrays and Lists
|
||||
var list = body.Object;
|
||||
Expression item;
|
||||
|
||||
if (list != null)
|
||||
{
|
||||
// Generic collection
|
||||
item = body.Arguments[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Static method
|
||||
// Must be Enumerable.Contains(source, item)
|
||||
if (body.Method.DeclaringType != typeof(Enumerable) || body.Arguments.Count != 2)
|
||||
{
|
||||
throw new NotSupportedException("Unexpected form of Enumerable.Contains");
|
||||
}
|
||||
|
||||
list = body.Arguments[0];
|
||||
item = body.Arguments[1];
|
||||
}
|
||||
|
||||
_sb.Append("(");
|
||||
|
||||
Visit(item);
|
||||
|
||||
_sb.Append(" IN ");
|
||||
|
||||
Visit(list);
|
||||
|
||||
_sb.Append(")");
|
||||
}
|
||||
|
||||
private void ParseStringContains(MethodCallExpression body)
|
||||
{
|
||||
_sb.Append("(");
|
||||
|
||||
Visit(body.Object);
|
||||
|
||||
_sb.Append(" LIKE '%' || ");
|
||||
|
||||
Visit(body.Arguments[0]);
|
||||
|
||||
_sb.Append(" || '%')");
|
||||
}
|
||||
|
||||
private void ParseStartsWith(MethodCallExpression body)
|
||||
{
|
||||
_sb.Append("(");
|
||||
|
||||
Visit(body.Object);
|
||||
|
||||
_sb.Append(" LIKE ");
|
||||
|
||||
Visit(body.Arguments[0]);
|
||||
|
||||
_sb.Append(" || '%')");
|
||||
}
|
||||
|
||||
private void ParseEndsWith(MethodCallExpression body)
|
||||
{
|
||||
_sb.Append("(");
|
||||
|
||||
Visit(body.Object);
|
||||
|
||||
_sb.Append(" LIKE '%' || ");
|
||||
|
||||
Visit(body.Arguments[0]);
|
||||
|
||||
_sb.Append(")");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sql = _sb.ToString();
|
||||
|
||||
if (_requireConcreteValue && !_gotConcreteValue)
|
||||
{
|
||||
var e = new InvalidOperationException("WhereBuilder requires a concrete condition");
|
||||
e.Data.Add("sql", sql);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,17 +20,17 @@ namespace NzbDrone.Core.Download.Pending
|
||||
|
||||
public void DeleteByArtistId(int artistId)
|
||||
{
|
||||
Delete(r => r.ArtistId == artistId);
|
||||
Delete(artistId);
|
||||
}
|
||||
|
||||
public List<PendingRelease> AllByArtistId(int artistId)
|
||||
{
|
||||
return Query.Where(p => p.ArtistId == artistId);
|
||||
return Query(p => p.ArtistId == artistId);
|
||||
}
|
||||
|
||||
public List<PendingRelease> WithoutFallback()
|
||||
{
|
||||
return Query.Where(p => p.Reason != PendingReleaseReason.Fallback);
|
||||
return Query(p => p.Reason != PendingReleaseReason.Fallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace NzbDrone.Core.Extras
|
||||
foreach (var trackFile in trackFiles)
|
||||
{
|
||||
var localTrackFile = trackFile;
|
||||
trackFile.Tracks = new LazyList<Track>(tracks.Where(e => e.TrackFileId == localTrackFile.Id));
|
||||
trackFile.Tracks = tracks.Where(e => e.TrackFileId == localTrackFile.Id).ToList();
|
||||
}
|
||||
|
||||
return trackFiles;
|
||||
|
||||
@@ -42,22 +42,22 @@ namespace NzbDrone.Core.Extras.Files
|
||||
|
||||
public List<TExtraFile> GetFilesByArtist(int artistId)
|
||||
{
|
||||
return Query.Where(c => c.ArtistId == artistId);
|
||||
return Query(c => c.ArtistId == artistId);
|
||||
}
|
||||
|
||||
public List<TExtraFile> GetFilesByAlbum(int artistId, int albumId)
|
||||
{
|
||||
return Query.Where(c => c.ArtistId == artistId && c.AlbumId == albumId);
|
||||
return Query(c => c.ArtistId == artistId && c.AlbumId == albumId);
|
||||
}
|
||||
|
||||
public List<TExtraFile> GetFilesByTrackFile(int trackFileId)
|
||||
{
|
||||
return Query.Where(c => c.TrackFileId == trackFileId);
|
||||
return Query(c => c.TrackFileId == trackFileId);
|
||||
}
|
||||
|
||||
public TExtraFile FindByPath(string path)
|
||||
{
|
||||
return Query.Where(c => c.RelativePath == path).SingleOrDefault();
|
||||
return Query(c => c.RelativePath == path).SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
@@ -30,63 +30,72 @@ namespace NzbDrone.Core.History
|
||||
|
||||
public History MostRecentForAlbum(int albumId)
|
||||
{
|
||||
return Query.Where(h => h.AlbumId == albumId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
return Query(h => h.AlbumId == albumId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public History MostRecentForDownloadId(string downloadId)
|
||||
{
|
||||
return Query.Where(h => h.DownloadId == downloadId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
return Query(h => h.DownloadId == downloadId)
|
||||
.OrderByDescending(h => h.Date)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<History> FindByDownloadId(string downloadId)
|
||||
{
|
||||
return Query.Join<History, Artist>(JoinType.Left, h => h.Artist, (h, a) => h.ArtistId == a.Id)
|
||||
.Join<History, Album>(JoinType.Left, h => h.Album, (h, r) => h.AlbumId == r.Id)
|
||||
.Where(h => h.DownloadId == downloadId);
|
||||
return _database.QueryJoined<History, Artist, Album>(
|
||||
Builder()
|
||||
.Join<History, Artist>((h, a) => h.ArtistId == a.Id)
|
||||
.Join<History, Album>((h, a) => h.AlbumId == a.Id)
|
||||
.Where<History>(h => h.DownloadId == downloadId),
|
||||
(history, artist, album) =>
|
||||
{
|
||||
history.Artist = artist;
|
||||
history.Album = album;
|
||||
return history;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public List<History> GetByArtist(int artistId, HistoryEventType? eventType)
|
||||
{
|
||||
var query = Query.Where(h => h.ArtistId == artistId);
|
||||
var builder = Builder().Where<History>(h => h.ArtistId == artistId);
|
||||
|
||||
if (eventType.HasValue)
|
||||
{
|
||||
query.AndWhere(h => h.EventType == eventType);
|
||||
builder.Where<History>(h => h.EventType == eventType);
|
||||
}
|
||||
|
||||
query.OrderByDescending(h => h.Date);
|
||||
|
||||
return query;
|
||||
return Query(builder).OrderByDescending(h => h.Date).ToList();
|
||||
}
|
||||
|
||||
public List<History> GetByAlbum(int albumId, HistoryEventType? eventType)
|
||||
{
|
||||
var query = Query.Join<History, Album>(JoinType.Inner, h => h.Album, (h, e) => h.AlbumId == e.Id)
|
||||
.Where(h => h.AlbumId == albumId);
|
||||
var builder = Builder()
|
||||
.Join<History, Album>((h, a) => h.AlbumId == a.Id)
|
||||
.Where<History>(h => h.AlbumId == albumId);
|
||||
|
||||
if (eventType.HasValue)
|
||||
{
|
||||
query.AndWhere(h => h.EventType == eventType);
|
||||
builder.Where<History>(h => h.EventType == eventType);
|
||||
}
|
||||
|
||||
query.OrderByDescending(h => h.Date);
|
||||
|
||||
return query;
|
||||
return _database.QueryJoined<History, Album>(
|
||||
builder,
|
||||
(history, album) =>
|
||||
{
|
||||
history.Album = album;
|
||||
return history;
|
||||
}).OrderByDescending(h => h.Date).ToList();
|
||||
}
|
||||
|
||||
public List<History> FindDownloadHistory(int idArtistId, QualityModel quality)
|
||||
{
|
||||
return Query.Where(h =>
|
||||
h.ArtistId == idArtistId &&
|
||||
h.Quality == quality &&
|
||||
(h.EventType == HistoryEventType.Grabbed ||
|
||||
h.EventType == HistoryEventType.DownloadFailed ||
|
||||
h.EventType == HistoryEventType.TrackFileImported))
|
||||
.ToList();
|
||||
var allowed = new[] { HistoryEventType.Grabbed, HistoryEventType.DownloadFailed, HistoryEventType.TrackFileImported };
|
||||
|
||||
return Query(h => h.ArtistId == idArtistId &&
|
||||
h.Quality == quality &&
|
||||
allowed.Contains(h.EventType));
|
||||
}
|
||||
|
||||
public void DeleteForArtist(int artistId)
|
||||
@@ -94,27 +103,29 @@ namespace NzbDrone.Core.History
|
||||
Delete(c => c.ArtistId == artistId);
|
||||
}
|
||||
|
||||
protected override SortBuilder<History> GetPagedQuery(QueryBuilder<History> query, PagingSpec<History> pagingSpec)
|
||||
{
|
||||
var baseQuery = query.Join<History, Artist>(JoinType.Inner, h => h.Artist, (h, a) => h.ArtistId == a.Id)
|
||||
.Join<History, Album>(JoinType.Inner, h => h.Album, (h, r) => h.AlbumId == r.Id)
|
||||
.Join<History, Track>(JoinType.Left, h => h.Track, (h, t) => h.TrackId == t.Id);
|
||||
|
||||
return base.GetPagedQuery(baseQuery, pagingSpec);
|
||||
}
|
||||
protected override SqlBuilder PagedBuilder() => new SqlBuilder()
|
||||
.Join<History, Artist>((h, a) => h.ArtistId == a.Id)
|
||||
.Join<History, Album>((h, a) => h.AlbumId == a.Id)
|
||||
.LeftJoin<History, Track>((h, t) => h.TrackId == t.Id);
|
||||
protected override IEnumerable<History> PagedQuery(SqlBuilder builder) =>
|
||||
_database.QueryJoined<History, Artist, Album, Track>(builder, (history, artist, album, track) =>
|
||||
{
|
||||
history.Artist = artist;
|
||||
history.Album = album;
|
||||
history.Track = track;
|
||||
return history;
|
||||
});
|
||||
|
||||
public List<History> Since(DateTime date, HistoryEventType? eventType)
|
||||
{
|
||||
var query = Query.Where(h => h.Date >= date);
|
||||
var builder = Builder().Where<History>(x => x.Date >= date);
|
||||
|
||||
if (eventType.HasValue)
|
||||
{
|
||||
query.AndWhere(h => h.EventType == eventType);
|
||||
builder.Where<History>(h => h.EventType == eventType);
|
||||
}
|
||||
|
||||
query.OrderBy(h => h.Date);
|
||||
|
||||
return query;
|
||||
return Query(builder).OrderBy(h => h.Date).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
@@ -13,9 +14,9 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT Id FROM MetadataFiles
|
||||
WHERE RelativePath
|
||||
@@ -25,6 +26,7 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
OR RelativePath
|
||||
LIKE '/%'
|
||||
)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
@@ -13,12 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM NamingConfig
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM NamingConfig
|
||||
WHERE ID NOT IN (
|
||||
SELECT ID FROM NamingConfig
|
||||
LIMIT 1)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
@@ -13,12 +14,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM Users
|
||||
WHERE ID NOT IN (
|
||||
SELECT ID FROM Users
|
||||
LIMIT 1)");
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM Users
|
||||
WHERE ID NOT IN (
|
||||
SELECT ID FROM Users
|
||||
LIMIT 1)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
|
||||
@@ -15,18 +16,17 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
var twoWeeksAgo = DateTime.UtcNow.AddDays(-14);
|
||||
|
||||
mapper.Delete<PendingRelease>(p => p.Added < twoWeeksAgo &&
|
||||
(p.Reason == PendingReleaseReason.DownloadClientUnavailable ||
|
||||
p.Reason == PendingReleaseReason.Fallback));
|
||||
|
||||
// mapper.AddParameter("twoWeeksAgo", $"{DateTime.UtcNow.AddDays(-14).ToString("s")}Z");
|
||||
|
||||
// mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases
|
||||
// WHERE Added < @twoWeeksAgo
|
||||
// AND (Reason = 'DownloadClientUnavailable' OR Reason = 'Fallback')");
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM PendingReleases
|
||||
WHERE Added < @TwoWeeksAgo
|
||||
AND REASON IN @Reasons",
|
||||
new
|
||||
{
|
||||
TwoWeeksAgo = DateTime.UtcNow.AddDays(-14),
|
||||
Reasons = new[] { (int)PendingReleaseReason.DownloadClientUnavailable, (int)PendingReleaseReason.Fallback }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -21,54 +22,58 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
private void DeleteDuplicateArtistMetadata()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT Id FROM MetadataFiles
|
||||
WHERE Type = 1
|
||||
GROUP BY ArtistId, Consumer
|
||||
HAVING COUNT(ArtistId) > 1
|
||||
)");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteDuplicateAlbumMetadata()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT Id FROM MetadataFiles
|
||||
WHERE Type = 6
|
||||
GROUP BY AlbumId, Consumer
|
||||
HAVING COUNT(AlbumId) > 1
|
||||
)");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteDuplicateTrackMetadata()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT Id FROM MetadataFiles
|
||||
WHERE Type = 2
|
||||
GROUP BY TrackFileId, Consumer
|
||||
HAVING COUNT(TrackFileId) > 1
|
||||
)");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteDuplicateTrackImages()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT Id FROM MetadataFiles
|
||||
WHERE Type = 5
|
||||
GROUP BY TrackFileId, Consumer
|
||||
HAVING COUNT(TrackFileId) > 1
|
||||
)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM Albums
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM Albums
|
||||
WHERE Id IN (
|
||||
SELECT Albums.Id FROM Albums
|
||||
LEFT OUTER JOIN Artists
|
||||
ON Albums.ArtistMetadataId = Artists.ArtistMetadataId
|
||||
WHERE Artists.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,15 +14,16 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM ArtistMetadata
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM ArtistMetadata
|
||||
WHERE Id IN (
|
||||
SELECT ArtistMetadata.Id FROM ArtistMetadata
|
||||
LEFT OUTER JOIN Albums ON Albums.ArtistMetadataId = ArtistMetadata.Id
|
||||
LEFT OUTER JOIN Tracks ON Tracks.ArtistMetadataId = ArtistMetadata.Id
|
||||
LEFT OUTER JOIN Artists ON Artists.ArtistMetadataId = ArtistMetadata.Id
|
||||
WHERE Albums.Id IS NULL AND Tracks.Id IS NULL AND Artists.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM Blacklist
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM Blacklist
|
||||
WHERE Id IN (
|
||||
SELECT Blacklist.Id FROM Blacklist
|
||||
LEFT OUTER JOIN Artists
|
||||
ON Blacklist.ArtistId = Artists.Id
|
||||
WHERE Artists.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM DownloadClientStatus
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM DownloadClientStatus
|
||||
WHERE Id IN (
|
||||
SELECT DownloadClientStatus.Id FROM DownloadClientStatus
|
||||
LEFT OUTER JOIN DownloadClients
|
||||
ON DownloadClientStatus.ProviderId = DownloadClients.Id
|
||||
WHERE DownloadClients.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
@@ -19,26 +20,28 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
private void CleanupOrphanedByArtist()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM History
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM History
|
||||
WHERE Id IN (
|
||||
SELECT History.Id FROM History
|
||||
LEFT OUTER JOIN Artists
|
||||
ON History.ArtistId = Artists.Id
|
||||
WHERE Artists.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
|
||||
private void CleanupOrphanedByAlbum()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM History
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM History
|
||||
WHERE Id IN (
|
||||
SELECT History.Id FROM History
|
||||
LEFT OUTER JOIN Albums
|
||||
ON History.AlbumId = Albums.Id
|
||||
WHERE Albums.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM ImportListStatus
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM ImportListStatus
|
||||
WHERE Id IN (
|
||||
SELECT ImportListStatus.Id FROM ImportListStatus
|
||||
LEFT OUTER JOIN ImportLists
|
||||
ON ImportListStatus.ProviderId = ImportLists.Id
|
||||
WHERE ImportLists.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM IndexerStatus
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM IndexerStatus
|
||||
WHERE Id IN (
|
||||
SELECT IndexerStatus.Id FROM IndexerStatus
|
||||
LEFT OUTER JOIN Indexers
|
||||
ON IndexerStatus.ProviderId = Indexers.Id
|
||||
WHERE Indexers.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -22,62 +23,67 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
private void DeleteOrphanedByArtist()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT MetadataFiles.Id FROM MetadataFiles
|
||||
LEFT OUTER JOIN Artists
|
||||
ON MetadataFiles.ArtistId = Artists.Id
|
||||
WHERE Artists.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteOrphanedByAlbum()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT MetadataFiles.Id FROM MetadataFiles
|
||||
LEFT OUTER JOIN Albums
|
||||
ON MetadataFiles.AlbumId = Albums.Id
|
||||
WHERE MetadataFiles.AlbumId > 0
|
||||
AND Albums.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteOrphanedByTrackFile()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT MetadataFiles.Id FROM MetadataFiles
|
||||
LEFT OUTER JOIN TrackFiles
|
||||
ON MetadataFiles.TrackFileId = TrackFiles.Id
|
||||
WHERE MetadataFiles.TrackFileId > 0
|
||||
AND TrackFiles.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteWhereAlbumIdIsZero()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT Id FROM MetadataFiles
|
||||
WHERE Type IN (4, 6)
|
||||
AND AlbumId = 0)");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteWhereTrackFileIsZero()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM MetadataFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM MetadataFiles
|
||||
WHERE Id IN (
|
||||
SELECT Id FROM MetadataFiles
|
||||
WHERE Type IN (2, 5)
|
||||
AND TrackFileId = 0)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM PendingReleases
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM PendingReleases
|
||||
WHERE Id IN (
|
||||
SELECT PendingReleases.Id FROM PendingReleases
|
||||
LEFT OUTER JOIN Artists
|
||||
ON PendingReleases.ArtistId = Artists.Id
|
||||
WHERE Artists.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM AlbumReleases
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM AlbumReleases
|
||||
WHERE Id IN (
|
||||
SELECT AlbumReleases.Id FROM AlbumReleases
|
||||
LEFT OUTER JOIN Albums
|
||||
ON AlbumReleases.AlbumId = Albums.Id
|
||||
WHERE Albums.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,10 +14,10 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
// Unlink where track no longer exists
|
||||
mapper.ExecuteNonQuery(@"UPDATE TrackFiles
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
// Unlink where track no longer exists
|
||||
mapper.Execute(@"UPDATE TrackFiles
|
||||
SET AlbumId = 0
|
||||
WHERE Id IN (
|
||||
SELECT TrackFiles.Id FROM TrackFiles
|
||||
@@ -24,14 +25,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
ON TrackFiles.Id = Tracks.TrackFileId
|
||||
WHERE Tracks.Id IS NULL)");
|
||||
|
||||
// Unlink Tracks where the Trackfiles entry no longer exists
|
||||
mapper.ExecuteNonQuery(@"UPDATE Tracks
|
||||
// Unlink Tracks where the Trackfiles entry no longer exists
|
||||
mapper.Execute(@"UPDATE Tracks
|
||||
SET TrackFileId = 0
|
||||
WHERE Id IN (
|
||||
SELECT Tracks.Id FROM Tracks
|
||||
LEFT OUTER JOIN TrackFiles
|
||||
ON Tracks.TrackFileId = TrackFiles.Id
|
||||
WHERE TrackFiles.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -13,14 +14,15 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.ExecuteNonQuery(@"DELETE FROM Tracks
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"DELETE FROM Tracks
|
||||
WHERE Id IN (
|
||||
SELECT Tracks.Id FROM Tracks
|
||||
LEFT OUTER JOIN AlbumReleases
|
||||
ON Tracks.AlbumReleaseId = AlbumReleases.Id
|
||||
WHERE AlbumReleases.Id IS NULL)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
@@ -17,24 +17,25 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
var usedTags = new[] { "Artists", "Notifications", "DelayProfiles", "ReleaseProfiles" }
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
var usedTags = new[] { "Artists", "Notifications", "DelayProfiles", "ReleaseProfiles" }
|
||||
.SelectMany(v => GetUsedTags(v, mapper))
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray());
|
||||
var usedTagsList = string.Join(",", usedTags.Select(d => d.ToString()).ToArray());
|
||||
|
||||
mapper.ExecuteNonQuery($"DELETE FROM Tags WHERE NOT Id IN ({usedTagsList})");
|
||||
mapper.Execute($"DELETE FROM Tags WHERE NOT Id IN ({usedTagsList})");
|
||||
}
|
||||
}
|
||||
|
||||
private int[] GetUsedTags(string table, IDataMapper mapper)
|
||||
private int[] GetUsedTags(string table, IDbConnection mapper)
|
||||
{
|
||||
return mapper.ExecuteReader($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]'", reader => reader.GetString(0))
|
||||
.SelectMany(Json.Deserialize<List<int>>)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
return mapper.Query<List<int>>($"SELECT DISTINCT Tags FROM {table} WHERE NOT Tags = '[]'")
|
||||
.SelectMany(x => x)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Dapper;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Datastore;
|
||||
@@ -23,12 +24,13 @@ namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
_logger.Debug("Not running scheduled task last execution cleanup during debug");
|
||||
}
|
||||
|
||||
var mapper = _database.GetDataMapper();
|
||||
mapper.AddParameter("time", DateTime.UtcNow);
|
||||
|
||||
mapper.ExecuteNonQuery(@"UPDATE ScheduledTasks
|
||||
SET LastExecution = @time
|
||||
WHERE LastExecution > @time");
|
||||
using (var mapper = _database.OpenConnection())
|
||||
{
|
||||
mapper.Execute(@"UPDATE ScheduledTasks
|
||||
SET LastExecution = @time
|
||||
WHERE LastExecution > @time",
|
||||
new { time = DateTime.UtcNow });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,14 @@ namespace NzbDrone.Core.ImportLists.Exclusions
|
||||
|
||||
public ImportListExclusion FindByForeignId(string foreignId)
|
||||
{
|
||||
return Query.Where<ImportListExclusion>(m => m.ForeignId == foreignId).SingleOrDefault();
|
||||
return Query(m => m.ForeignId == foreignId).SingleOrDefault();
|
||||
}
|
||||
|
||||
public List<ImportListExclusion> FindByForeignId(List<string> ids)
|
||||
{
|
||||
return Query.Where($"[ForeignId] IN ('{string.Join("', '", ids)}')").ToList();
|
||||
// Using Enumerable.Contains forces the builder to create an 'IN'
|
||||
// and not a string 'LIKE' expression
|
||||
return Query(x => Enumerable.Contains(ids, x.ForeignId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Gazelle
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
[FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
||||
public int? EarlyReleaseLimit { get; set; }
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(2)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
[FieldDefinition(3, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
||||
public int? EarlyReleaseLimit { get; set; }
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
int MinimumSeeders { get; set; }
|
||||
|
||||
SeedCriteriaSettings SeedCriteria { get; }
|
||||
SeedCriteriaSettings SeedCriteria { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
[FieldDefinition(4, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
||||
public int? EarlyReleaseLimit { get; set; }
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
[FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
||||
public int? EarlyReleaseLimit { get; set; }
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
[FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
||||
public int? EarlyReleaseLimit { get; set; }
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Indexers.Torrentleech
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(3)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
[FieldDefinition(4, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
||||
public int? EarlyReleaseLimit { get; set; }
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(7)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
public override NzbDroneValidationResult Validate()
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.Indexers.Waffles
|
||||
public int MinimumSeeders { get; set; }
|
||||
|
||||
[FieldDefinition(4)]
|
||||
public SeedCriteriaSettings SeedCriteria { get; } = new SeedCriteriaSettings();
|
||||
public SeedCriteriaSettings SeedCriteria { get; set; } = new SeedCriteriaSettings();
|
||||
|
||||
[FieldDefinition(5, Type = FieldType.Number, Label = "Early Download Limit", Unit = "days", HelpText = "Time before release date Lidarr will download from this indexer, empty is no limit", Advanced = true)]
|
||||
public int? EarlyReleaseLimit { get; set; }
|
||||
|
||||
@@ -4,9 +4,11 @@ using NLog;
|
||||
using NLog.Config;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation;
|
||||
using NzbDrone.Common.Instrumentation.Sentry;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Configuration.Events;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Instrumentation
|
||||
@@ -47,6 +49,10 @@ namespace NzbDrone.Core.Instrumentation
|
||||
SetMinimumLogLevel(rules, "appFileInfo", minimumLogLevel <= LogLevel.Info ? LogLevel.Info : LogLevel.Off);
|
||||
SetMinimumLogLevel(rules, "appFileDebug", minimumLogLevel <= LogLevel.Debug ? LogLevel.Debug : LogLevel.Off);
|
||||
SetMinimumLogLevel(rules, "appFileTrace", minimumLogLevel <= LogLevel.Trace ? LogLevel.Trace : LogLevel.Off);
|
||||
SetLogRotation();
|
||||
|
||||
//Log Sql
|
||||
SqlBuilderExtensions.LogSql = _configFileProvider.LogSql;
|
||||
|
||||
//Sentry
|
||||
ReconfigureSentry();
|
||||
@@ -77,6 +83,14 @@ namespace NzbDrone.Core.Instrumentation
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLogRotation()
|
||||
{
|
||||
foreach (var target in LogManager.Configuration.AllTargets.OfType<NzbDroneFileTarget>())
|
||||
{
|
||||
target.MaxArchiveFiles = _configFileProvider.LogRotate;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReconfigureSentry()
|
||||
{
|
||||
var sentryTarget = LogManager.Configuration.AllTargets.OfType<SentryTarget>().FirstOrDefault();
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Jobs
|
||||
|
||||
public ScheduledTask GetDefinition(Type type)
|
||||
{
|
||||
return Query.Where(c => c.TypeName == type.FullName).Single();
|
||||
return Query(c => c.TypeName == type.FullName).Single();
|
||||
}
|
||||
|
||||
public void SetLastExecutionTime(int id, DateTime executionTime, DateTime startTime)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<TargetFrameworks>net462;netcoreapp3.1</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.0.30" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.0" />
|
||||
@@ -21,7 +23,6 @@
|
||||
<PackageReference Include="MonoTorrent" Version="1.0.11" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Marr.Data\Marr.Data.csproj" />
|
||||
<ProjectReference Include="..\NzbDrone.Common\Lidarr.Common.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using Dapper;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
@@ -32,83 +31,129 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
// always join with all the other good stuff
|
||||
// needed more often than not so better to load it all now
|
||||
protected override QueryBuilder<TrackFile> Query =>
|
||||
DataMapper.Query<TrackFile>()
|
||||
.Join<TrackFile, Track>(JoinType.Left, t => t.Tracks, (t, x) => t.Id == x.TrackFileId)
|
||||
.Join<TrackFile, Album>(JoinType.Left, t => t.Album, (t, a) => t.AlbumId == a.Id)
|
||||
.Join<TrackFile, Artist>(JoinType.Left, t => t.Artist, (t, a) => t.Album.Value.ArtistMetadataId == a.ArtistMetadataId)
|
||||
.Join<Artist, ArtistMetadata>(JoinType.Left, a => a.Metadata, (a, m) => a.ArtistMetadataId == m.Id);
|
||||
protected override SqlBuilder Builder() => new SqlBuilder()
|
||||
.LeftJoin<TrackFile, Track>((t, x) => t.Id == x.TrackFileId)
|
||||
.LeftJoin<TrackFile, Album>((t, a) => t.AlbumId == a.Id)
|
||||
.LeftJoin<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||
.LeftJoin<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id);
|
||||
|
||||
protected override List<TrackFile> Query(SqlBuilder builder) => Query(_database, builder).ToList();
|
||||
|
||||
public static IEnumerable<TrackFile> Query(IDatabase database, SqlBuilder builder)
|
||||
{
|
||||
var fileDictionary = new Dictionary<int, TrackFile>();
|
||||
|
||||
_ = database.QueryJoined<TrackFile, Track, Album, Artist, ArtistMetadata>(builder, (file, track, album, artist, metadata) => Map(fileDictionary, file, track, album, artist, metadata));
|
||||
|
||||
return fileDictionary.Values;
|
||||
}
|
||||
|
||||
private static TrackFile Map(Dictionary<int, TrackFile> dict, TrackFile file, Track track, Album album, Artist artist, ArtistMetadata metadata)
|
||||
{
|
||||
if (!dict.TryGetValue(file.Id, out var entry))
|
||||
{
|
||||
if (artist != null)
|
||||
{
|
||||
artist.Metadata = metadata;
|
||||
}
|
||||
|
||||
entry = file;
|
||||
entry.Tracks = new List<Track>();
|
||||
entry.Album = album;
|
||||
entry.Artist = artist;
|
||||
dict.Add(entry.Id, entry);
|
||||
}
|
||||
|
||||
if (track != null)
|
||||
{
|
||||
entry.Tracks.Value.Add(track);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesByArtist(int artistId)
|
||||
{
|
||||
return Query
|
||||
.Join<Track, AlbumRelease>(JoinType.Inner, t => t.AlbumRelease, (t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.AndWhere(t => t.Artist.Value.Id == artistId)
|
||||
.ToList();
|
||||
return Query(Builder().LeftJoin<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.Where<Artist>(a => a.Id == artistId));
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesByAlbum(int albumId)
|
||||
{
|
||||
return Query
|
||||
.Join<Track, AlbumRelease>(JoinType.Inner, t => t.AlbumRelease, (t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.AndWhere(f => f.AlbumId == albumId)
|
||||
.ToList();
|
||||
return Query(Builder().LeftJoin<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.Where<TrackFile>(f => f.AlbumId == albumId));
|
||||
}
|
||||
|
||||
public List<TrackFile> GetUnmappedFiles()
|
||||
{
|
||||
var query = "SELECT TrackFiles.* " +
|
||||
"FROM TrackFiles " +
|
||||
"LEFT JOIN Tracks ON Tracks.TrackFileId = TrackFiles.Id " +
|
||||
"WHERE Tracks.Id IS NULL ";
|
||||
|
||||
return DataMapper.Query<TrackFile>().QueryText(query).ToList();
|
||||
//x.Id == null is converted to SQL, so warning incorrect
|
||||
#pragma warning disable CS0472
|
||||
return _database.Query<TrackFile>(new SqlBuilder().Select(typeof(TrackFile))
|
||||
.LeftJoin<TrackFile, Track>((f, t) => f.Id == t.TrackFileId)
|
||||
.Where<Track>(t => t.Id == null)).ToList();
|
||||
#pragma warning restore CS0472
|
||||
}
|
||||
|
||||
public void DeleteFilesByAlbum(int albumId)
|
||||
{
|
||||
var ids = DataMapper.Query<TrackFile>().Where(x => x.AlbumId == albumId);
|
||||
DeleteMany(ids);
|
||||
Delete(x => x.AlbumId == albumId);
|
||||
}
|
||||
|
||||
public void UnlinkFilesByAlbum(int albumId)
|
||||
{
|
||||
var files = DataMapper.Query<TrackFile>().Where(x => x.AlbumId == albumId).ToList();
|
||||
var files = Query(x => x.AlbumId == albumId);
|
||||
files.ForEach(x => x.AlbumId = 0);
|
||||
SetFields(files, f => f.AlbumId);
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesByRelease(int releaseId)
|
||||
{
|
||||
return Query
|
||||
.Where<Track>(x => x.AlbumReleaseId == releaseId)
|
||||
.ToList();
|
||||
return Query(Builder().Where<Track>(x => x.AlbumReleaseId == releaseId));
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesWithBasePath(string path)
|
||||
{
|
||||
// ensure path ends with a single trailing path separator to avoid matching partial paths
|
||||
var safePath = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
return DataMapper.Query<TrackFile>()
|
||||
.Where(x => x.Path.StartsWith(safePath))
|
||||
.ToList();
|
||||
return _database.Query<TrackFile>(new SqlBuilder().Where<TrackFile>(x => x.Path.StartsWith(safePath))).ToList();
|
||||
}
|
||||
|
||||
public TrackFile GetFileWithPath(string path)
|
||||
{
|
||||
return Query.Where(x => x.Path == path).SingleOrDefault();
|
||||
return Query(x => x.Path == path).SingleOrDefault();
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFileWithPath(List<string> paths)
|
||||
{
|
||||
// use more limited join for speed
|
||||
var all = DataMapper.Query<TrackFile>()
|
||||
.Join<TrackFile, Track>(JoinType.Left, t => t.Tracks, (t, x) => t.Id == x.TrackFileId)
|
||||
.ToList();
|
||||
var builder = new SqlBuilder()
|
||||
.LeftJoin<TrackFile, Track>((f, t) => f.Id == t.TrackFileId);
|
||||
|
||||
var dict = new Dictionary<int, TrackFile>();
|
||||
_ = _database.QueryJoined<TrackFile, Track>(builder, (file, track) => MapTrack(dict, file, track)).ToList();
|
||||
var all = dict.Values.ToList();
|
||||
|
||||
var joined = all.Join(paths, x => x.Path, x => x, (file, path) => file, PathEqualityComparer.Instance).ToList();
|
||||
return joined;
|
||||
}
|
||||
|
||||
private TrackFile MapTrack(Dictionary<int, TrackFile> dict, TrackFile file, Track track)
|
||||
{
|
||||
if (!dict.TryGetValue(file.Id, out var entry))
|
||||
{
|
||||
entry = file;
|
||||
entry.Tracks = new List<Track>();
|
||||
dict.Add(entry.Id, entry);
|
||||
}
|
||||
|
||||
if (track != null)
|
||||
{
|
||||
entry.Tracks.Value.Add(track);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||
(f, af) => new { DiskFile = f, DbFile = af },
|
||||
PathEqualityComparer.Instance)
|
||||
.ToList();
|
||||
_logger.Trace($"Matched paths for {combined.Count} files");
|
||||
|
||||
List<IFileInfo> unwanted = null;
|
||||
if (filter == FilterFilesType.Known)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SQLite;
|
||||
using Dapper;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
@@ -10,22 +10,16 @@ namespace NzbDrone.Core.Messaging.Commands
|
||||
{
|
||||
void Trim();
|
||||
void OrphanStarted();
|
||||
List<CommandModel> FindCommands(string name);
|
||||
List<CommandModel> FindQueuedOrStarted(string name);
|
||||
List<CommandModel> Queued();
|
||||
List<CommandModel> Started();
|
||||
void Start(CommandModel command);
|
||||
void End(CommandModel command);
|
||||
}
|
||||
|
||||
public class CommandRepository : BasicRepository<CommandModel>, ICommandRepository
|
||||
{
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
public CommandRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public void Trim()
|
||||
@@ -37,37 +31,23 @@ namespace NzbDrone.Core.Messaging.Commands
|
||||
|
||||
public void OrphanStarted()
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
var sql = @"UPDATE Commands SET Status = @Orphaned, EndedAt = @Ended WHERE Status = @Started";
|
||||
var args = new
|
||||
{
|
||||
Orphaned = (int)CommandStatus.Orphaned,
|
||||
Started = (int)CommandStatus.Started,
|
||||
Ended = DateTime.UtcNow
|
||||
};
|
||||
|
||||
mapper.Parameters.Add(new SQLiteParameter("@orphaned", (int)CommandStatus.Orphaned));
|
||||
mapper.Parameters.Add(new SQLiteParameter("@started", (int)CommandStatus.Started));
|
||||
mapper.Parameters.Add(new SQLiteParameter("@ended", DateTime.UtcNow));
|
||||
|
||||
mapper.ExecuteNonQuery(@"UPDATE Commands
|
||||
SET Status = @orphaned, EndedAt = @ended
|
||||
WHERE Status = @started");
|
||||
}
|
||||
|
||||
public List<CommandModel> FindCommands(string name)
|
||||
{
|
||||
return Query.Where(c => c.Name == name).ToList();
|
||||
}
|
||||
|
||||
public List<CommandModel> FindQueuedOrStarted(string name)
|
||||
{
|
||||
return Query.Where(c => c.Name == name)
|
||||
.AndWhere("[Status] IN (0,1)")
|
||||
.ToList();
|
||||
using (var conn = _database.OpenConnection())
|
||||
{
|
||||
conn.Execute(sql, args);
|
||||
}
|
||||
}
|
||||
|
||||
public List<CommandModel> Queued()
|
||||
{
|
||||
return Query.Where(c => c.Status == CommandStatus.Queued);
|
||||
}
|
||||
|
||||
public List<CommandModel> Started()
|
||||
{
|
||||
return Query.Where(c => c.Status == CommandStatus.Started);
|
||||
return Query(c => c.Status == CommandStatus.Queued);
|
||||
}
|
||||
|
||||
public void Start(CommandModel command)
|
||||
|
||||
@@ -2,8 +2,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Equ;
|
||||
using Marr.Data;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
@@ -58,6 +59,7 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
//compatibility properties with old version of Album
|
||||
[MemberwiseEqualityIgnore]
|
||||
[JsonIgnore]
|
||||
public int ArtistId
|
||||
{
|
||||
get { return Artist?.Value?.Id ?? 0; } set { Artist.Value.Id = value; }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Profiles.Metadata;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Equ;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
@@ -32,265 +32,82 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public class AlbumRepository : BasicRepository<Album>, IAlbumRepository
|
||||
{
|
||||
private readonly IMainDatabase _database;
|
||||
|
||||
public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
public List<Album> GetAlbums(int artistId)
|
||||
{
|
||||
return Query.Join<Album, Artist>(JoinType.Inner, album => album.Artist, (l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Where<Artist>(a => a.Id == artistId).ToList();
|
||||
return Query(Builder().Join<Album, Artist>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId).Where<Artist>(a => a.Id == artistId));
|
||||
}
|
||||
|
||||
public List<Album> GetLastAlbums(IEnumerable<int> artistMetadataIds)
|
||||
{
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"WHERE Albums.ArtistMetadataId IN ({0}) " +
|
||||
"AND Albums.ReleaseDate < datetime('now') " +
|
||||
"GROUP BY Albums.ArtistMetadataId " +
|
||||
"HAVING Albums.ReleaseDate = MAX(Albums.ReleaseDate)",
|
||||
string.Join(", ", artistMetadataIds));
|
||||
|
||||
return Query.QueryText(query);
|
||||
var now = DateTime.UtcNow;
|
||||
return Query(Builder().Where<Album>(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate < now)
|
||||
.GroupBy<Album>(x => x.ArtistMetadataId)
|
||||
.Having("Albums.ReleaseDate = MAX(Albums.ReleaseDate)"));
|
||||
}
|
||||
|
||||
public List<Album> GetNextAlbums(IEnumerable<int> artistMetadataIds)
|
||||
{
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"WHERE Albums.ArtistMetadataId IN ({0}) " +
|
||||
"AND Albums.ReleaseDate > datetime('now') " +
|
||||
"GROUP BY Albums.ArtistMetadataId " +
|
||||
"HAVING Albums.ReleaseDate = MIN(Albums.ReleaseDate)",
|
||||
string.Join(", ", artistMetadataIds));
|
||||
|
||||
return Query.QueryText(query);
|
||||
var now = DateTime.UtcNow;
|
||||
return Query(Builder().Where<Album>(x => artistMetadataIds.Contains(x.ArtistMetadataId) && x.ReleaseDate > now)
|
||||
.GroupBy<Album>(x => x.ArtistMetadataId)
|
||||
.Having("Albums.ReleaseDate = MIN(Albums.ReleaseDate)"));
|
||||
}
|
||||
|
||||
public List<Album> GetAlbumsByArtistMetadataId(int artistMetadataId)
|
||||
{
|
||||
return Query.Where(s => s.ArtistMetadataId == artistMetadataId);
|
||||
return Query(s => s.ArtistMetadataId == artistMetadataId);
|
||||
}
|
||||
|
||||
public List<Album> GetAlbumsForRefresh(int artistMetadataId, IEnumerable<string> foreignIds)
|
||||
{
|
||||
return Query
|
||||
.Where(a => a.ArtistMetadataId == artistMetadataId)
|
||||
.OrWhere($"[ForeignAlbumId] IN ('{string.Join("', '", foreignIds)}')")
|
||||
.ToList();
|
||||
return Query(a => a.ArtistMetadataId == artistMetadataId || foreignIds.Contains(a.ForeignAlbumId));
|
||||
}
|
||||
|
||||
public Album FindById(string foreignAlbumId)
|
||||
{
|
||||
return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault();
|
||||
return Query(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault();
|
||||
}
|
||||
|
||||
//x.Id == null is converted to SQL, so warning incorrect
|
||||
#pragma warning disable CS0472
|
||||
private SqlBuilder AlbumsWithoutFilesBuilder(DateTime currentTime, bool monitored) => Builder()
|
||||
.Join<Album, Artist>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Join<Album, AlbumRelease>((a, r) => a.Id == r.AlbumId)
|
||||
.Join<AlbumRelease, Track>((r, t) => r.Id == t.AlbumReleaseId)
|
||||
.LeftJoin<Track, TrackFile>((t, f) => t.TrackFileId == f.Id)
|
||||
.Where<TrackFile>(f => f.Id == null)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.Where<Album>(a => a.ReleaseDate <= currentTime && a.Monitored == monitored)
|
||||
.Where<Artist>(a => a.Monitored == monitored)
|
||||
.GroupBy<Album>(a => a.Id);
|
||||
#pragma warning restore CS0472
|
||||
|
||||
public PagingSpec<Album> AlbumsWithoutFiles(PagingSpec<Album> pagingSpec)
|
||||
{
|
||||
var currentTime = DateTime.UtcNow;
|
||||
var monitored = pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True");
|
||||
|
||||
//pagingSpec.TotalRecords = GetMissingAlbumsQuery(pagingSpec, currentTime).GetRowCount(); Cant Use GetRowCount with a Manual Query
|
||||
pagingSpec.TotalRecords = GetMissingAlbumsQueryCount(pagingSpec, currentTime);
|
||||
pagingSpec.Records = GetMissingAlbumsQuery(pagingSpec, currentTime).ToList();
|
||||
pagingSpec.Records = GetPagedRecords(AlbumsWithoutFilesBuilder(currentTime, monitored), pagingSpec, PagedQuery);
|
||||
pagingSpec.TotalRecords = GetPagedRecordCount(AlbumsWithoutFilesBuilder(currentTime, monitored).SelectCountDistinct<Album>(x => x.Id), pagingSpec);
|
||||
|
||||
return pagingSpec;
|
||||
}
|
||||
|
||||
public PagingSpec<Album> AlbumsWhereCutoffUnmet(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
{
|
||||
pagingSpec.TotalRecords = GetCutOffAlbumsQueryCount(pagingSpec, qualitiesBelowCutoff);
|
||||
pagingSpec.Records = GetCutOffAlbumsQuery(pagingSpec, qualitiesBelowCutoff).ToList();
|
||||
|
||||
return pagingSpec;
|
||||
}
|
||||
|
||||
public List<Album> AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored)
|
||||
{
|
||||
var query = Query.Join<Album, Artist>(JoinType.Inner, rg => rg.Artist, (rg, a) => rg.ArtistMetadataId == a.ArtistMetadataId)
|
||||
.Where<Album>(rg => rg.ReleaseDate >= startDate)
|
||||
.AndWhere(rg => rg.ReleaseDate <= endDate);
|
||||
|
||||
if (!includeUnmonitored)
|
||||
{
|
||||
query.AndWhere(e => e.Monitored)
|
||||
.AndWhere(e => e.Artist.Value.Monitored);
|
||||
}
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
|
||||
public List<Album> ArtistAlbumsBetweenDates(Artist artist, DateTime startDate, DateTime endDate, bool includeUnmonitored)
|
||||
{
|
||||
var query = Query.Join<Album, Artist>(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistMetadataId == s.ArtistMetadataId)
|
||||
.Where<Album>(e => e.ReleaseDate >= startDate)
|
||||
.AndWhere(e => e.ReleaseDate <= endDate)
|
||||
.AndWhere(e => e.ArtistMetadataId == artist.ArtistMetadataId);
|
||||
|
||||
if (!includeUnmonitored)
|
||||
{
|
||||
query.AndWhere(e => e.Monitored)
|
||||
.AndWhere(e => e.Artist.Value.Monitored);
|
||||
}
|
||||
|
||||
return query.ToList();
|
||||
}
|
||||
|
||||
private QueryBuilder<Album> GetMissingAlbumsQuery(PagingSpec<Album> pagingSpec, DateTime currentTime)
|
||||
{
|
||||
string sortKey;
|
||||
string monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)";
|
||||
|
||||
if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True"))
|
||||
{
|
||||
monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)";
|
||||
}
|
||||
|
||||
if (pagingSpec.SortKey == "releaseDate")
|
||||
{
|
||||
sortKey = "Albums." + pagingSpec.SortKey;
|
||||
}
|
||||
else if (pagingSpec.SortKey == "artist.sortName")
|
||||
{
|
||||
sortKey = "Artists." + pagingSpec.SortKey.Split('.').Last();
|
||||
}
|
||||
else if (pagingSpec.SortKey == "albumTitle")
|
||||
{
|
||||
sortKey = "Albums.title";
|
||||
}
|
||||
else
|
||||
{
|
||||
sortKey = "Albums.releaseDate";
|
||||
}
|
||||
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN Artists ON Albums.ArtistMetadataId = Artists.ArtistMetadataId " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"LEFT OUTER JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " +
|
||||
"WHERE TrackFiles.Id IS NULL " +
|
||||
"AND AlbumReleases.Monitored = 1 " +
|
||||
"AND ({0}) AND {1} " +
|
||||
"GROUP BY Albums.Id " +
|
||||
" ORDER BY {2} {3} LIMIT {4} OFFSET {5}",
|
||||
monitored,
|
||||
BuildReleaseDateCutoffWhereClause(currentTime),
|
||||
sortKey,
|
||||
pagingSpec.ToSortDirection(),
|
||||
pagingSpec.PageSize,
|
||||
pagingSpec.PagingOffset());
|
||||
|
||||
return Query.QueryText(query);
|
||||
}
|
||||
|
||||
private int GetMissingAlbumsQueryCount(PagingSpec<Album> pagingSpec, DateTime currentTime)
|
||||
{
|
||||
var monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)";
|
||||
|
||||
if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True"))
|
||||
{
|
||||
monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)";
|
||||
}
|
||||
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN Artists ON Albums.ArtistMetadataId = Artists.ArtistMetadataId " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"LEFT OUTER JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " +
|
||||
"WHERE TrackFiles.Id IS NULL " +
|
||||
"AND AlbumReleases.Monitored = 1 " +
|
||||
"AND ({0}) AND {1} " +
|
||||
"GROUP BY Albums.Id ",
|
||||
monitored,
|
||||
BuildReleaseDateCutoffWhereClause(currentTime));
|
||||
|
||||
return Query.QueryText(query).Count();
|
||||
}
|
||||
|
||||
private string BuildReleaseDateCutoffWhereClause(DateTime currentTime)
|
||||
{
|
||||
return string.Format("datetime(strftime('%s', Albums.[ReleaseDate]), 'unixepoch') <= '{0}'",
|
||||
currentTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
private QueryBuilder<Album> GetCutOffAlbumsQuery(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
{
|
||||
string sortKey;
|
||||
string monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)";
|
||||
|
||||
if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True"))
|
||||
{
|
||||
monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)";
|
||||
}
|
||||
|
||||
if (pagingSpec.SortKey == "releaseDate")
|
||||
{
|
||||
sortKey = "Albums." + pagingSpec.SortKey;
|
||||
}
|
||||
else if (pagingSpec.SortKey == "artist.sortName")
|
||||
{
|
||||
sortKey = "Artists." + pagingSpec.SortKey.Split('.').Last();
|
||||
}
|
||||
else if (pagingSpec.SortKey == "albumTitle")
|
||||
{
|
||||
sortKey = "Albums.title";
|
||||
}
|
||||
else
|
||||
{
|
||||
sortKey = "Albums.releaseDate";
|
||||
}
|
||||
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN Artists on Albums.ArtistMetadataId == Artists.ArtistMetadataId " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " +
|
||||
"WHERE {0} " +
|
||||
"AND AlbumReleases.Monitored = 1 " +
|
||||
"GROUP BY Albums.Id " +
|
||||
"HAVING {1} " +
|
||||
"ORDER BY {2} {3} LIMIT {4} OFFSET {5}",
|
||||
monitored,
|
||||
BuildQualityCutoffWhereClause(qualitiesBelowCutoff),
|
||||
sortKey,
|
||||
pagingSpec.ToSortDirection(),
|
||||
pagingSpec.PageSize,
|
||||
pagingSpec.PagingOffset());
|
||||
|
||||
return Query.QueryText(query);
|
||||
}
|
||||
|
||||
private int GetCutOffAlbumsQueryCount(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
{
|
||||
var monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)";
|
||||
|
||||
if (pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True"))
|
||||
{
|
||||
monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)";
|
||||
}
|
||||
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN Artists on Albums.ArtistMetadataId == Artists.ArtistMetadataId " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " +
|
||||
"WHERE {0} " +
|
||||
"AND AlbumReleases.Monitored = 1 " +
|
||||
"GROUP BY Albums.Id " +
|
||||
"HAVING {1}",
|
||||
monitored,
|
||||
BuildQualityCutoffWhereClause(qualitiesBelowCutoff));
|
||||
|
||||
return Query.QueryText(query).Count();
|
||||
}
|
||||
private SqlBuilder AlbumsWhereCutoffUnmetBuilder(bool monitored, List<QualitiesBelowCutoff> qualitiesBelowCutoff) => Builder()
|
||||
.Join<Album, Artist>((l, r) => l.ArtistMetadataId == r.ArtistMetadataId)
|
||||
.Join<Album, AlbumRelease>((a, r) => a.Id == r.AlbumId)
|
||||
.Join<AlbumRelease, Track>((r, t) => r.Id == t.AlbumReleaseId)
|
||||
.LeftJoin<Track, TrackFile>((t, f) => t.TrackFileId == f.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.Where<Album>(a => a.Monitored == monitored)
|
||||
.Where<Artist>(a => a.Monitored == monitored)
|
||||
.GroupBy<Album>(a => a.Id)
|
||||
.Having(BuildQualityCutoffWhereClause(qualitiesBelowCutoff));
|
||||
|
||||
private string BuildQualityCutoffWhereClause(List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
{
|
||||
@@ -307,6 +124,46 @@ namespace NzbDrone.Core.Music
|
||||
return string.Format("({0})", string.Join(" OR ", clauses));
|
||||
}
|
||||
|
||||
public PagingSpec<Album> AlbumsWhereCutoffUnmet(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
{
|
||||
var monitored = pagingSpec.FilterExpressions.FirstOrDefault().ToString().Contains("True");
|
||||
|
||||
pagingSpec.Records = GetPagedRecords(AlbumsWhereCutoffUnmetBuilder(monitored, qualitiesBelowCutoff), pagingSpec, PagedQuery);
|
||||
|
||||
var countTemplate = $"SELECT COUNT(*) FROM (SELECT /**select**/ FROM {TableMapping.Mapper.TableNameMapping(typeof(Album))} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/)";
|
||||
pagingSpec.TotalRecords = GetPagedRecordCount(AlbumsWhereCutoffUnmetBuilder(monitored, qualitiesBelowCutoff).Select(typeof(Album)), pagingSpec, countTemplate);
|
||||
|
||||
return pagingSpec;
|
||||
}
|
||||
|
||||
public List<Album> AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored)
|
||||
{
|
||||
var builder = Builder().Where<Album>(rg => rg.ReleaseDate >= startDate && rg.ReleaseDate <= endDate);
|
||||
|
||||
if (!includeUnmonitored)
|
||||
{
|
||||
builder = builder.Where<Album>(e => e.Monitored == true)
|
||||
.Where<Artist>(e => e.Monitored == true);
|
||||
}
|
||||
|
||||
return Query(builder);
|
||||
}
|
||||
|
||||
public List<Album> ArtistAlbumsBetweenDates(Artist artist, DateTime startDate, DateTime endDate, bool includeUnmonitored)
|
||||
{
|
||||
var builder = Builder().Where<Album>(rg => rg.ReleaseDate >= startDate &&
|
||||
rg.ReleaseDate <= endDate &&
|
||||
rg.ArtistMetadataId == artist.ArtistMetadataId);
|
||||
|
||||
if (!includeUnmonitored)
|
||||
{
|
||||
builder = builder.Where<Album>(e => e.Monitored == true)
|
||||
.Where<Artist>(e => e.Monitored == true);
|
||||
}
|
||||
|
||||
return Query(builder);
|
||||
}
|
||||
|
||||
public void SetMonitoredFlat(Album album, bool monitored)
|
||||
{
|
||||
album.Monitored = monitored;
|
||||
@@ -315,15 +172,8 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public void SetMonitored(IEnumerable<int> ids, bool monitored)
|
||||
{
|
||||
var mapper = _database.GetDataMapper();
|
||||
|
||||
mapper.AddParameter("monitored", monitored);
|
||||
|
||||
var sql = "UPDATE Albums " +
|
||||
"SET Monitored = @monitored " +
|
||||
$"WHERE Id IN ({string.Join(", ", ids)})";
|
||||
|
||||
mapper.ExecuteNonQuery(sql);
|
||||
var albums = ids.Select(x => new Album { Id = x, Monitored = monitored }).ToList();
|
||||
SetFields(albums, p => p.Monitored);
|
||||
}
|
||||
|
||||
public Album FindByTitle(int artistMetadataId, string title)
|
||||
@@ -335,45 +185,32 @@ namespace NzbDrone.Core.Music
|
||||
cleanTitle = title;
|
||||
}
|
||||
|
||||
return Query.Where(s => s.CleanTitle == cleanTitle || s.Title == title)
|
||||
.AndWhere(s => s.ArtistMetadataId == artistMetadataId)
|
||||
.ExclusiveOrDefault();
|
||||
return Query(s => (s.CleanTitle == cleanTitle || s.Title == title) && s.ArtistMetadataId == artistMetadataId)
|
||||
.ExclusiveOrDefault();
|
||||
}
|
||||
|
||||
public Album FindAlbumByRelease(string albumReleaseId)
|
||||
{
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId = Albums.Id " +
|
||||
"WHERE AlbumReleases.ForeignReleaseId = '{0}'",
|
||||
albumReleaseId);
|
||||
return Query.QueryText(query).FirstOrDefault();
|
||||
return Query(Builder().Join<Album, AlbumRelease>((a, r) => a.Id == r.AlbumId)
|
||||
.Where<AlbumRelease>(x => x.ForeignReleaseId == albumReleaseId)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public Album FindAlbumByTrack(int trackId)
|
||||
{
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId = Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId = AlbumReleases.Id " +
|
||||
"WHERE Tracks.Id = {0}",
|
||||
trackId);
|
||||
return Query.QueryText(query).FirstOrDefault();
|
||||
return Query(Builder().Join<Album, AlbumRelease>((a, r) => a.Id == r.AlbumId)
|
||||
.Join<AlbumRelease, Track>((r, t) => r.Id == t.AlbumReleaseId)
|
||||
.Where<Track>(x => x.Id == trackId)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public List<Album> GetArtistAlbumsWithFiles(Artist artist)
|
||||
{
|
||||
string query = string.Format("SELECT Albums.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " +
|
||||
"WHERE Albums.ArtistMetadataId == {0} " +
|
||||
"AND AlbumReleases.Monitored = 1 " +
|
||||
"GROUP BY Albums.Id ",
|
||||
artist.ArtistMetadataId);
|
||||
|
||||
return Query.QueryText(query).ToList();
|
||||
var id = artist.ArtistMetadataId;
|
||||
return Query(Builder().Join<Album, AlbumRelease>((a, r) => a.Id == r.AlbumId)
|
||||
.Join<AlbumRelease, Track>((r, t) => r.Id == t.AlbumReleaseId)
|
||||
.Join<Track, TrackFile>((t, f) => t.TrackFileId == f.Id)
|
||||
.Where<Album>(x => x.ArtistMetadataId == id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.GroupBy<Album>(x => x.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public List<ArtistMetadata> FindById(List<string> foreignIds)
|
||||
{
|
||||
return Query.Where($"[ForeignArtistId] IN ('{string.Join("','", foreignIds)}')").ToList();
|
||||
return Query(x => Enumerable.Contains(foreignIds, x.ForeignArtistId));
|
||||
}
|
||||
|
||||
public bool UpsertMany(List<ArtistMetadata> data)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -12,32 +13,43 @@ namespace NzbDrone.Core.Music
|
||||
Artist FindByName(string cleanName);
|
||||
Artist FindById(string foreignArtistId);
|
||||
Artist GetArtistByMetadataId(int artistMetadataId);
|
||||
List<Artist> GetArtistByMetadataId(IEnumerable<int> artistMetadataId);
|
||||
}
|
||||
|
||||
public class ArtistRepository : BasicRepository<Artist>, IArtistRepository
|
||||
{
|
||||
public ArtistRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
public ArtistRepository(IMainDatabase database,
|
||||
IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
|
||||
// Always explicitly join with ArtistMetadata to populate Metadata without repeated LazyLoading
|
||||
protected override QueryBuilder<Artist> Query => DataMapper.Query<Artist>().Join<Artist, ArtistMetadata>(JoinType.Inner, a => a.Metadata, (l, r) => l.ArtistMetadataId == r.Id);
|
||||
protected override SqlBuilder Builder() => new SqlBuilder()
|
||||
.Join<Artist, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id);
|
||||
|
||||
protected override List<Artist> Query(SqlBuilder builder) => Query(_database, builder).ToList();
|
||||
|
||||
public static IEnumerable<Artist> Query(IDatabase database, SqlBuilder builder)
|
||||
{
|
||||
return database.QueryJoined<Artist, ArtistMetadata>(builder, (artist, metadata) =>
|
||||
{
|
||||
artist.Metadata = metadata;
|
||||
return artist;
|
||||
});
|
||||
}
|
||||
|
||||
public bool ArtistPathExists(string path)
|
||||
{
|
||||
return Query.Where(c => c.Path == path).Any();
|
||||
return Query(c => c.Path == path).Any();
|
||||
}
|
||||
|
||||
public Artist FindById(string foreignArtistId)
|
||||
{
|
||||
var artist = Query.Where<ArtistMetadata>(m => m.ForeignArtistId == foreignArtistId).SingleOrDefault();
|
||||
var artist = Query(Builder().Where<ArtistMetadata>(m => m.ForeignArtistId == foreignArtistId)).SingleOrDefault();
|
||||
|
||||
if (artist == null)
|
||||
{
|
||||
var id = "\"" + foreignArtistId + "\"";
|
||||
artist = Query.Where<ArtistMetadata>(x => x.OldForeignArtistIds.Contains(id))
|
||||
.SingleOrDefault();
|
||||
artist = Query(Builder().Where<ArtistMetadata>(x => x.OldForeignArtistIds.Contains(foreignArtistId))).SingleOrDefault();
|
||||
}
|
||||
|
||||
return artist;
|
||||
@@ -47,12 +59,17 @@ namespace NzbDrone.Core.Music
|
||||
{
|
||||
cleanName = cleanName.ToLowerInvariant();
|
||||
|
||||
return Query.Where(s => s.CleanName == cleanName).ExclusiveOrDefault();
|
||||
return Query(s => s.CleanName == cleanName).ExclusiveOrDefault();
|
||||
}
|
||||
|
||||
public Artist GetArtistByMetadataId(int artistMetadataId)
|
||||
{
|
||||
return Query.Where(s => s.ArtistMetadataId == artistMetadataId).SingleOrDefault();
|
||||
return Query(s => s.ArtistMetadataId == artistMetadataId).SingleOrDefault();
|
||||
}
|
||||
|
||||
public List<Artist> GetArtistByMetadataId(IEnumerable<int> artistMetadataIds)
|
||||
{
|
||||
return Query(s => artistMetadataIds.Contains(s.ArtistMetadataId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Marr.Data.QGen;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
@@ -25,15 +25,12 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public AlbumRelease FindByForeignReleaseId(string foreignReleaseId, bool checkRedirect = false)
|
||||
{
|
||||
var release = Query
|
||||
.Where(x => x.ForeignReleaseId == foreignReleaseId)
|
||||
.SingleOrDefault();
|
||||
var release = Query(x => x.ForeignReleaseId == foreignReleaseId).SingleOrDefault();
|
||||
|
||||
if (release == null && checkRedirect)
|
||||
{
|
||||
var id = "\"" + foreignReleaseId + "\"";
|
||||
release = Query.Where(x => x.OldForeignReleaseIds.Contains(id))
|
||||
.SingleOrDefault();
|
||||
release = Query(x => x.OldForeignReleaseIds.Contains(id)).SingleOrDefault();
|
||||
}
|
||||
|
||||
return release;
|
||||
@@ -41,31 +38,32 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public List<AlbumRelease> GetReleasesForRefresh(int albumId, IEnumerable<string> foreignReleaseIds)
|
||||
{
|
||||
return Query
|
||||
.Where(r => r.AlbumId == albumId)
|
||||
.OrWhere($"[ForeignReleaseId] IN ('{string.Join("', '", foreignReleaseIds)}')")
|
||||
.ToList();
|
||||
return Query(r => r.AlbumId == albumId || foreignReleaseIds.Contains(r.ForeignReleaseId));
|
||||
}
|
||||
|
||||
public List<AlbumRelease> FindByAlbum(int id)
|
||||
{
|
||||
// populate the albums and artist metadata also
|
||||
// this hopefully speeds up the track matching a lot
|
||||
return Query
|
||||
.Join<AlbumRelease, Album>(JoinType.Left, r => r.Album, (r, a) => r.AlbumId == a.Id)
|
||||
.Join<Album, ArtistMetadata>(JoinType.Left, a => a.ArtistMetadata, (a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Where<AlbumRelease>(r => r.AlbumId == id)
|
||||
.ToList();
|
||||
var builder = new SqlBuilder()
|
||||
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
|
||||
.Join<Album, ArtistMetadata>((a, m) => a.ArtistMetadataId == m.Id)
|
||||
.Where<AlbumRelease>(r => r.AlbumId == id);
|
||||
|
||||
return _database.QueryJoined<AlbumRelease, Album, ArtistMetadata>(builder, (release, album, metadata) =>
|
||||
{
|
||||
release.Album = album;
|
||||
release.Album.Value.ArtistMetadata = metadata;
|
||||
return release;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public List<AlbumRelease> FindByRecordingId(List<string> recordingIds)
|
||||
{
|
||||
var query = "SELECT DISTINCT AlbumReleases.*" +
|
||||
"FROM AlbumReleases " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId = AlbumReleases.Id " +
|
||||
$"WHERE Tracks.ForeignRecordingId IN ('{string.Join("', '", recordingIds)}')";
|
||||
|
||||
return Query.QueryText(query).ToList();
|
||||
return Query(Builder()
|
||||
.Join<AlbumRelease, Track>((r, t) => r.Id == t.AlbumReleaseId)
|
||||
.Where<Track>(t => Enumerable.Contains(recordingIds, t.ForeignRecordingId))
|
||||
.GroupBy<AlbumRelease>(x => x.Id));
|
||||
}
|
||||
|
||||
public List<AlbumRelease> SetMonitored(AlbumRelease release)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
@@ -28,91 +30,76 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public List<Track> GetTracks(int artistId)
|
||||
{
|
||||
string query = string.Format("SELECT Tracks.* " +
|
||||
"FROM Artists " +
|
||||
"JOIN Albums ON Albums.ArtistMetadataId == Artists.ArtistMetadataId " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"WHERE Artists.Id = {0} " +
|
||||
"AND AlbumReleases.Monitored = 1",
|
||||
artistId);
|
||||
|
||||
return Query.QueryText(query).ToList();
|
||||
return Query(Builder()
|
||||
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
|
||||
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.Where<Artist>(x => x.Id == artistId));
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByAlbum(int albumId)
|
||||
{
|
||||
string query = string.Format("SELECT Tracks.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"WHERE Albums.Id = {0} " +
|
||||
"AND AlbumReleases.Monitored = 1",
|
||||
albumId);
|
||||
|
||||
return Query.QueryText(query).ToList();
|
||||
return Query(Builder()
|
||||
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.Where<Album>(x => x.Id == albumId));
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByRelease(int albumReleaseId)
|
||||
{
|
||||
return Query.Where(t => t.AlbumReleaseId == albumReleaseId).ToList();
|
||||
return Query(t => t.AlbumReleaseId == albumReleaseId).ToList();
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByReleases(List<int> albumReleaseIds)
|
||||
{
|
||||
// this will populate the artist metadata also
|
||||
return Query
|
||||
.Join<Track, ArtistMetadata>(Marr.Data.QGen.JoinType.Inner, t => t.ArtistMetadata, (l, r) => l.ArtistMetadataId == r.Id)
|
||||
.Where($"[AlbumReleaseId] IN ({string.Join(", ", albumReleaseIds)})")
|
||||
.ToList();
|
||||
return _database.QueryJoined<Track, ArtistMetadata>(Builder()
|
||||
.Join<Track, ArtistMetadata>((l, r) => l.ArtistMetadataId == r.Id)
|
||||
.Where<Track>(x => albumReleaseIds.Contains(x.AlbumReleaseId)), (track, metadata) =>
|
||||
{
|
||||
track.ArtistMetadata = metadata;
|
||||
return track;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public List<Track> GetTracksForRefresh(int albumReleaseId, IEnumerable<string> foreignTrackIds)
|
||||
{
|
||||
return Query
|
||||
.Where(t => t.AlbumReleaseId == albumReleaseId)
|
||||
.OrWhere($"[ForeignTrackId] IN ('{string.Join("', '", foreignTrackIds)}')")
|
||||
.ToList();
|
||||
return Query(a => a.AlbumReleaseId == albumReleaseId || foreignTrackIds.Contains(a.ForeignTrackId));
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByFileId(int fileId)
|
||||
{
|
||||
return Query.Where(e => e.TrackFileId == fileId).ToList();
|
||||
return Query(e => e.TrackFileId == fileId);
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByFileId(IEnumerable<int> ids)
|
||||
{
|
||||
return Query.Where($"[TrackFileId] IN ({string.Join(", ", ids)})").ToList();
|
||||
return Query(x => ids.Contains(x.TrackFileId));
|
||||
}
|
||||
|
||||
public List<Track> TracksWithFiles(int artistId)
|
||||
{
|
||||
string query = string.Format("SELECT Tracks.* " +
|
||||
"FROM Artists " +
|
||||
"JOIN Albums ON Albums.ArtistMetadataId = Artists.ArtistMetadataId " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " +
|
||||
"WHERE Artists.Id == {0} " +
|
||||
"AND AlbumReleases.Monitored = 1 ",
|
||||
artistId);
|
||||
|
||||
return Query.QueryText(query).ToList();
|
||||
return Query(Builder()
|
||||
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||
.Join<AlbumRelease, Album>((r, a) => r.AlbumId == a.Id)
|
||||
.Join<Album, Artist>((album, artist) => album.ArtistMetadataId == artist.ArtistMetadataId)
|
||||
.Join<Track, TrackFile>((t, f) => t.TrackFileId == f.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true)
|
||||
.Where<Artist>(x => x.Id == artistId));
|
||||
}
|
||||
|
||||
public List<Track> TracksWithoutFiles(int albumId)
|
||||
{
|
||||
string query = string.Format("SELECT Tracks.* " +
|
||||
"FROM Albums " +
|
||||
"JOIN AlbumReleases ON AlbumReleases.AlbumId == Albums.Id " +
|
||||
"JOIN Tracks ON Tracks.AlbumReleaseId == AlbumReleases.Id " +
|
||||
"LEFT OUTER JOIN TrackFiles ON TrackFiles.Id == Tracks.TrackFileId " +
|
||||
"WHERE Albums.Id == {0} " +
|
||||
"AND AlbumReleases.Monitored = 1 " +
|
||||
"AND TrackFiles.Id IS NULL",
|
||||
albumId);
|
||||
|
||||
return Query.QueryText(query).ToList();
|
||||
//x.Id == null is converted to SQL, so warning incorrect
|
||||
#pragma warning disable CS0472
|
||||
return Query(Builder()
|
||||
.Join<Track, AlbumRelease>((t, r) => t.AlbumReleaseId == r.Id)
|
||||
.LeftJoin<Track, TrackFile>((t, f) => t.TrackFileId == f.Id)
|
||||
.Where<AlbumRelease>(r => r.Monitored == true && r.AlbumId == albumId)
|
||||
.Where<TrackFile>(x => x.Id == null));
|
||||
#pragma warning restore CS0472
|
||||
}
|
||||
|
||||
public void SetFileId(List<Track> tracks)
|
||||
@@ -122,11 +109,9 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public void DetachTrackFile(int trackFileId)
|
||||
{
|
||||
DataMapper.Update<Track>()
|
||||
.Where(x => x.TrackFileId == trackFileId)
|
||||
.ColumnsIncluding(x => x.TrackFileId)
|
||||
.Entity(new Track { TrackFileId = 0 })
|
||||
.Execute();
|
||||
var tracks = GetTracksByFileId(trackFileId);
|
||||
tracks.ForEach(x => x.TrackFileId = 0);
|
||||
SetFileId(tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace NzbDrone.Core.Music
|
||||
|
||||
public void SetAddOptions(IEnumerable<Album> albums)
|
||||
{
|
||||
_albumRepository.SetFields(albums, s => s.AddOptions);
|
||||
_albumRepository.SetFields(albums.ToList(), s => s.AddOptions);
|
||||
}
|
||||
|
||||
public PagingSpec<Album> AlbumsWithoutFiles(PagingSpec<Album> pagingSpec)
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Profiles.Metadata
|
||||
|
||||
public bool Exists(int id)
|
||||
{
|
||||
return DataMapper.Query<MetadataProfile>().Where(p => p.Id == id).GetRowCount() == 1;
|
||||
return Query(p => p.Id == id).Count == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Core.Profiles.Qualities
|
||||
|
||||
public bool Exists(int id)
|
||||
{
|
||||
return DataMapper.Query<QualityProfile>().Where(p => p.Id == id).GetRowCount() == 1;
|
||||
return Query(p => p.Id == id).Count == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Tags
|
||||
|
||||
public Tag GetByLabel(string label)
|
||||
{
|
||||
var model = Query.Where(c => c.Label == label).SingleOrDefault();
|
||||
var model = Query(c => c.Label == label).SingleOrDefault();
|
||||
|
||||
if (model == null)
|
||||
{
|
||||
@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Tags
|
||||
|
||||
public Tag FindByLabel(string label)
|
||||
{
|
||||
return Query.Where(c => c.Label == label).SingleOrDefault();
|
||||
return Query(c => c.Label == label).SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
using NzbDrone.Core.Datastore;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Dapper;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Reflection;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.ThingiProvider
|
||||
{
|
||||
public class ProviderRepository<TProviderDefinition> : BasicRepository<TProviderDefinition>, IProviderRepository<TProviderDefinition>
|
||||
where TProviderDefinition : ModelBase,
|
||||
where TProviderDefinition : ProviderDefinition,
|
||||
new()
|
||||
{
|
||||
protected ProviderRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
@@ -12,9 +17,40 @@ namespace NzbDrone.Core.ThingiProvider
|
||||
{
|
||||
}
|
||||
|
||||
// public void DeleteImplementations(string implementation)
|
||||
// {
|
||||
// DataMapper.Delete<TProviderDefinition>(c => c.Implementation == implementation);
|
||||
// }
|
||||
protected override List<TProviderDefinition> Query(SqlBuilder builder)
|
||||
{
|
||||
var type = typeof(TProviderDefinition);
|
||||
var sql = builder.Select(type).AddSelectTemplate(type);
|
||||
|
||||
var results = new List<TProviderDefinition>();
|
||||
|
||||
using (var conn = _database.OpenConnection())
|
||||
using (var reader = conn.ExecuteReader(sql.RawSql, sql.Parameters))
|
||||
{
|
||||
var parser = reader.GetRowParser<TProviderDefinition>(typeof(TProviderDefinition));
|
||||
var settingsIndex = reader.GetOrdinal(nameof(ProviderDefinition.Settings));
|
||||
var serializerSettings = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
var body = reader.IsDBNull(settingsIndex) ? null : reader.GetString(settingsIndex);
|
||||
var item = parser(reader);
|
||||
var impType = typeof(IProviderConfig).Assembly.FindTypeByName(item.ConfigContract);
|
||||
|
||||
if (body.IsNullOrWhiteSpace())
|
||||
{
|
||||
item.Settings = NullConfig.Instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Settings = (IProviderConfig)JsonSerializer.Deserialize(body, impType, serializerSettings);
|
||||
}
|
||||
|
||||
results.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.ThingiProvider.Status
|
||||
|
||||
public TModel FindByProviderId(int providerId)
|
||||
{
|
||||
return Query.Where(c => c.ProviderId == providerId).SingleOrDefault();
|
||||
return Query(c => c.ProviderId == providerId).SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user