-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
89635a9
commit 305cb60
Showing
5 changed files
with
226 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
Source/BSN.Commons.Orm.EntityFrameworkCore/DynamicFilterableRepositoryBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
using BSN.Commons.Extensions; | ||
using BSN.Commons.Infrastructure; | ||
using BSN.Commons.Orm.EntityFrameworkCore.Extensions; | ||
using Sieve.Exceptions; | ||
using Sieve.Models; | ||
using Sieve.Services; | ||
using System; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
|
||
namespace BSN.Commons.Orm.EntityFrameworkCore | ||
{ | ||
/// <summary> | ||
/// Default implementation of <see cref="IDynamicFilterableRepository{T}"/> | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
public class DynamicFilterableRepositoryBase<T> : RepositoryBase<T>, IDynamicFilterableRepository<T> where T : class | ||
{ | ||
/// <inheritdoc /> | ||
protected DynamicFilterableRepositoryBase(IDatabaseFactory databaseFactory, ISieveProcessor sieveProcessor) : base(databaseFactory) | ||
{ | ||
SieveProcessor = sieveProcessor ?? throw new ArgumentNullException(nameof(sieveProcessor)); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public PagedEntityCollection<T> GetMany(Expression<Func<T, bool>> where, string filters, string sorts, uint pageNumber, uint pageSize) | ||
{ | ||
if (pageNumber == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); | ||
|
||
if (pageSize == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); | ||
|
||
IQueryable<T> query = dbSet.Where(where); | ||
|
||
try | ||
{ | ||
query = SieveProcessor.Apply(new SieveModel() { Filters = filters, Sorts = sorts, PageSize = (int?)pageSize, Page = (int?)pageNumber }, query, applyPagination: false); | ||
} | ||
catch (SieveException ex) | ||
{ | ||
throw new InvalidOperationException(message: ex.ExtractMessage()); | ||
} | ||
|
||
return query.Paginate(pageNumber, pageSize); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public PagedEntityCollection<T> GetMany(string filters, string sorts, uint pageNumber, uint pageSize) | ||
{ | ||
return GetMany((entity) => true, filters, sorts, pageNumber, pageSize); | ||
} | ||
|
||
/// <summary> | ||
/// The engine of filtering | ||
/// </summary> | ||
/// <remarks> | ||
/// So if you want to any extra filtering or changing behaviour of current filtering, you have to use this engine | ||
/// </remarks> | ||
protected ISieveProcessor SieveProcessor { get; } | ||
} | ||
} |
124 changes: 62 additions & 62 deletions
124
Source/BSN.Commons.Orm.EntityFrameworkCore/Extensions/IQueryableExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,63 +1,63 @@ | ||
using Microsoft.EntityFrameworkCore; | ||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace BSN.Commons.Extensions | ||
{ | ||
using Microsoft.EntityFrameworkCore; | ||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
namespace BSN.Commons.Extensions | ||
{ | ||
/// <summary> | ||
/// The place for IQuerubable extenstions | ||
/// </summary> | ||
public static partial class IQueryableExtensions | ||
{ | ||
/// <summary> | ||
/// Paginate IQueryable of <typeparamref name="T"/> | ||
/// with given pageNumber and pageSize | ||
/// </summary> | ||
public static PagedEntityCollection<T> Paginate<T>(this IQueryable<T> query, uint pageNumber, uint pageSize) | ||
{ | ||
if (pageNumber == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); | ||
|
||
if (pageSize == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); | ||
|
||
var result = new PagedEntityCollection<T> | ||
{ | ||
CurrentPage = pageNumber, | ||
PageSize = pageSize, | ||
RecordCount = (uint)query.Count(), | ||
Results = query.Skip((int)((pageNumber - 1) * pageSize)).Take((int)pageSize).ToList() | ||
}; | ||
|
||
result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); | ||
|
||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Paginate IQueryable of <typeparamref name="T"/> | ||
/// with given pageNumber and pageSize | ||
/// </summary> | ||
public static async Task<PagedEntityCollection<T>> PaginateAsync<T>(this IQueryable<T> query, uint pageNumber, uint pageSize) | ||
{ | ||
if (pageNumber == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); | ||
|
||
if (pageSize == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); | ||
|
||
var result = new PagedEntityCollection<T> | ||
{ | ||
CurrentPage = pageNumber, | ||
PageSize = pageSize, | ||
RecordCount = (uint) await query.CountAsync(), | ||
Results = await query.Skip((int)((pageNumber - 1) * pageSize)).Take((int)pageSize).ToListAsync() | ||
}; | ||
|
||
result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); | ||
|
||
return result; | ||
} | ||
} | ||
} | ||
/// The place for IQuerubable extenstions | ||
/// </summary> | ||
public static partial class IQueryableExtensions | ||
{ | ||
/// <summary> | ||
/// Paginate IQueryable of <typeparamref name="T"/> | ||
/// with given pageNumber and pageSize | ||
/// </summary> | ||
public static PagedEntityCollection<T> Paginate<T>(this IQueryable<T> query, uint pageNumber, uint pageSize) | ||
{ | ||
if (pageNumber == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); | ||
|
||
if (pageSize == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); | ||
|
||
var result = new PagedEntityCollection<T> | ||
{ | ||
CurrentPage = pageNumber, | ||
PageSize = pageSize, | ||
RecordCount = (uint)query.Count(), | ||
Results = query.Skip((int)((pageNumber - 1) * pageSize)).Take((int)pageSize).ToList() | ||
}; | ||
|
||
result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); | ||
|
||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Paginate IQueryable of <typeparamref name="T"/> | ||
/// with given pageNumber and pageSize | ||
/// </summary> | ||
public static async Task<PagedEntityCollection<T>> PaginateAsync<T>(this IQueryable<T> query, uint pageNumber, uint pageSize) | ||
{ | ||
if (pageNumber == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageNumber)); | ||
|
||
if (pageSize == 0) | ||
throw new ArgumentException("Must be greater than zero.", nameof(pageSize)); | ||
|
||
var result = new PagedEntityCollection<T> | ||
{ | ||
CurrentPage = pageNumber, | ||
PageSize = pageSize, | ||
RecordCount = (uint) await query.CountAsync(), | ||
Results = await query.Skip((int)((pageNumber - 1) * pageSize)).Take((int)pageSize).ToListAsync() | ||
}; | ||
|
||
result.PageCount = (uint)Math.Ceiling((double)result.RecordCount / pageSize); | ||
|
||
return result; | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
Source/BSN.Commons.Orm.EntityFrameworkCore/Extensions/SieveExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
using Sieve.Exceptions; | ||
|
||
namespace BSN.Commons.Orm.EntityFrameworkCore.Extensions | ||
{ | ||
/// <summary> | ||
/// Add some extensions to make Sieve more easy to use | ||
/// </summary> | ||
public static class SieveExtensions | ||
{ | ||
/// <summary> | ||
/// Ordered extract message from <see cref="SieveException"/> based on inner exceptions | ||
/// </summary> | ||
/// <param name="ex"></param> | ||
/// <returns></returns> | ||
public static string ExtractMessage(this SieveException ex) | ||
{ | ||
string message = ex.InnerException?.InnerException?.Message; | ||
|
||
message = message ?? ex.InnerException?.Message; | ||
message = message ?? ex.Message; | ||
|
||
return message; | ||
} | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
Source/BSN.Commons/Infrastructure/IDynamicFilterableRepository.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
using System; | ||
using System.Linq.Expressions; | ||
|
||
namespace BSN.Commons.Infrastructure | ||
{ | ||
/// <summary> | ||
/// Add Dynamic filterable query ability to <see cref="IRepository{T}"/> pattern" | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
public interface IDynamicFilterableRepository<T> : IRepository<T> where T : class | ||
{ | ||
/// <summary> | ||
/// Get list of objects based on <paramref name="filters"/> | ||
/// </summary> | ||
/// <remarks> | ||
/// This method retrieve all objects based on filters and apply pagination. | ||
/// So to retrieve all objects that matched with filters, you need iterate all pages. | ||
/// </remarks> | ||
/// <param name="filters"> | ||
/// is a comma-delimited list of <c>{Name}{Operator}{Value}</c> where | ||
/// <list type="bullet"> | ||
/// <item> | ||
/// <description> | ||
/// <c>{Name}</c> is the name of a property with the Sieve attribute or the name of a custom filter method for <c>TEntity</c> | ||
/// <list type="bullet"> | ||
/// <item> | ||
/// <description> | ||
/// You can also have multiple names (for OR logic) by enclosing them in brackets and using a pipe delimiter, | ||
/// eg. <c> (LikeCount|CommentCount)>10 </c> asks if LikeCount or <c> CommentCount is >10 </c> | ||
/// </description> | ||
/// </item> | ||
/// </list> | ||
/// </description> | ||
/// </item> | ||
/// <item> | ||
/// <description> | ||
/// <c>{Operator}</c> is one of the Operators | ||
/// </description> | ||
/// </item> | ||
/// <item> | ||
/// <description> | ||
/// <c>{Value}</c> is the value to use for filtering | ||
/// <list type="bullet"> | ||
/// <item> | ||
/// <description> | ||
/// You can also have multiple values (for OR logic) by using a pipe delimiter, | ||
/// eg. <c>Title@=new|hot</c> will return posts with titles that contain the text "new" or "hot" | ||
/// </description> | ||
/// </item> | ||
/// </list> | ||
/// </description> | ||
/// </item> | ||
/// </list> | ||
/// </param> | ||
/// <param name="sorts">is a comma-delimited ordered list of property names to sort by. Adding a <c>-</c>before the name switches to sorting descendingly.</param> | ||
/// <param name="pageNumber">is the number of page to return</param> | ||
/// <param name="pageSize">is the number of items returned per page</param> | ||
/// <returns>Paginated list of matched objects based on filters</returns> | ||
PagedEntityCollection<T> GetMany(string filters, string sorts, uint pageNumber, uint pageSize); | ||
|
||
/// <summary> | ||
/// Get list of objects based on <paramref name="filters"/> in scope of <paramref name="where"/> expression | ||
/// <see cref="GetMany(string, string, uint, uint)"/> | ||
/// </summary> | ||
/// <param name="where">A function to test each element for a condition.</param> | ||
/// <param name="filters"><inheritdoc cref="GetMany(string, string, uint, uint)" path='/param[@name="filters"]'/></param> | ||
/// <param name="sorts"><inheritdoc cref="GetMany(string, string, uint, uint)" path='/param[@name="sorts"]'/></param> | ||
/// <param name="pageNumber"><inheritdoc cref="GetMany(string, string, uint, uint)" path='/param[@name="pageNumber"]'/></param> | ||
/// <param name="pageSize"><inheritdoc cref="GetMany(string, string, uint, uint)" path='/param[@name="pageSize"]'/></param> | ||
/// <returns>Paginated list of matched objects based on <paramref name="filters"/> in scope of <paramref name="where"/> expression</returns> | ||
PagedEntityCollection<T> GetMany(Expression<Func<T, bool>> where, string filters, string sorts, uint pageNumber, uint pageSize); | ||
} | ||
} | ||
|