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 offset and fetch functions to the SqlBuilder to allow pagination #40

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
81 changes: 76 additions & 5 deletions Dapper.GraphQL/Contexts/SqlQueryContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ public SqlQueryContext(string alias = null, dynamic parameters = null)
}
}

public class SqlQueryContext
public class SqlQueryContext
{
protected List<string> _splitOn;
protected List<Type> _types;

public DynamicParameters Parameters { get; set; }
protected Dapper.SqlBuilder SqlBuilder { get; set; }
protected DapperSqlBuilder SqlBuilder { get; set; }
protected Dapper.SqlBuilder.Template QueryTemplate { get; set; }

public SqlQueryContext(string from, dynamic parameters = null)
{
_splitOn = new List<string>();
_types = new List<Type>();
Parameters = new DynamicParameters(parameters);
SqlBuilder = new Dapper.SqlBuilder();
SqlBuilder = new DapperSqlBuilder();

// See https://github.com/StackExchange/Dapper/blob/master/Dapper.SqlBuilder/SqlBuilder.cs
QueryTemplate = SqlBuilder.AddTemplate($@"SELECT
/**select**/
FROM {from}/**innerjoin**//**leftjoin**//**rightjoin**//**join**/
/**where**//**orderby**/");
/**where**//**orderby**//**offset**//**top**/");
}

/// <summary>
Expand Down Expand Up @@ -178,7 +178,7 @@ public IEnumerable<TEntityType> Execute<TEntityType>(
/// <param name="options">The options for the query (optional).</param>
/// <returns>A list of entities returned by the query.</returns>
public async Task<IEnumerable<TEntityType>> ExecuteAsync<TEntityType>(
IDbConnection connection,
IDbConnection connection,
IHaveSelectionSet selectionSet,
IEntityMapper<TEntityType> mapper = null,
IDbTransaction transaction = null,
Expand Down Expand Up @@ -348,6 +348,77 @@ public SqlQueryContext OrderBy(string orderBy, dynamic parameters = null)
return this;
}

/// <summary>
/// Adds an Offset clause to allow pagination (it will skip N rows)
/// </summary>
/// <remarks>
/// Order by clause is a must when using offset
/// </remarks>
/// <example>
/// var queryBuilder = new SqlQueryBuilder();
/// queryBuilder.From("Customer customer");
/// queryBuilder.Select(
/// "customer.id",
/// "customer.name",
/// );
/// queryBuilder.SplitOn<Customer>("id");
/// queryBuilder.Where("customer.id == @id");
/// queryBuilder.Parameters.Add("id", 1);
/// queryBuilder.Orderby("customer.name");
/// queryBuilder.Offset(20);
/// var customer = queryBuilder
/// .Execute<Customer>(dbConnection, graphQLSelectionSet);
/// .FirstOrDefault();
///
/// // SELECT customer.id, customer.name
/// // FROM Customer customer
/// // WHERE customer.id == @id
/// // ORDER BY customer.name
/// </example>
/// <param name="rowsToSkip">total of rows to skip</param>
/// <returns>The query builder</returns>
public SqlQueryContext Offset(int rowsToSkip)
{
SqlBuilder.Offset(rowsToSkip);
return this;
}

/// <summary>
/// Adds a fetch clause to allow pagination
/// </summary>
/// <remarks>
/// Order by clause is a must when using fetch
/// </remarks>
/// <example>
/// var queryBuilder = new SqlQueryBuilder();
/// queryBuilder.From("Customer customer");
/// queryBuilder.Select(
/// "customer.id",
/// "customer.name",
/// );
/// queryBuilder.SplitOn<Customer>("id");
/// queryBuilder.Where("customer.id == @id");
/// queryBuilder.Parameters.Add("id", 1);
/// queryBuilder.Orderby("customer.name");
/// queryBuilder.Offset(20);
/// queryBuilder.Fetch(10);
/// var customer = queryBuilder
/// .Execute<Customer>(dbConnection, graphQLSelectionSet);
/// .FirstOrDefault();
///
/// // SELECT customer.id, customer.name
/// // FROM Customer customer
/// // WHERE customer.id == @id
/// // ORDER BY customer.name
/// </example>
/// <param name="rowsToReturn">total of rows to return</param>
/// <returns>The query builder.</returns>
public SqlQueryContext Fetch(int rowsToReturn)
{
SqlBuilder.Fetch(rowsToReturn);
return this;
}

/// <summary>
/// Adds a WHERE clause to the query, joining it with the previous with an 'OR' operator if needed.
/// </summary>
Expand Down
86 changes: 86 additions & 0 deletions Dapper.GraphQL/DapperSqlBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
namespace Dapper.GraphQL
{
/// <summary>
/// A builder for SQL queries and statements inheriting the official Dapper.Sql Builder to extend its functions.
/// </summary>
public class DapperSqlBuilder : Dapper.SqlBuilder
{
/// <summary>
/// If the object has an offset there is no need to the fetch function to add an offset with 0 rows to skip
/// (offset clause is a must when using the fetch clause)
/// </summary>
private bool _hasOffset = false;

/// <summary>
/// Adds an Offset clause to allow pagination (it will skip N rows)
/// </summary>
/// <remarks>
/// Order by clause is a must when using offset
/// </remarks>
/// <example>
/// var queryBuilder = new SqlQueryBuilder();
/// queryBuilder.From("Customer customer");
/// queryBuilder.Select(
/// "customer.id",
/// "customer.name",
/// );
/// queryBuilder.SplitOn<Customer>("id");
/// queryBuilder.Where("customer.id == @id");
/// queryBuilder.Parameters.Add("id", 1);
/// queryBuilder.Orderby("customer.name");
/// queryBuilder.Offset(20);
/// var customer = queryBuilder
/// .Execute<Customer>(dbConnection, graphQLSelectionSet);
/// .FirstOrDefault();
///
/// // SELECT customer.id, customer.name
/// // FROM Customer customer
/// // WHERE customer.id == @id
/// // ORDER BY customer.name
/// </example>
/// <param name="rowsToSkip">total of rows to skip</param>
/// <returns>The query builder</returns>
public DapperSqlBuilder Offset(int rowsToSkip)
{
_hasOffset = true;
return AddClause("offset", $"{rowsToSkip}", null, " + ", "OFFSET ", " ROWS\n", false) as DapperSqlBuilder;
}

/// <summary>
/// Adds a fetch clause to allow pagination
/// </summary>
/// <remarks>
/// Order by clause is a must when using fetch
/// </remarks>
/// <example>
/// var queryBuilder = new SqlQueryBuilder();
/// queryBuilder.From("Customer customer");
/// queryBuilder.Select(
/// "customer.id",
/// "customer.name",
/// );
/// queryBuilder.SplitOn<Customer>("id");
/// queryBuilder.Where("customer.id == @id");
/// queryBuilder.Parameters.Add("id", 1);
/// queryBuilder.Orderby("customer.name");
/// queryBuilder.Offset(20);
/// queryBuilder.Fetch(10);
/// var customer = queryBuilder
/// .Execute<Customer>(dbConnection, graphQLSelectionSet);
/// .FirstOrDefault();
///
/// // SELECT customer.id, customer.name
/// // FROM Customer customer
/// // WHERE customer.id == @id
/// // ORDER BY customer.name
/// </example>
/// <param name="rowsToReturn">total of rows to return</param>
/// <returns>The query builder.</returns>
public DapperSqlBuilder Fetch(int rowsToReturn)
{
if(!_hasOffset)
Offset(0);
return AddClause("fetch", $"{rowsToReturn}", null, " + ", "FETCH FIRST ", " ROWS ONLY\n", false) as DapperSqlBuilder;
}
}
}