mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-21 22:04:31 -04:00
Swap to dapper with lazyload
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user