Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added count and pagination. #58

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Dommel/Dommel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Description>Simple CRUD operations for Dapper.</Description>
<Copyright>Copyright © Henk Mollema 2017</Copyright>
<AssemblyTitle>Dommel</AssemblyTitle>
<VersionPrefix>1.8.1</VersionPrefix>
<VersionPrefix>1.8.3</VersionPrefix>
<Authors>Henk Mollema</Authors>
<TargetFrameworks>net45;net451;netstandard1.3</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand All @@ -26,4 +26,4 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.ComponentModel.Annotations" Version="4.1.0" />
</ItemGroup>
</Project>
</Project>
194 changes: 192 additions & 2 deletions src/Dommel/DommelMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static class DommelMapper
private static readonly ConcurrentDictionary<Type, string> _updateQueryCache = new ConcurrentDictionary<Type, string>();
private static readonly ConcurrentDictionary<Type, string> _deleteQueryCache = new ConcurrentDictionary<Type, string>();
private static readonly ConcurrentDictionary<Type, string> _deleteAllQueryCache = new ConcurrentDictionary<Type, string>();
private static readonly ConcurrentDictionary<Type, string> _getCountCache = new ConcurrentDictionary<Type, string>();

/// <summary>
/// Retrieves the entity of type <typeparamref name="TEntity"/> with the specified id.
Expand Down Expand Up @@ -867,6 +868,129 @@ private static string BuildSelectSql<TEntity>(Expression<Func<TEntity, bool>> pr
return sql;
}

/// <summary>
/// Count the entities matching the specified predicate.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="predicate">A predicate to filter the results.</param>
/// <returns>
/// Count of entities of type <typeparamref name="TEntity"/> matching the specified
/// <paramref name="predicate"/>.
/// </returns>
public static long Count<TEntity>(this IDbConnection connection, Expression<Func<TEntity, bool>> predicate)
{
DynamicParameters parameters;
var sql = BuildCountSql(predicate, out parameters);
return connection.ExecuteScalar<long>(sql, parameters);
}

/// <summary>
/// Selects all the entities matching the specified predicate.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="predicate">A predicate to filter the results.</param>
/// <returns>
/// A collection of entities of type <typeparamref name="TEntity"/> matching the specified
/// <paramref name="predicate"/>.
/// </returns>
public static Task<long> CountAsync<TEntity>(this IDbConnection connection, Expression<Func<TEntity, bool>> predicate)
{
DynamicParameters parameters;
var sql = BuildCountSql(predicate, out parameters);
return connection.ExecuteScalarAsync<long>(sql, parameters);
}

private static string BuildCountSql<TEntity>(Expression<Func<TEntity, bool>> predicate, out DynamicParameters parameters)
{
var type = typeof(TEntity);
string sql;
if (!_getCountCache.TryGetValue(type, out sql))
{
var tableName = Resolvers.Table(type);
sql = $"select count(*) from {tableName}";
_getCountCache.TryAdd(type, sql);
}

sql += new SqlExpression<TEntity>()
.Where(predicate)
.ToSql(out parameters);
return sql;
}

/// <summary>
/// Selects the paginated entities matching the specified predicate.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="predicate">A predicate to filter the results.</param>
/// <param name="orderByExpression">A expression to order by the results.</param>
/// <param name="orderByAsc">Order by type.</param>
/// <param name="pageNo">The page number of the results.</param>
/// <param name="pageSize">The page size of the results.</param>
/// <returns>
/// A collection of entities of type <typeparamref name="TEntity"/> matching the specified
/// <paramref name="predicate"/>.
/// </returns>
public static IEnumerable<TEntity> Query<TEntity>(this IDbConnection connection,
Expression<Func<TEntity, bool>> predicate,
Expression<Func<TEntity, object>> orderByExpression = null,
bool orderByAsc = true,
int? pageNo = null, int? pageSize = null)
{
var tableName = Resolvers.Table(typeof(TEntity));
var orderBySql = BuildOrderSql(orderByExpression, orderByAsc);

DynamicParameters parameters;
var sql = BuildSelectSql<TEntity>(predicate, out parameters);
if (pageNo == null || pageSize == null)
{
sql = $"{sql} {orderBySql}";
}
else
{
sql = BuildPaginationSql(connection, tableName, sql, orderBySql, orderByAsc, pageNo.Value, pageSize.Value);
}

return connection.Query<TEntity>(sql, (object) parameters);
}

private static string BuildOrderSql<TEntity>(Expression<Func<TEntity, object>> predicate, bool orderByAsc)
{
if (predicate != null)
{
MemberExpression memberExpression = null;

var predicateBodyNodeType = predicate.Body.NodeType;
if (predicateBodyNodeType == ExpressionType.Convert ||
predicateBodyNodeType == ExpressionType.ConvertChecked)
{
var unaryExpression = predicate.Body as UnaryExpression;
memberExpression = unaryExpression?.Operand as MemberExpression;
}
else if (predicate.Body.NodeType == ExpressionType.MemberAccess)
{
memberExpression = predicate.Body as MemberExpression;
}

if (memberExpression?.Expression != null)
{
var property = new SqlExpression<TEntity>().VisitMemberAccess(memberExpression);
return $" ORDER BY {property} " + (orderByAsc ? "ASC" : "DESC");
}
}

return "";
}

private static string BuildPaginationSql(IDbConnection connection, string tableName, string sql,
string orderBySql, bool orderByAsc, int pageNo, int pageSize)
{
var builder = GetBuilder(connection);
return builder.BuildPagination(tableName, sql, orderBySql, orderByAsc, pageNo, pageSize);
}

