diff --git a/src/NzbDrone.Core.Test/Datastore/TableMapperFixture.cs b/src/NzbDrone.Core.Test/Datastore/TableMapperFixture.cs index 436344e2d..492e0a754 100644 --- a/src/NzbDrone.Core.Test/Datastore/TableMapperFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/TableMapperFixture.cs @@ -45,7 +45,7 @@ public void test_mappable_types() { var properties = typeof(TypeWithAllMappableProperties).GetProperties(); properties.Should().NotBeEmpty(); - properties.Should().OnlyContain(c => c.IsMappableProperty()); + properties.Should().OnlyContain(c => ColumnMapper.IsMappableProperty(c)); } [Test] @@ -53,7 +53,7 @@ public void test_un_mappable_types() { var properties = typeof(TypeWithNoMappableProperties).GetProperties(); properties.Should().NotBeEmpty(); - properties.Should().NotContain(c => c.IsMappableProperty()); + properties.Should().NotContain(c => ColumnMapper.IsMappableProperty(c)); } } } diff --git a/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs b/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs index 1d6b6cb3f..e954bdfde 100644 --- a/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/WhereBuilderFixture.cs @@ -24,7 +24,7 @@ public void MapTables() private WhereBuilder Where(Expression> filter) { - return new WhereBuilder(filter, true, 0); + return new WhereBuilder(filter, true); } [Test] @@ -32,8 +32,9 @@ public void where_equal_const() { _subject = Where(x => x.Id == 10); - _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @Clause1_P1)"); - _subject.Parameters.Get("Clause1_P1").Should().Be(10); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @{name})"); + _subject.Parameters.Get(name).Should().Be(10); } [Test] @@ -42,71 +43,44 @@ public void where_equal_variable() var id = 10; _subject = Where(x => x.Id == id); - _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @Clause1_P1)"); - _subject.Parameters.Get("Clause1_P1").Should().Be(id); - } - - [Test] - public void where_equal_property() - { - var movie = new Movie { Id = 10 }; - _subject = Where(x => x.Id == movie.Id); - - _subject.Parameters.ParameterNames.Should().HaveCount(1); - _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @Clause1_P1)"); - _subject.Parameters.Get("Clause1_P1").Should().Be(movie.Id); - } - - [Test] - public void where_equal_joined_property() - { - _subject = Where(x => x.Profile.Id == 1); - - _subject.Parameters.ParameterNames.Should().HaveCount(1); - _subject.ToString().Should().Be($"(\"Profiles\".\"Id\" = @Clause1_P1)"); - _subject.Parameters.Get("Clause1_P1").Should().Be(1); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = @{name})"); + _subject.Parameters.Get(name).Should().Be(id); } [Test] public void where_throws_without_concrete_condition_if_requiresConcreteCondition() { - Expression> filter = (x, y) => x.Id == y.Id; - _subject = new WhereBuilder(filter, true, 0); + var movie = new Movie(); + Expression> filter = (x) => x.Id == movie.Id; + _subject = new WhereBuilder(filter, true); Assert.Throws(() => _subject.ToString()); } [Test] public void where_allows_abstract_condition_if_not_requiresConcreteCondition() { - Expression> filter = (x, y) => x.Id == y.Id; - _subject = new WhereBuilder(filter, false, 0); + var movie = new Movie(); + Expression> filter = (x) => x.Id == movie.Id; + _subject = new WhereBuilder(filter, false); _subject.ToString().Should().Be($"(\"Movies\".\"Id\" = \"Movies\".\"Id\")"); } [Test] public void where_string_is_null() { - _subject = Where(x => x.CleanTitle == null); + _subject = Where(x => x.ImdbId == null); - _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" IS NULL)"); + _subject.ToString().Should().Be($"(\"Movies\".\"ImdbId\" IS NULL)"); } [Test] public void where_string_is_null_value() { - string cleanTitle = null; - _subject = Where(x => x.CleanTitle == cleanTitle); + string imdb = null; + _subject = Where(x => x.ImdbId == imdb); - _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" IS NULL)"); - } - - [Test] - public void where_equal_null_property() - { - var movie = new Movie { CleanTitle = null }; - _subject = Where(x => x.CleanTitle == movie.CleanTitle); - - _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" IS NULL)"); + _subject.ToString().Should().Be($"(\"Movies\".\"ImdbId\" IS NULL)"); } [Test] @@ -115,8 +89,9 @@ public void where_column_contains_string() var test = "small"; _subject = Where(x => x.CleanTitle.Contains(test)); - _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE '%' || @Clause1_P1 || '%')"); - _subject.Parameters.Get("Clause1_P1").Should().Be(test); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE '%' || @{name} || '%')"); + _subject.Parameters.Get(name).Should().Be(test); } [Test] @@ -125,8 +100,9 @@ public void where_string_contains_column() var test = "small"; _subject = Where(x => test.Contains(x.CleanTitle)); - _subject.ToString().Should().Be($"(@Clause1_P1 LIKE '%' || \"Movies\".\"CleanTitle\" || '%')"); - _subject.Parameters.Get("Clause1_P1").Should().Be(test); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(@{name} LIKE '%' || \"Movies\".\"CleanTitle\" || '%')"); + _subject.Parameters.Get(name).Should().Be(test); } [Test] @@ -135,8 +111,9 @@ public void where_column_starts_with_string() var test = "small"; _subject = Where(x => x.CleanTitle.StartsWith(test)); - _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE @Clause1_P1 || '%')"); - _subject.Parameters.Get("Clause1_P1").Should().Be(test); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE @{name} || '%')"); + _subject.Parameters.Get(name).Should().Be(test); } [Test] @@ -145,8 +122,9 @@ public void where_column_ends_with_string() var test = "small"; _subject = Where(x => x.CleanTitle.EndsWith(test)); - _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE '%' || @Clause1_P1)"); - _subject.Parameters.Get("Clause1_P1").Should().Be(test); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"CleanTitle\" LIKE '%' || @{name})"); + _subject.Parameters.Get(name).Should().Be(test); } [Test] @@ -155,9 +133,11 @@ public void where_in_list() var list = new List { 1, 2, 3 }; _subject = Where(x => list.Contains(x.Id)); - _subject.ToString().Should().Be($"(\"Movies\".\"Id\" IN (1, 2, 3))"); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Id\" IN @{name})"); - _subject.Parameters.ParameterNames.Should().BeEmpty(); + var param = _subject.Parameters.Get>(name); + param.Should().BeEquivalentTo(list); } [Test] @@ -166,33 +146,37 @@ public void where_in_list_2() var list = new List { 1, 2, 3 }; _subject = Where(x => x.CleanTitle == "test" && list.Contains(x.Id)); - _subject.ToString().Should().Be($"((\"Movies\".\"CleanTitle\" = @Clause1_P1) AND (\"Movies\".\"Id\" IN (1, 2, 3)))"); + var names = _subject.Parameters.ParameterNames.ToList(); + _subject.ToString().Should().Be($"((\"Movies\".\"CleanTitle\" = @{names[0]}) AND (\"Movies\".\"Id\" IN @{names[1]}))"); } [Test] public void enum_as_int() { - _subject = Where(x => x.Status == MovieStatusType.Announced); + _subject = Where(x => x.Status == MovieStatusType.Released); - _subject.ToString().Should().Be($"(\"Movies\".\"Status\" = @Clause1_P1)"); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" = @{name})"); } [Test] public void enum_in_list() { - var allowed = new List { MovieStatusType.Announced, MovieStatusType.InCinemas }; + var allowed = new List { MovieStatusType.InCinemas, MovieStatusType.Released }; _subject = Where(x => allowed.Contains(x.Status)); - _subject.ToString().Should().Be($"(\"Movies\".\"Status\" IN @Clause1_P1)"); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" IN @{name})"); } [Test] public void enum_in_array() { - var allowed = new MovieStatusType[] { MovieStatusType.Announced, MovieStatusType.InCinemas }; + var allowed = new MovieStatusType[] { MovieStatusType.InCinemas, MovieStatusType.Released }; _subject = Where(x => allowed.Contains(x.Status)); - _subject.ToString().Should().Be($"(\"Movies\".\"Status\" IN @Clause1_P1)"); + var name = _subject.Parameters.ParameterNames.First(); + _subject.ToString().Should().Be($"(\"Movies\".\"Status\" IN @{name})"); } } } diff --git a/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs b/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs index eb479eb76..4f4f4e063 100644 --- a/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs +++ b/src/NzbDrone.Core/Blacklisting/BlacklistRepository.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.Linq; +using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; @@ -34,11 +36,23 @@ public List BlacklistedByMovie(int movieId) return Query(x => x.MovieId == movieId); } - protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join((b, m) => b.MovieId == m.Id); - protected override IEnumerable PagedQuery(SqlBuilder sql) => _database.QueryJoined(sql, (bl, movie) => + private IEnumerable SelectJoined(SqlBuilder.Template sql) + { + using (var conn = _database.OpenConnection()) + { + return conn.Query( + sql.RawSql, + (bl, movie) => { bl.Movie = movie; return bl; - }); + }, + sql.Parameters) + .ToList(); + } + } + + protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join((b, m) => b.MovieId == m.Id); + protected override IEnumerable PagedSelector(SqlBuilder.Template sql) => SelectJoined(sql); } } diff --git a/src/NzbDrone.Core/Datastore/BasicRepository.cs b/src/NzbDrone.Core/Datastore/BasicRepository.cs index 3f2b02a35..d73160107 100644 --- a/src/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/src/NzbDrone.Core/Datastore/BasicRepository.cs @@ -47,6 +47,8 @@ public class BasicRepository : IBasicRepository protected readonly IDatabase _database; protected readonly string _table; + protected string _selectTemplate; + protected string _deleteTemplate; public BasicRepository(IDatabase database, IEventAggregator eventAggregator) { @@ -60,19 +62,42 @@ public BasicRepository(IDatabase database, IEventAggregator eventAggregator) 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(); + _properties = type.GetProperties().Where(x => !excluded.Contains(x.Name)).ToList(); _insertSql = GetInsertSql(); _updateSql = GetUpdateSql(_properties); + + _selectTemplate = $"SELECT /**select**/ FROM {_table} /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**orderby**/"; + _deleteTemplate = $"DELETE FROM {_table} /**where**/"; } - protected virtual SqlBuilder Builder() => new SqlBuilder(); + protected virtual SqlBuilder BuilderBase() => new SqlBuilder(); + protected virtual SqlBuilder Builder() => BuilderBase().SelectAll(); - protected virtual List Query(SqlBuilder builder) => _database.Query(builder).ToList(); + protected virtual IEnumerable GetResults(SqlBuilder.Template sql) + { + using (var conn = _database.OpenConnection()) + { + return conn.Query(sql.RawSql, sql.Parameters); + } + } - protected List Query(Expression> where) => Query(Builder().Where(where)); + protected List Query(Expression> where) + { + return Query(Builder().Where(where)); + } - protected virtual List QueryDistinct(SqlBuilder builder) => _database.QueryDistinct(builder).ToList(); + protected List Query(SqlBuilder builder) + { + return Query(builder, GetResults); + } + + protected List Query(SqlBuilder builder, Func> queryFunc) + { + var sql = builder.AddTemplate(_selectTemplate).LogQuery(); + + return queryFunc(sql).ToList(); + } public int Count() { @@ -172,7 +197,6 @@ private string GetInsertSql() 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); @@ -238,7 +262,7 @@ protected void Delete(Expression> where) protected void Delete(SqlBuilder builder) { - var sql = builder.AddDeleteTemplate(typeof(TModel)).LogQuery(); + var sql = builder.AddTemplate(_deleteTemplate).LogQuery(); using (var conn = _database.OpenConnection()) { @@ -344,7 +368,7 @@ public void SetFields(IList models, params Expression propertiesToUpdate) { var sb = new StringBuilder(); - sb.AppendFormat("UPDATE {0} SET ", _table); + sb.AppendFormat("update {0} set ", _table); for (var i = 0; i < propertiesToUpdate.Count; i++) { @@ -356,7 +380,7 @@ private string GetUpdateSql(List propertiesToUpdate) } } - sb.Append($" WHERE \"{_keyProperty.Name}\" = @{_keyProperty.Name}"); + sb.Append($" where \"{_keyProperty.Name}\" = @{_keyProperty.Name}"); return sb.ToString(); } @@ -365,8 +389,6 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction, { var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate); - SqlBuilderExtensions.LogQuery(sql, model); - connection.Execute(sql, model, transaction: transaction); } @@ -374,20 +396,15 @@ private void UpdateFields(IDbConnection connection, IDbTransaction transaction, { var sql = propertiesToUpdate == _properties ? _updateSql : GetUpdateSql(propertiesToUpdate); - foreach (var model in models) - { - SqlBuilderExtensions.LogQuery(sql, model); - } - connection.Execute(sql, models, transaction: transaction); } - protected virtual SqlBuilder PagedBuilder() => Builder(); - protected virtual IEnumerable PagedQuery(SqlBuilder sql) => Query(sql); + protected virtual SqlBuilder PagedBuilder() => BuilderBase(); + protected virtual IEnumerable PagedSelector(SqlBuilder.Template sql) => GetResults(sql); public virtual PagingSpec GetPaged(PagingSpec pagingSpec) { - pagingSpec.Records = GetPagedRecords(PagedBuilder(), pagingSpec, PagedQuery); + pagingSpec.Records = GetPagedRecords(PagedBuilder().SelectAll(), pagingSpec, PagedSelector); pagingSpec.TotalRecords = GetPagedRecordCount(PagedBuilder().SelectCount(), pagingSpec); return pagingSpec; @@ -403,7 +420,7 @@ private void AddFilters(SqlBuilder builder, PagingSpec pagingSpec) } } - protected List GetPagedRecords(SqlBuilder builder, PagingSpec pagingSpec, Func> queryFunc) + protected List GetPagedRecords(SqlBuilder builder, PagingSpec pagingSpec, Func> queryFunc) { AddFilters(builder, pagingSpec); @@ -411,22 +428,16 @@ protected List GetPagedRecords(SqlBuilder builder, PagingSpec pa var pagingOffset = (pagingSpec.Page - 1) * pagingSpec.PageSize; builder.OrderBy($"{pagingSpec.SortKey} {sortDirection} LIMIT {pagingSpec.PageSize} OFFSET {pagingOffset}"); - return queryFunc(builder).ToList(); + var sql = builder.AddTemplate(_selectTemplate).LogQuery(); + + return queryFunc(sql).ToList(); } - protected int GetPagedRecordCount(SqlBuilder builder, PagingSpec pagingSpec, string template = null) + protected int GetPagedRecordCount(SqlBuilder builder, PagingSpec pagingSpec) { AddFilters(builder, pagingSpec); - SqlBuilder.Template sql; - if (template != null) - { - sql = builder.AddTemplate(template).LogQuery(); - } - else - { - sql = builder.AddPageCountTemplate(typeof(TModel)); - } + var sql = builder.AddTemplate(_selectTemplate).LogQuery(); using (var conn = _database.OpenConnection()) { diff --git a/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs index cba52d2cb..8136ff427 100644 --- a/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs +++ b/src/NzbDrone.Core/Datastore/Extensions/BuilderExtensions.cs @@ -14,18 +14,12 @@ namespace NzbDrone.Core.Datastore { public static class SqlBuilderExtensions { + public static bool LogSql { get; set; } 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) + public static SqlBuilder SelectAll(this SqlBuilder builder) { - return builder.Select(types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", ")); - } - - public static SqlBuilder SelectDistinct(this SqlBuilder builder, params Type[] types) - { - return builder.Select("DISTINCT " + types.Select(x => TableMapping.Mapper.TableNameMapping(x) + ".*").Join(", ")); + return builder.Select("*"); } public static SqlBuilder SelectCount(this SqlBuilder builder) @@ -33,30 +27,23 @@ public static SqlBuilder SelectCount(this SqlBuilder builder) return builder.Select("COUNT(*)"); } - public static SqlBuilder SelectCountDistinct(this SqlBuilder builder, Expression> property) - { - var table = TableMapping.Mapper.TableNameMapping(typeof(TModel)); - var propName = property.GetMemberName().Name; - return builder.Select($"COUNT(DISTINCT \"{table}\".\"{propName}\")"); - } - public static SqlBuilder Where(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, true, builder.Sequence); + var wb = new WhereBuilder(filter, true); return builder.Where(wb.ToString(), wb.Parameters); } public static SqlBuilder OrWhere(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, true, builder.Sequence); + var wb = new WhereBuilder(filter, true); return builder.OrWhere(wb.ToString(), wb.Parameters); } public static SqlBuilder Join(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, false, builder.Sequence); + var wb = new WhereBuilder(filter, false); var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight)); @@ -65,76 +52,41 @@ public static SqlBuilder Join(this SqlBuilder builder, Expression public static SqlBuilder LeftJoin(this SqlBuilder builder, Expression> filter) { - var wb = new WhereBuilder(filter, false, builder.Sequence); + var wb = new WhereBuilder(filter, false); var rightTable = TableMapping.Mapper.TableNameMapping(typeof(TRight)); return builder.LeftJoin($"{rightTable} ON {wb.ToString()}"); } - public static SqlBuilder GroupBy(this SqlBuilder builder, Expression> 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); + var sb = new StringBuilder(); + sb.AppendLine(); + sb.AppendLine("==== Begin Query Trace ===="); + sb.AppendLine(); + sb.AppendLine("QUERY TEXT:"); + sb.AppendLine(template.RawSql); + sb.AppendLine(); + sb.AppendLine("PARAMETERS:"); + foreach (var p in ((DynamicParameters)template.Parameters).ToDictionary()) + { + object 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()); } 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 ToDictionary(this DynamicParameters dynamicParams) { var argsDictionary = new Dictionary(); @@ -147,21 +99,32 @@ private static Dictionary ToDictionary(this DynamicParameters dy } var templates = dynamicParams.GetType().GetField("templates", BindingFlags.NonPublic | BindingFlags.Instance); - if (templates != null && templates.GetValue(dynamicParams) is List list) + if (templates != null) { - foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList())) + var list = templates.GetValue(dynamicParams) as List; + if (list != null) { - objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value)); + foreach (var objProps in list.Select(obj => obj.GetPropertyValuePairs().ToList())) + { + objProps.ForEach(p => argsDictionary.Add(p.Key, p.Value)); + } } } return argsDictionary; } - private static Dictionary GetPropertyValuePairs(this object obj) + private static Dictionary GetPropertyValuePairs(this object obj, string[] hidden = null) { var type = obj.GetType(); - var pairs = type.GetProperties().Where(x => x.IsMappableProperty()) + var pairs = hidden == null + ? type.GetProperties() + .DistinctBy(propertyInfo => propertyInfo.Name) + .ToDictionary( + propertyInfo => propertyInfo.Name, + propertyInfo => propertyInfo.GetValue(obj, null)) + : type.GetProperties() + .Where(it => !hidden.Contains(it.Name)) .DistinctBy(propertyInfo => propertyInfo.Name) .ToDictionary( propertyInfo => propertyInfo.Name, diff --git a/src/NzbDrone.Core/Datastore/Extensions/MappingExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/MappingExtensions.cs deleted file mode 100644 index 7be628d96..000000000 --- a/src/NzbDrone.Core/Datastore/Extensions/MappingExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Linq.Expressions; -using System.Reflection; -using Dapper; -using NzbDrone.Common.Reflection; - -namespace NzbDrone.Core.Datastore -{ - public static class MappingExtensions - { - public static PropertyInfo GetMemberName(this Expression> member) - { - if (!(member.Body is MemberExpression memberExpression)) - { - memberExpression = (member.Body as UnaryExpression).Operand as MemberExpression; - } - - return (PropertyInfo)memberExpression.Member; - } - - public static bool IsMappableProperty(this MemberInfo memberInfo) - { - var propertyInfo = memberInfo as PropertyInfo; - - if (propertyInfo == null) - { - return false; - } - - if (!propertyInfo.IsReadable() || !propertyInfo.IsWritable()) - { - return false; - } - - // 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; - } - - return false; - } - } -} diff --git a/src/NzbDrone.Core/Datastore/Extensions/SqlMapperExtensions.cs b/src/NzbDrone.Core/Datastore/Extensions/SqlMapperExtensions.cs deleted file mode 100644 index 801961c30..000000000 --- a/src/NzbDrone.Core/Datastore/Extensions/SqlMapperExtensions.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using Dapper; - -namespace NzbDrone.Core.Datastore -{ - public static class SqlMapperExtensions - { - public static IEnumerable Query(this IDatabase db, string sql, object param = null) - { - using (var conn = db.OpenConnection()) - { - var items = SqlMapper.Query(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 Query(this IDatabase db, string sql, Func 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 result = null; - using (var conn = db.OpenConnection()) - { - result = SqlMapper.Query(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - return result; - } - - public static IEnumerable Query(this IDatabase db, string sql, Func 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 result = null; - using (var conn = db.OpenConnection()) - { - result = SqlMapper.Query(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - return result; - } - - public static IEnumerable Query(this IDatabase db, string sql, Func 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 result = null; - using (var conn = db.OpenConnection()) - { - result = SqlMapper.Query(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - return result; - } - - public static IEnumerable Query(this IDatabase db, string sql, Func 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 result = null; - using (var conn = db.OpenConnection()) - { - result = SqlMapper.Query(conn, sql, MapWithLazy, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - return result; - } - - public static IEnumerable Query(this IDatabase db, SqlBuilder builder) - { - var type = typeof(T); - var sql = builder.Select(type).AddSelectTemplate(type); - - return db.Query(sql.RawSql, sql.Parameters); - } - - public static IEnumerable QueryDistinct(this IDatabase db, SqlBuilder builder) - { - var type = typeof(T); - var sql = builder.SelectDistinct(type).AddSelectTemplate(type); - - return db.Query(sql.RawSql, sql.Parameters); - } - - public static IEnumerable QueryJoined(this IDatabase db, SqlBuilder builder, Func 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 QueryJoined(this IDatabase db, SqlBuilder builder, Func 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 QueryJoined(this IDatabase db, SqlBuilder builder, Func 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 QueryJoined(this IDatabase db, SqlBuilder builder, Func 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(IDatabase db, TModel model) - { - if (TableMapping.Mapper.LazyLoadList.TryGetValue(typeof(TModel), out var lazyProperties)) - { - ApplyLazyLoad(db, model, lazyProperties); - } - } - - private static void ApplyLazyLoad(IDatabase db, TModel model, List 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); - } - } - } -} diff --git a/src/NzbDrone.Core/Datastore/LazyLoaded.cs b/src/NzbDrone.Core/Datastore/LazyLoaded.cs deleted file mode 100644 index 91ff44c82..000000000 --- a/src/NzbDrone.Core/Datastore/LazyLoaded.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using NLog; -using NzbDrone.Common.Instrumentation; - -namespace NzbDrone.Core.Datastore -{ - public interface ILazyLoaded : ICloneable - { - bool IsLoaded { get; } - void Prepare(IDatabase database, object parent); - void LazyLoad(); - } - - /// - /// Allows a field to be lazy loaded. - /// - /// - public class LazyLoaded : 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 val) - { - return new LazyLoaded(val); - } - - public static implicit operator TChild(LazyLoaded 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; - } - } - - /// - /// This is the lazy loading proxy. - /// - /// The parent entity that contains the lazy loaded entity. - /// The child entity that is being lazy loaded. - internal class LazyLoaded : LazyLoaded - { - private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(LazyLoaded)); - - private readonly Func _query; - private readonly Func _condition; - - private IDatabase _database; - private TParent _parent; - - public LazyLoaded(TChild val) - : base(val) - { - _value = val; - IsLoaded = true; - } - - internal LazyLoaded(Func query, Func condition = null) - { - _query = query; - _condition = condition; - } - - public static implicit operator LazyLoaded(TChild val) - { - return new LazyLoaded(val); - } - - public static implicit operator TChild(LazyLoaded 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)) - { - if (SqlBuilderExtensions.LogSql) - { - Logger.Trace($"Lazy loading {typeof(TChild)} for {typeof(TParent)}"); - Logger.Trace("StackTrace: '{0}'", Environment.StackTrace); - } - - _value = _query(_database, _parent); - } - else - { - _value = default; - } - - IsLoaded = true; - } - } - } -} diff --git a/src/NzbDrone.Core/Datastore/SqlBuilder.cs b/src/NzbDrone.Core/Datastore/SqlBuilder.cs deleted file mode 100644 index e686d4852..000000000 --- a/src/NzbDrone.Core/Datastore/SqlBuilder.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using Dapper; - -namespace NzbDrone.Core.Datastore -{ - public class SqlBuilder - { - private readonly Dictionary _data = new Dictionary(); - - 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 - { - 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; - } - } - } -} diff --git a/src/NzbDrone.Core/Datastore/TableMapper.cs b/src/NzbDrone.Core/Datastore/TableMapper.cs index 0dbfca678..8a8e19b73 100644 --- a/src/NzbDrone.Core/Datastore/TableMapper.cs +++ b/src/NzbDrone.Core/Datastore/TableMapper.cs @@ -3,36 +3,48 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Dapper; +using NzbDrone.Common.Reflection; namespace NzbDrone.Core.Datastore { + public static class MappingExtensions + { + public static PropertyInfo GetMemberName(this Expression> member) + { + var memberExpression = member.Body as MemberExpression; + if (memberExpression == null) + { + memberExpression = (member.Body as UnaryExpression).Operand as MemberExpression; + } + + return (PropertyInfo)memberExpression.Member; + } + } + public class TableMapper { public TableMapper() { IgnoreList = new Dictionary>(); - LazyLoadList = new Dictionary>(); TableMap = new Dictionary(); } public Dictionary> IgnoreList { get; set; } - public Dictionary> LazyLoadList { get; set; } public Dictionary TableMap { get; set; } public ColumnMapper Entity(string tableName) - where TEntity : ModelBase { - var type = typeof(TEntity); - TableMap.Add(type, tableName); + TableMap.Add(typeof(TEntity), tableName); - if (IgnoreList.TryGetValue(type, out var list)) + if (IgnoreList.TryGetValue(typeof(TEntity), out var list)) { - return new ColumnMapper(list, LazyLoadList[type]); + return new ColumnMapper(list); } - IgnoreList[type] = new List(); - LazyLoadList[type] = new List(); - return new ColumnMapper(IgnoreList[type], LazyLoadList[type]); + list = new List(); + IgnoreList[typeof(TEntity)] = list; + return new ColumnMapper(list); } public List ExcludeProperties(Type x) @@ -44,44 +56,21 @@ 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 - where T : ModelBase { private readonly List _ignoreList; - private readonly List _lazyLoadList; - public ColumnMapper(List ignoreList, List lazyLoadList) + public ColumnMapper(List ignoreList) { _ignoreList = ignoreList; - _lazyLoadList = lazyLoadList; } public ColumnMapper AutoMapPropertiesWhere(Func predicate) { - var properties = typeof(T).GetProperties(); + Type entityType = typeof(T); + var properties = entityType.GetProperties(); _ignoreList.AddRange(properties.Where(x => !predicate(x))); return this; @@ -89,7 +78,7 @@ public ColumnMapper AutoMapPropertiesWhere(Func predicate public ColumnMapper RegisterModel() { - return AutoMapPropertiesWhere(x => x.IsMappableProperty()); + return AutoMapPropertiesWhere(IsMappableProperty); } public ColumnMapper Ignore(Expression> property) @@ -98,31 +87,30 @@ public ColumnMapper Ignore(Expression> property) return this; } - public ColumnMapper LazyLoad(Expression>> property, Func query, Func condition) + public static bool IsMappableProperty(MemberInfo memberInfo) { - var lazyLoad = new LazyLoaded(query, condition); + var propertyInfo = memberInfo as PropertyInfo; - var item = new LazyLoadedProperty + if (propertyInfo == null) { - Property = property.GetMemberName(), - LazyLoad = lazyLoad - }; + return false; + } - _lazyLoadList.Add(item); + if (!propertyInfo.IsReadable() || !propertyInfo.IsWritable()) + { + return false; + } - return this; - } + // 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; + } - public ColumnMapper HasOne(Expression>> portalExpression, Func childIdSelector) - where TChild : ModelBase - { - return LazyLoad(portalExpression, - (db, parent) => - { - var id = childIdSelector(parent); - return db.Query(new SqlBuilder().Where(x => x.Id == id)).SingleOrDefault(); - }, - parent => childIdSelector(parent) > 0); + return false; } } } diff --git a/src/NzbDrone.Core/Datastore/WhereBuilder.cs b/src/NzbDrone.Core/Datastore/WhereBuilder.cs index e42d0e990..24b3b17e3 100644 --- a/src/NzbDrone.Core/Datastore/WhereBuilder.cs +++ b/src/NzbDrone.Core/Datastore/WhereBuilder.cs @@ -19,9 +19,9 @@ public class WhereBuilder : ExpressionVisitor private int _paramCount = 0; private bool _gotConcreteValue = false; - public WhereBuilder(Expression filter, bool requireConcreteValue, int seq) + public WhereBuilder(Expression filter, bool requireConcreteValue) { - _paramNamePrefix = string.Format("Clause{0}", seq + 1); + _paramNamePrefix = Guid.NewGuid().ToString().Replace("-", "_"); _requireConcreteValue = requireConcreteValue; _sb = new StringBuilder(); @@ -87,16 +87,16 @@ protected override Expression VisitMethodCall(MethodCallExpression 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); + var tableName = expression != null ? TableMapping.Mapper.TableNameMapping(expression.Expression.Type) : null; - // Only use the SQL condition if the expression didn't resolve to an actual value - if (tableName != null && !gotValue) + if (tableName != null) { _sb.Append($"\"{tableName}\".\"{expression.Member.Name}\""); } else { + var value = GetRightValue(expression); + if (value != null) { // string is IEnumerable but we don't want to pick up that case @@ -138,43 +138,33 @@ protected override Expression VisitConstant(ConstantExpression expression) private bool TryGetConstantValue(Expression expression, out object result) { - result = null; - if (expression is ConstantExpression constExp) { result = constExp.Value; return true; } + result = null; 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 container = (nested.Expression as ConstantExpression).Value; var entity = GetFieldValue(container, nested.Member); result = GetFieldValue(entity, expression.Member); return true; } + result = null; 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) { @@ -182,31 +172,30 @@ private bool TryGetVariableValue(MemberExpression expression, out object result) return true; } + result = null; return false; } - private bool TryGetRightValue(Expression expression, out object value) + private object GetRightValue(Expression expression) { - value = null; - - if (TryGetConstantValue(expression, out value)) + if (TryGetConstantValue(expression, out var constValue)) { - return true; + return constValue; } var memberExp = expression as MemberExpression; - if (TryGetPropertyValue(memberExp, out value)) + if (TryGetPropertyValue(memberExp, out var propValue)) { - return true; + return propValue; } - if (TryGetVariableValue(memberExp, out value)) + if (TryGetVariableValue(memberExp, out var variableValue)) { - return true; + return variableValue; } - return false; + return null; } private object GetFieldValue(object entity, MemberInfo member) @@ -235,8 +224,8 @@ private bool IsNullVariable(Expression expression) if (expression.NodeType == ExpressionType.MemberAccess && expression is MemberExpression member && - ((TryGetPropertyValue(member, out var result) && result == null) || - (TryGetVariableValue(member, out result) && result == null))) + TryGetVariableValue(member, out var variableResult) && + variableResult == null) { return true; } @@ -275,7 +264,7 @@ private void ParseContainsExpression(MethodCallExpression expression) { var list = expression.Object; - if (list != null && (list.Type == typeof(string) || list.Type == typeof(List))) + if (list != null && list.Type == typeof(string)) { ParseStringContains(expression); return; @@ -315,20 +304,7 @@ private void ParseEnumerableContains(MethodCallExpression body) _sb.Append(" IN "); - // hardcode the integer list if it exists to bypass parameter limit - if (item.Type == typeof(int) && TryGetRightValue(list, out var value)) - { - var items = (IEnumerable)value; - _sb.Append("("); - _sb.Append(string.Join(", ", items)); - _sb.Append(")"); - - _gotConcreteValue = true; - } - else - { - Visit(list); - } + Visit(list); _sb.Append(")"); } diff --git a/src/NzbDrone.Core/History/HistoryRepository.cs b/src/NzbDrone.Core/History/HistoryRepository.cs index b1237e9df..d3c9da069 100644 --- a/src/NzbDrone.Core/History/HistoryRepository.cs +++ b/src/NzbDrone.Core/History/HistoryRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Dapper; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Movies; @@ -73,17 +74,28 @@ public void DeleteForMovie(int movieId) Delete(c => c.MovieId == movieId); } - protected override SqlBuilder PagedBuilder() => new SqlBuilder() - .Join((h, m) => h.MovieId == m.Id) - .Join((m, p) => m.ProfileId == p.Id); - - protected override IEnumerable PagedQuery(SqlBuilder sql) => - _database.QueryJoined(sql, (hist, movie, profile) => + private IEnumerable SelectJoined(SqlBuilder.Template sql) + { + using (var conn = _database.OpenConnection()) + { + return conn.Query( + sql.RawSql, + (hist, movie, profile) => { hist.Movie = movie; hist.Movie.Profile = profile; return hist; - }); + }, + sql.Parameters) + .ToList(); + } + } + + protected override SqlBuilder PagedBuilder() => new SqlBuilder() + .Join((h, m) => h.MovieId == m.Id) + .Join((m, p) => m.ProfileId == p.Id); + + protected override IEnumerable PagedSelector(SqlBuilder.Template sql) => SelectJoined(sql); public MovieHistory MostRecentForMovie(int movieId) { diff --git a/src/NzbDrone.Core/Movies/MovieRepository.cs b/src/NzbDrone.Core/Movies/MovieRepository.cs index 03f55de70..dedcbf4dd 100644 --- a/src/NzbDrone.Core/Movies/MovieRepository.cs +++ b/src/NzbDrone.Core/Movies/MovieRepository.cs @@ -40,7 +40,7 @@ public MovieRepository(IMainDatabase database, _profileRepository = profileRepository; } - protected override SqlBuilder Builder() => new SqlBuilder() + protected override SqlBuilder BuilderBase() => new SqlBuilder() .Join((m, p) => m.ProfileId == p.Id) .LeftJoin((m, t) => m.Id == t.MovieId) .LeftJoin((m, f) => m.Id == f.MovieId); @@ -65,33 +65,40 @@ private Movie Map(Dictionary dict, Movie movie, Profile profile, Alt return movieEntry; } - protected override List Query(SqlBuilder builder) + protected override IEnumerable GetResults(SqlBuilder.Template sql) { var movieDictionary = new Dictionary(); - _ = _database.QueryJoined( - builder, - (movie, profile, altTitle, file) => Map(movieDictionary, movie, profile, altTitle, file)); + using (var conn = _database.OpenConnection()) + { + conn.Query( + sql.RawSql, + (movie, profile, altTitle, file) => Map(movieDictionary, movie, profile, altTitle, file), + sql.Parameters); + } - return movieDictionary.Values.ToList(); + return movieDictionary.Values; } public override IEnumerable All() { // the skips the join on profile and populates manually // to avoid repeatedly deserializing the same profile - var builder = new SqlBuilder() - .LeftJoin((m, t) => m.Id == t.MovieId) - .LeftJoin((m, f) => m.Id == f.MovieId); + var noProfileTemplate = $"SELECT /**select**/ FROM {_table} /**leftjoin**/ /**where**/ /**orderby**/"; + var sql = Builder().AddTemplate(noProfileTemplate).LogQuery(); var movieDictionary = new Dictionary(); var profiles = _profileRepository.All().ToDictionary(x => x.Id); - _ = _database.QueryJoined( - builder, - (movie, altTitle, file) => Map(movieDictionary, movie, profiles[movie.ProfileId], altTitle, file)); + using (var conn = _database.OpenConnection()) + { + conn.Query( + sql.RawSql, + (movie, altTitle, file) => Map(movieDictionary, movie, profiles[movie.ProfileId], altTitle, file), + sql.Parameters); + } - return movieDictionary.Values.ToList(); + return movieDictionary.Values; } public bool MoviePathExists(string path) @@ -156,24 +163,23 @@ public List MoviesBetweenDates(DateTime start, DateTime end, bool include return Query(builder); } - public SqlBuilder MoviesWithoutFilesBuilder() => Builder() - .Where(x => x.MovieFileId == 0); + public SqlBuilder MoviesWithoutFilesBuilder() => BuilderBase().Where(x => x.MovieFileId == 0); public PagingSpec MoviesWithoutFiles(PagingSpec pagingSpec) { - pagingSpec.Records = GetPagedRecords(MoviesWithoutFilesBuilder(), pagingSpec, PagedQuery); + pagingSpec.Records = GetPagedRecords(MoviesWithoutFilesBuilder().SelectAll(), pagingSpec, PagedSelector); pagingSpec.TotalRecords = GetPagedRecordCount(MoviesWithoutFilesBuilder().SelectCount(), pagingSpec); return pagingSpec; } - public SqlBuilder MoviesWhereCutoffUnmetBuilder(List qualitiesBelowCutoff) => Builder() + public SqlBuilder MoviesWhereCutoffUnmetBuilder(List qualitiesBelowCutoff) => BuilderBase() .Where(x => x.MovieFileId != 0) .Where(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)); public PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) { - pagingSpec.Records = GetPagedRecords(MoviesWhereCutoffUnmetBuilder(qualitiesBelowCutoff), pagingSpec, PagedQuery); + pagingSpec.Records = GetPagedRecords(MoviesWhereCutoffUnmetBuilder(qualitiesBelowCutoff).SelectAll(), pagingSpec, PagedSelector); pagingSpec.TotalRecords = GetPagedRecordCount(MoviesWhereCutoffUnmetBuilder(qualitiesBelowCutoff).SelectCount(), pagingSpec); return pagingSpec; diff --git a/src/NzbDrone.Core/Profiles/ProfileRepository.cs b/src/NzbDrone.Core/Profiles/ProfileRepository.cs index 8d312a53c..ed5616da8 100644 --- a/src/NzbDrone.Core/Profiles/ProfileRepository.cs +++ b/src/NzbDrone.Core/Profiles/ProfileRepository.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Dapper; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.Messaging.Events; @@ -23,11 +24,11 @@ public ProfileRepository(IMainDatabase database, _customFormatService = customFormatService; } - protected override List Query(SqlBuilder builder) + protected override IEnumerable GetResults(SqlBuilder.Template sql) { var cfs = _customFormatService.All().ToDictionary(c => c.Id); - var profiles = base.Query(builder); + var profiles = base.GetResults(sql); // Do the conversions from Id to full CustomFormat object here instead of in // CustomFormatIntConverter to remove need to for a static property containing diff --git a/src/NzbDrone.Core/Radarr.Core.csproj b/src/NzbDrone.Core/Radarr.Core.csproj index 6f9c408d4..ecfd8a5b7 100644 --- a/src/NzbDrone.Core/Radarr.Core.csproj +++ b/src/NzbDrone.Core/Radarr.Core.csproj @@ -4,6 +4,7 @@ + diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs index 66bbe559a..c78b29d7d 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderRepository.cs @@ -16,18 +16,15 @@ protected ProviderRepository(IMainDatabase database, IEventAggregator eventAggre { } - protected override List Query(SqlBuilder builder) + protected override IEnumerable GetResults(SqlBuilder.Template sql) { - var type = typeof(TProviderDefinition); - var sql = builder.Select(type).AddSelectTemplate(type); - var results = new List(); using (var conn = _database.OpenConnection()) using (var reader = conn.ExecuteReader(sql.RawSql, sql.Parameters)) { var parser = reader.GetRowParser(typeof(TProviderDefinition)); - var settingsIndex = reader.GetOrdinal(nameof(ProviderDefinition.Settings)); + var settingsIndex = reader.GetOrdinal("Settings"); var serializerSettings = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; while (reader.Read())