add some Extensions Methods for XQuery Pages Counting ...

This commit is contained in:
2025-11-04 18:10:53 +03:30
parent 4a64a13630
commit 0415364fea
+244 -136
View File
@@ -6,12 +6,16 @@ using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using xCommons.Extensions; using xCommons.Extensions;
using xDataService.Configuration;
using xDataService.Constants; using xDataService.Constants;
using xExceptions.Constants; using xExceptions.Constants;
using xModels.Base; using xModels.Base;
using xModels.Dtos;
namespace xDataService.Extensions { namespace xDataService.Extensions
public static class IQueryableExtensions { {
public static class IQueryableExtensions
{
/// <summary> /// <summary>
/// Apply Search Filter on a Query /// Apply Search Filter on a Query
/// </summary> /// </summary>
@@ -19,14 +23,15 @@ namespace xDataService.Extensions {
/// <param name="filter"></param> /// <param name="filter"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IQueryable<T> ApplyFilter<T, TKey> ( public static IQueryable<T> ApplyFilter<T, TKey>(
this IQueryable<T> source, this IQueryable<T> source,
string filter string filter
) )
where T : XBaseEntity<TKey> { where T : XBaseEntity<TKey>
{
// //
// Apply Filter ... // Apply Filter ...
return source.Where (r => r.PropValuesContains (filter)); return source.Where(r => r.PropValuesContains(filter));
} }
/// <summary> /// <summary>
@@ -36,38 +41,41 @@ namespace xDataService.Extensions {
/// <param name="filter"></param> /// <param name="filter"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static async Task<IQueryable<T>> ApplyFilterAsync<T, TKey> ( public static async Task<IQueryable<T>> ApplyFilterAsync<T, TKey>(
this IQueryable<T> source, this IQueryable<T> source,
string filter string filter
) )
where T : XBaseEntity<TKey> { where T : XBaseEntity<TKey>
{
// //
// Where Clause ... // Where Clause ...
Expression<Func<T, bool>> whereClause = r => r Expression<Func<T, bool>> whereClause = r => r
.PropValuesContains (filter); .PropValuesContains(filter);
// //
// Generate Where Function ... // Generate Where Function ...
var whereFunc = whereClause.Compile (); var whereFunc = whereClause.Compile();
// //
// Get Enumerable ... // Get Enumerable ...
var enumerator = source var enumerator = source
.AsAsyncEnumerable (); .AsAsyncEnumerable();
// //
var result = new List<T> (); var result = new List<T>();
await await
foreach (var entity in enumerator) { foreach (var entity in enumerator)
{
// //
var isApproved = whereFunc (entity); var isApproved = whereFunc(entity);
if (isApproved) { if (isApproved)
result.Add (entity); {
result.Add(entity);
} }
} }
// //
return result.AsQueryable (); return result.AsQueryable();
} }
/// <summary> /// <summary>
@@ -77,40 +85,43 @@ namespace xDataService.Extensions {
/// <param name="filter"></param> /// <param name="filter"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static async Task<IEnumerable<T>> ApplyFilterAsync<T, TKey> ( public static async Task<IEnumerable<T>> ApplyFilterAsync<T, TKey>(
this IEnumerable<T> source, this IEnumerable<T> source,
string filter string filter
) )
where T : XBaseEntity<TKey> { where T : XBaseEntity<TKey>
{
// //
// Where Clause ... // Where Clause ...
Expression<Func<T, bool>> whereClause = r => r Expression<Func<T, bool>> whereClause = r => r
.PropValuesContains (filter); .PropValuesContains(filter);
// //
// Generate Where Function ... // Generate Where Function ...
var whereFunc = whereClause.Compile (); var whereFunc = whereClause.Compile();
// //
// Get Enumerable ... // Get Enumerable ...
var enumerator = source var enumerator = source
.AsQueryable () .AsQueryable()
.AsAsyncEnumerable (); .AsAsyncEnumerable();
// //
var result = new List<T> (); var result = new List<T>();
await await
foreach (var entity in enumerator) { foreach (var entity in enumerator)
{
// //
var isApproved = whereFunc (entity); var isApproved = whereFunc(entity);
if (isApproved) { if (isApproved)
result.Add (entity); {
result.Add(entity);
} }
} }
// //
return result return result
.AsEnumerable (); .AsEnumerable();
} }
/// <summary> /// <summary>
@@ -120,14 +131,15 @@ namespace xDataService.Extensions {
/// <param name="filter"></param> /// <param name="filter"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IEnumerable<T> ApplyFilter<T> ( public static IEnumerable<T> ApplyFilter<T>(
this IEnumerable<T> source, this IEnumerable<T> source,
string filter string filter
) )
where T : class { where T : class
{
// //
// Apply Filter ... // Apply Filter ...
return source.Where (r => r.PropValuesContains (filter)); return source.Where(r => r.PropValuesContains(filter));
} }
/// <summary> /// <summary>
@@ -137,43 +149,46 @@ namespace xDataService.Extensions {
/// <param name="propertyName"></param> /// <param name="propertyName"></param>
/// <typeparam name="TSource"></typeparam> /// <typeparam name="TSource"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IOrderedQueryable<TSource> OrderBy<TSource> ( public static IOrderedQueryable<TSource> OrderBy<TSource>(
this IQueryable<TSource> query, this IQueryable<TSource> query,
string propertyName string propertyName
) { )
{
// //
var entityType = typeof (TSource); var entityType = typeof(TSource);
// //
// Create x => x.PropName // Create x => x.PropName
var propertyInfo = entityType.GetProperty (propertyName); var propertyInfo = entityType.GetProperty(propertyName);
if (propertyInfo.IsNull ()) { if (propertyInfo.IsNull())
XException.InvalidArgs.Throw (); {
XException.InvalidArgs.Throw();
} }
// //
ParameterExpression arg = Expression.Parameter (entityType, "x"); ParameterExpression arg = Expression.Parameter(entityType, "x");
MemberExpression property = Expression.Property (arg, propertyName); MemberExpression property = Expression.Property(arg, propertyName);
var selector = Expression.Lambda (property, new ParameterExpression[] { arg }); var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
// //
// Get System.Linq.Queryable.OrderBy() method. // Get System.Linq.Queryable.OrderBy() method.
var enumarableType = typeof (System.Linq.Queryable); var enumarableType = typeof(System.Linq.Queryable);
var method = enumarableType.GetMethods () var method = enumarableType.GetMethods()
.Where (m => m.Name == "OrderBy" && m.IsGenericMethodDefinition) .Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
.Where (m => { .Where(m =>
{
// //
var parameters = m.GetParameters ().ToList (); var parameters = m.GetParameters().ToList();
// //
// Put more restriction here to ensure selecting the right overload // Put more restriction here to ensure selecting the right overload
return parameters.Count == 2; // overload that has 2 parameters return parameters.Count == 2; // overload that has 2 parameters
}).Single (); }).Single();
// //
// The linq's OrderBy<TSource, TKey> has two generic types, which provided here ... // The linq's OrderBy<TSource, TKey> has two generic types, which provided here ...
MethodInfo genericMethod = method MethodInfo genericMethod = method
.MakeGenericMethod (entityType, propertyInfo.PropertyType); .MakeGenericMethod(entityType, propertyInfo.PropertyType);
/* /*
Call query.OrderBy(selector), with query and selector: x=> x.PropName Call query.OrderBy(selector), with query and selector: x=> x.PropName
@@ -181,8 +196,8 @@ namespace xDataService.Extensions {
By doing so EF can extract "order by" columns and generate SQL for it. By doing so EF can extract "order by" columns and generate SQL for it.
*/ */
var newQuery = (IOrderedQueryable<TSource>) genericMethod var newQuery = (IOrderedQueryable<TSource>)genericMethod
.Invoke (genericMethod, new object[] { query, selector }); .Invoke(genericMethod, new object[] { query, selector });
// //
return newQuery; return newQuery;
@@ -195,19 +210,21 @@ namespace xDataService.Extensions {
/// <param name="propertyName"></param> /// <param name="propertyName"></param>
/// <typeparam name="TSource"></typeparam> /// <typeparam name="TSource"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IOrderedEnumerable<TSource> OrderBy<TSource> ( public static IOrderedEnumerable<TSource> OrderBy<TSource>(
this IEnumerable<TSource> query, this IEnumerable<TSource> query,
string propertyName string propertyName
) { )
{
// //
var entityType = typeof (TSource); var entityType = typeof(TSource);
var propertyInfo = entityType.GetProperty (propertyName); var propertyInfo = entityType.GetProperty(propertyName);
if (propertyInfo.IsNull ()) { if (propertyInfo.IsNull())
XException.InvalidArgs.Throw (); {
XException.InvalidArgs.Throw();
} }
// //
return query.OrderBy (x => propertyInfo.GetValue (x, null)); return query.OrderBy(x => propertyInfo.GetValue(x, null));
} }
/// <summary> /// <summary>
@@ -217,43 +234,46 @@ namespace xDataService.Extensions {
/// <param name="propertyName"></param> /// <param name="propertyName"></param>
/// <typeparam name="TSource"></typeparam> /// <typeparam name="TSource"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IOrderedQueryable<TSource> OrderByDescending<TSource> ( public static IOrderedQueryable<TSource> OrderByDescending<TSource>(
this IQueryable<TSource> query, this IQueryable<TSource> query,
string propertyName string propertyName
) { )
{
// //
var entityType = typeof (TSource); var entityType = typeof(TSource);
// //
// Create x => x.PropName // Create x => x.PropName
var propertyInfo = entityType.GetProperty (propertyName); var propertyInfo = entityType.GetProperty(propertyName);
if (propertyInfo.IsNull ()) { if (propertyInfo.IsNull())
XException.InvalidArgs.Throw (); {
XException.InvalidArgs.Throw();
} }
// //
ParameterExpression arg = Expression.Parameter (entityType, "x"); ParameterExpression arg = Expression.Parameter(entityType, "x");
MemberExpression property = Expression.Property (arg, propertyName); MemberExpression property = Expression.Property(arg, propertyName);
var selector = Expression.Lambda (property, new ParameterExpression[] { arg }); var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
// //
// Get System.Linq.Queryable.OrderBy() method. // Get System.Linq.Queryable.OrderBy() method.
var enumarableType = typeof (System.Linq.Queryable); var enumarableType = typeof(System.Linq.Queryable);
var method = enumarableType.GetMethods () var method = enumarableType.GetMethods()
.Where (m => m.Name == "OrderByDescending" && m.IsGenericMethodDefinition) .Where(m => m.Name == "OrderByDescending" && m.IsGenericMethodDefinition)
.Where (m => { .Where(m =>
{
// //
var parameters = m.GetParameters ().ToList (); var parameters = m.GetParameters().ToList();
// //
// Put more restriction here to ensure selecting the right overload // Put more restriction here to ensure selecting the right overload
return parameters.Count == 2; // overload that has 2 parameters return parameters.Count == 2; // overload that has 2 parameters
}).Single (); }).Single();
// //
// The linq's OrderBy<TSource, TKey> has two generic types, which provided here ... // The linq's OrderBy<TSource, TKey> has two generic types, which provided here ...
MethodInfo genericMethod = method MethodInfo genericMethod = method
.MakeGenericMethod (entityType, propertyInfo.PropertyType); .MakeGenericMethod(entityType, propertyInfo.PropertyType);
/* /*
Call query.OrderBy(selector), with query and selector: x=> x.PropName Call query.OrderBy(selector), with query and selector: x=> x.PropName
@@ -261,8 +281,8 @@ namespace xDataService.Extensions {
By doing so EF can extract "order by" columns and generate SQL for it. By doing so EF can extract "order by" columns and generate SQL for it.
*/ */
var newQuery = (IOrderedQueryable<TSource>) genericMethod var newQuery = (IOrderedQueryable<TSource>)genericMethod
.Invoke (genericMethod, new object[] { query, selector }); .Invoke(genericMethod, new object[] { query, selector });
// //
return newQuery; return newQuery;
@@ -275,19 +295,21 @@ namespace xDataService.Extensions {
/// <param name="propertyName"></param> /// <param name="propertyName"></param>
/// <typeparam name="TSource"></typeparam> /// <typeparam name="TSource"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IOrderedEnumerable<TSource> OrderByDescending<TSource> ( public static IOrderedEnumerable<TSource> OrderByDescending<TSource>(
this IEnumerable<TSource> query, this IEnumerable<TSource> query,
string propertyName string propertyName
) { )
{
// //
var entityType = typeof (TSource); var entityType = typeof(TSource);
var propertyInfo = entityType.GetProperty (propertyName); var propertyInfo = entityType.GetProperty(propertyName);
if (propertyInfo.IsNull ()) { if (propertyInfo.IsNull())
XException.InvalidArgs.Throw (); {
XException.InvalidArgs.Throw();
} }
// //
return query.OrderByDescending (x => propertyInfo.GetValue (x, null)); return query.OrderByDescending(x => propertyInfo.GetValue(x, null));
} }
/// <summary> /// <summary>
@@ -299,21 +321,24 @@ namespace xDataService.Extensions {
/// <param name="columnsMap"></param> /// <param name="columnsMap"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IQueryable<T> ApplySorting<T, TKey> ( public static IQueryable<T> ApplySorting<T, TKey>(
this IQueryable<T> source, this IQueryable<T> source,
string sortBy, string sortBy,
bool isAscending, bool isAscending,
IDictionary<string, Expression<Func<T, object>>> columnsMap = null IDictionary<string, Expression<Func<T, object>>> columnsMap = null
) )
where T : XBaseEntity<TKey> { where T : XBaseEntity<TKey>
{
// //
if (columnsMap == null) { if (columnsMap == null)
{
// //
columnsMap = source.FirstOrDefault () columnsMap = source.FirstOrDefault()
.GetDefaultColumnsMap (); .GetDefaultColumnsMap();
// //
if (columnsMap == null) { if (columnsMap == null)
{
return source; return source;
} }
} }
@@ -321,18 +346,22 @@ namespace xDataService.Extensions {
// //
// Validate SortBy ... // Validate SortBy ...
var sortByItem = columnsMap.Keys var sortByItem = columnsMap.Keys
.FirstOrDefault (p => .FirstOrDefault(p =>
p.ToNormalString () == sortBy.ToNormalString ()); p.ToNormalString() == sortBy.ToNormalString());
if (sortByItem.IsNullOrEmpty ()) { if (sortByItem.IsNullOrEmpty())
{
return source; return source;
} }
// //
// Apply Sorting ... // Apply Sorting ...
if (isAscending) { if (isAscending)
return source.OrderBy (sortByItem); {
} else { return source.OrderBy(sortByItem);
return source.OrderByDescending (sortByItem); }
else
{
return source.OrderByDescending(sortByItem);
} }
} }
@@ -345,21 +374,24 @@ namespace xDataService.Extensions {
/// <param name="columnsMap"></param> /// <param name="columnsMap"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IEnumerable<T> ApplySorting<T, TKey> ( public static IEnumerable<T> ApplySorting<T, TKey>(
this IEnumerable<T> source, this IEnumerable<T> source,
string sortBy, string sortBy,
bool isAscending, bool isAscending,
IDictionary<string, Expression<Func<T, object>>> columnsMap = null IDictionary<string, Expression<Func<T, object>>> columnsMap = null
) )
where T : XBaseEntity<TKey> { where T : XBaseEntity<TKey>
{
// //
if (columnsMap == null) { if (columnsMap == null)
{
// //
columnsMap = source.FirstOrDefault () columnsMap = source.FirstOrDefault()
.GetDefaultColumnsMap (); .GetDefaultColumnsMap();
// //
if (columnsMap == null) { if (columnsMap == null)
{
return source; return source;
} }
} }
@@ -367,18 +399,22 @@ namespace xDataService.Extensions {
// //
// Validate SortBy ... // Validate SortBy ...
var sortByItem = columnsMap.Keys var sortByItem = columnsMap.Keys
.FirstOrDefault (p => .FirstOrDefault(p =>
p.ToNormalString () == sortBy.ToNormalString ()); p.ToNormalString() == sortBy.ToNormalString());
if (sortByItem.IsNullOrEmpty ()) { if (sortByItem.IsNullOrEmpty())
{
return source; return source;
} }
// //
// Apply Sorting ... // Apply Sorting ...
if (isAscending) { if (isAscending)
return source.OrderBy (sortByItem); {
} else { return source.OrderBy(sortByItem);
return source.OrderByDescending (sortByItem); }
else
{
return source.OrderByDescending(sortByItem);
} }
} }
@@ -391,21 +427,24 @@ namespace xDataService.Extensions {
/// <param name="columnsMap"></param> /// <param name="columnsMap"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IEnumerable<T> ApplySorting<T> ( public static IEnumerable<T> ApplySorting<T>(
this IEnumerable<T> source, this IEnumerable<T> source,
string sortBy, string sortBy,
bool isAscending, bool isAscending,
IDictionary<string, Expression<Func<T, object>>> columnsMap = null IDictionary<string, Expression<Func<T, object>>> columnsMap = null
) )
where T : class { where T : class
{
// //
if (columnsMap == null) { if (columnsMap == null)
{
// //
columnsMap = source.FirstOrDefault () columnsMap = source.FirstOrDefault()
.GetDefaultColumnsMap (); .GetDefaultColumnsMap();
// //
if (columnsMap == null) { if (columnsMap == null)
{
return source; return source;
} }
} }
@@ -413,18 +452,22 @@ namespace xDataService.Extensions {
// //
// Validate SortBy ... // Validate SortBy ...
var sortByItem = columnsMap.Keys var sortByItem = columnsMap.Keys
.FirstOrDefault (p => .FirstOrDefault(p =>
p.ToNormalString () == sortBy.ToNormalString ()); p.ToNormalString() == sortBy.ToNormalString());
if (sortByItem.IsNullOrEmpty ()) { if (sortByItem.IsNullOrEmpty())
{
return source; return source;
} }
// //
// Apply Sorting ... // Apply Sorting ...
if (isAscending) { if (isAscending)
return source.OrderBy (sortByItem); {
} else { return source.OrderBy(sortByItem);
return source.OrderByDescending (sortByItem); }
else
{
return source.OrderByDescending(sortByItem);
} }
} }
@@ -436,32 +479,35 @@ namespace xDataService.Extensions {
/// <param name="pageSize"></param> /// <param name="pageSize"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IQueryable<T> ApplyPaging<T> ( public static IQueryable<T> ApplyPaging<T>(
this IQueryable<T> source, this IQueryable<T> source,
int? page, int? page,
int? pageSize int? pageSize
) { )
{
// //
if (!page.HasValue || if (!page.HasValue ||
page <= 0) { page <= 0)
{
page = 1; page = 1;
} }
// //
if (!pageSize.HasValue || if (!pageSize.HasValue ||
pageSize <= 0 || pageSize <= 0 ||
pageSize > XDataServiceConstants.MAX_AVAILABLE_PAGE_SIZE) { pageSize > XDataServiceConstants.MAX_AVAILABLE_PAGE_SIZE)
{
pageSize = XDataServiceConstants.DEFAULT_PAGE_SIZE; pageSize = XDataServiceConstants.DEFAULT_PAGE_SIZE;
} }
// //
var pageVal = page.GetValueOrDefault (); var pageVal = page.GetValueOrDefault();
var pageSizeVal = pageSize.GetValueOrDefault (); var pageSizeVal = pageSize.GetValueOrDefault();
// //
return source return source
.Skip ((pageVal - 1) * pageSizeVal) .Skip((pageVal - 1) * pageSizeVal)
.Take (pageSizeVal); .Take(pageSizeVal);
} }
/// <summary> /// <summary>
@@ -472,32 +518,94 @@ namespace xDataService.Extensions {
/// <param name="pageSize"></param> /// <param name="pageSize"></param>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <returns></returns> /// <returns></returns>
public static IEnumerable<T> ApplyPaging<T> ( public static IEnumerable<T> ApplyPaging<T>(
this IEnumerable<T> source, this IEnumerable<T> source,
int? page, int? page,
int? pageSize int? pageSize
) { )
{
// //
if (!page.HasValue || if (!page.HasValue ||
page <= 0) { page <= 0)
{
page = 1; page = 1;
} }
// //
if (!pageSize.HasValue || if (!pageSize.HasValue ||
pageSize <= 0 || pageSize <= 0 ||
pageSize > XDataServiceConstants.MAX_AVAILABLE_PAGE_SIZE) { pageSize > XDataServiceConstants.MAX_AVAILABLE_PAGE_SIZE)
{
pageSize = XDataServiceConstants.DEFAULT_PAGE_SIZE; pageSize = XDataServiceConstants.DEFAULT_PAGE_SIZE;
} }
// //
var pageVal = page.GetValueOrDefault (); var pageVal = page.GetValueOrDefault();
var pageSizeVal = pageSize.GetValueOrDefault (); var pageSizeVal = pageSize.GetValueOrDefault();
// //
return source return source
.Skip ((pageVal - 1) * pageSizeVal) .Skip((pageVal - 1) * pageSizeVal)
.Take (pageSizeVal); .Take(pageSizeVal);
}
/// <summary>
/// Count Query Pages based on Specified Total Number ...
/// </summary>
/// <param name="source"></param>
/// <param name="totalItems"></param>
/// <returns></returns>
public static int CountPages(this XQuery source, int totalItems)
{
//
int result = 0;
//
// Validate and Calculate ...
if (!source.IsNull() && source.PageSize > 0 && totalItems > 0)
{
//
result = totalItems / source.PageSize;
if (totalItems % source.PageSize > 0)
{
result++;
}
}
//
return result;
}
/// <summary>
/// Normalize an XQuery based on Data Service Configurations ...
/// </summary>
/// <param name="source"></param>
/// <param name="configuration"></param>
/// <returns></returns>
public static XQuery NormalizeQuery(
this XQuery source,
XDataServiceConfiguration configuration //
)
{
//
if (source.IsNull())
{
source = new XQuery();
}
//
// Page Size Normalization ...
if (source.PageSize < configuration.PagingConfiguration.MinAvailablePageSize)
{
source.PageSize = configuration.PagingConfiguration.MinAvailablePageSize;
}
if (source.PageSize > configuration.PagingConfiguration.MaxAvailablePageSize)
{
source.PageSize = configuration.PagingConfiguration.MaxAvailablePageSize;
}
//
return source;
} }
} }
} }