Swap to dapper with lazyload

This commit is contained in:
ta264
2020-02-21 16:23:16 +00:00
parent 251f69fdfc
commit b50b0a1411
131 changed files with 3158 additions and 2439 deletions
@@ -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)
+248 -118
View File
@@ -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);
}
}
+21 -12
View File
@@ -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)
+5 -9
View File
@@ -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);
}
}
}
}
-28
View File
@@ -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;
}
}
}
+129
View File
@@ -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;
}
}
}
}
+4 -4
View File
@@ -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;
+4 -4
View File
@@ -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;
+168
View File
@@ -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;
}
}
}
}
+129
View File
@@ -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);
}
}
}
+123 -119
View File
@@ -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);
}
}
}
+377
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -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();
}
}
}
+52 -41
View File
@@ -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)");
}
}
}
}
@@ -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)
+2 -1
View File
@@ -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)
+3 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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;
}
}
}
+2 -2
View File
@@ -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();
}
}
}