/// <summary>
/// Represents a typed SQL expression.
/// </summary>
Expand All @@ -884,7 +1008,10 @@ public class SqlExpression<TEntity>
/// <returns>The current <see cref="DommelMapper.SqlExpression&lt;TEntity&gt;"/> instance.</returns>
public virtual SqlExpression<TEntity> Where(Expression<Func<TEntity, bool>> expression)
{
AppendToWhere("and", expression);
if (expression != null)
{
AppendToWhere("and", expression);
}
return this;
}

Expand Down Expand Up @@ -1072,7 +1199,7 @@ protected virtual object VisitNew(NewExpression expression)
/// </summary>
/// <param name="expression">The member access expression.</param>
/// <returns>The result of the processing.</returns>
protected virtual object VisitMemberAccess(MemberExpression expression)
public virtual object VisitMemberAccess(MemberExpression expression)
{
if (expression.Expression != null && expression.Expression.NodeType == ExpressionType.Parameter)
{
Expand Down Expand Up @@ -2015,6 +2142,18 @@ public interface ISqlBuilder
/// </param>
/// <returns>An insert query including a query to fetch the new id.</returns>
string BuildInsert(string tableName, string[] columnNames, string[] paramNames, PropertyInfo keyProperty);

/// <summary>
/// Builds an pagination query using the specified page number and page size.
/// </summary>
/// <param name="tableName">The table name.</param>
/// <param name="whereSql">Sql including where</param>
/// <param name="orderBySql">Order by clause.</param>
/// <param name="orderByAsc">Order by type.</param>
/// <param name="pageNo">The page number.</param>
/// <param name="pageSize">The page size.</param>
/// <returns>The pagination query.</returns>
string BuildPagination(string tableName, string whereSql, string orderBySql, bool orderByAsc, int pageNo, int pageSize);
}

private sealed class SqlServerSqlBuilder : ISqlBuilder
Expand All @@ -2023,6 +2162,25 @@ public string BuildInsert(string tableName, string[] columnNames, string[] param
{
return $"set nocount on insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}) select cast(scope_identity() as int)";
}

public string BuildPagination(string tableName, string sql, string orderBySql, bool orderByAsc, int pageNo, int pageSize)
{
var start = pageNo >= 1 ? (pageNo - 1) * pageSize : 0;
var end = pageNo * pageSize;

if (string.IsNullOrWhiteSpace(orderBySql))
{
orderBySql = " ORDER BY ID ";
}

return
$" SELECT * FROM ( SELECT ROW_NUMBER() OVER ( {orderBySql} ) AS RowNum, * "+
$" FROM {tableName} " +
" ) AS RowConstrainedResult " +
$" WHERE RowNum >= {start} " +
$" AND RowNum <= {end} " +
" ORDER BY RowNum " + (orderByAsc ? "ASC" : "DESC");
}
}

private sealed class SqlServerCeSqlBuilder : ISqlBuilder
Expand All @@ -2031,6 +2189,20 @@ public string BuildInsert(string tableName, string[] columnNames, string[] param
{
return $"insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}) select cast(@@IDENTITY as int)";
}

public string BuildPagination(string tableName, string sql, string orderBySql, bool orderByAsc, int pageNo, int pageSize)
{
var start = pageNo >= 1 ? (pageNo - 1) * pageSize : 0;
var end = pageNo * pageSize;

return
$" SELECT * FROM ( SELECT ROW_NUMBER() OVER ( {orderBySql} ) AS RowNum, * "+
$" FROM {tableName} " +
" ) AS RowConstrainedResult " +
$" WHERE RowNum >= {start} " +
$" AND RowNum <= {end} " +
" ORDER BY RowNum " + (orderByAsc ? "ASC" : "DESC");
}
}

private sealed class SqliteSqlBuilder : ISqlBuilder
Expand All @@ -2039,6 +2211,12 @@ public string BuildInsert(string tableName, string[] columnNames, string[] param
{
return $"insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}); select last_insert_rowid() id";
}

public string BuildPagination(string tableName, string sql, string orderBySql, bool orderByAsc, int pageNo, int pageSize)
{
var start = pageNo >= 1 ? (pageNo - 1) * pageSize : 0;
return $" {sql} {orderBySql} LIMIT {start}, {pageSize} ";
}
}

private sealed class MySqlSqlBuilder : ISqlBuilder
Expand All @@ -2047,6 +2225,12 @@ public string BuildInsert(string tableName, string[] columnNames, string[] param
{
return $"insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}); select LAST_INSERT_ID() id";
}

public string BuildPagination(string tableName, string sql, string orderBySql, bool orderByAsc, int pageNo, int pageSize)
{
var start = pageNo >= 1 ? (pageNo - 1) * pageSize : 0;
return $" {sql} {orderBySql} LIMIT {start}, {pageSize} ";
}
}

private sealed class PostgresSqlBuilder : ISqlBuilder
Expand All @@ -2069,6 +2253,12 @@ public string BuildInsert(string tableName, string[] columnNames, string[] param

return sql;
}

public string BuildPagination(string tableName, string sql, string orderBySql, bool orderByAsc, int pageNo, int pageSize)
{
var start = pageNo >= 1 ? (pageNo - 1) * pageSize : 0;
return $" {sql} {orderBySql} OFFSET {start} LIMIT {pageSize} ";
}
}
#endregion
}
Expand Down