610 lines
19 KiB
C#
610 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using xCommons.Extensions;
|
|
using xDataService.Configuration;
|
|
using xDataService.Constants;
|
|
using xExceptions.Constants;
|
|
using xModels.Base;
|
|
using xModels.Dtos;
|
|
|
|
namespace xDataService.Extensions
|
|
{
|
|
public static class IQueryableExtensions
|
|
{
|
|
/// <summary>
|
|
/// Apply Search Filter on a Query
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="filter"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static IQueryable<T> ApplyFilter<T, TKey>(
|
|
this IQueryable<T> source,
|
|
string filter
|
|
)
|
|
where T : XBaseEntity<TKey>
|
|
{
|
|
//
|
|
// Apply Filter ...
|
|
return source.Where(r => r.PropValuesContains(filter));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Search Filter on a Query
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="filter"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static async Task<IQueryable<T>> ApplyFilterAsync<T, TKey>(
|
|
this IQueryable<T> source,
|
|
string filter
|
|
)
|
|
where T : XBaseEntity<TKey>
|
|
{
|
|
//
|
|
// Where Clause ...
|
|
Expression<Func<T, bool>> whereClause = r => r
|
|
.PropValuesContains(filter);
|
|
|
|
//
|
|
// Generate Where Function ...
|
|
var whereFunc = whereClause.Compile();
|
|
|
|
//
|
|
// Get Enumerable ...
|
|
var enumerator = source
|
|
.AsAsyncEnumerable();
|
|
|
|
//
|
|
var result = new List<T>();
|
|
await
|
|
foreach (var entity in enumerator)
|
|
{
|
|
//
|
|
var isApproved = whereFunc(entity);
|
|
if (isApproved)
|
|
{
|
|
result.Add(entity);
|
|
}
|
|
}
|
|
|
|
//
|
|
return result.AsQueryable();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Search Filter on a Enumerable
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="filter"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static async Task<IEnumerable<T>> ApplyFilterAsync<T, TKey>(
|
|
this IEnumerable<T> source,
|
|
string filter
|
|
)
|
|
where T : XBaseEntity<TKey>
|
|
{
|
|
//
|
|
// Where Clause ...
|
|
Expression<Func<T, bool>> whereClause = r => r
|
|
.PropValuesContains(filter);
|
|
|
|
//
|
|
// Generate Where Function ...
|
|
var whereFunc = whereClause.Compile();
|
|
|
|
//
|
|
// Get Enumerable ...
|
|
var enumerator = source
|
|
.ToList()
|
|
.AsQueryable();
|
|
|
|
//
|
|
var result = new List<T>();
|
|
foreach (var entity in enumerator)
|
|
{
|
|
//
|
|
var isApproved = whereFunc(entity);
|
|
if (isApproved)
|
|
{
|
|
result.Add(entity);
|
|
}
|
|
}
|
|
|
|
//
|
|
return result
|
|
.AsEnumerable();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Search Filter on a Enumerable
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="filter"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static IEnumerable<T> ApplyFilter<T>(
|
|
this IEnumerable<T> source,
|
|
string filter
|
|
)
|
|
where T : class
|
|
{
|
|
//
|
|
// Apply Filter ...
|
|
return source.Where(r => r.PropValuesContains(filter));
|
|
}
|
|
|
|
/// <summary>
|
|
/// provide a way to Order an IQueryable by string FieldName ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
/// <param name="propertyName"></param>
|
|
/// <typeparam name="TSource"></typeparam>
|
|
/// <returns></returns>
|
|
public static IOrderedQueryable<TSource> OrderBy<TSource>(
|
|
this IQueryable<TSource> query,
|
|
string propertyName
|
|
)
|
|
{
|
|
//
|
|
var entityType = typeof(TSource);
|
|
|
|
//
|
|
// Create x => x.PropName
|
|
var propertyInfo = entityType.GetProperty(propertyName);
|
|
if (propertyInfo.IsNull())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
ParameterExpression arg = Expression.Parameter(entityType, "x");
|
|
MemberExpression property = Expression.Property(arg, propertyName);
|
|
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
|
|
|
|
//
|
|
// Get System.Linq.Queryable.OrderBy() method.
|
|
var enumarableType = typeof(System.Linq.Queryable);
|
|
var method = enumarableType.GetMethods()
|
|
.Where(m => m.Name == "OrderBy" && m.IsGenericMethodDefinition)
|
|
.Where(m =>
|
|
{
|
|
//
|
|
var parameters = m.GetParameters().ToList();
|
|
|
|
//
|
|
// Put more restriction here to ensure selecting the right overload
|
|
return parameters.Count == 2; // overload that has 2 parameters
|
|
}).Single();
|
|
|
|
//
|
|
// The linq's OrderBy<TSource, TKey> has two generic types, which provided here ...
|
|
MethodInfo genericMethod = method
|
|
.MakeGenericMethod(entityType, propertyInfo.PropertyType);
|
|
|
|
/*
|
|
Call query.OrderBy(selector), with query and selector: x=> x.PropName
|
|
Note that we pass the selector as Expression to the method and we don't compile it.
|
|
|
|
By doing so EF can extract "order by" columns and generate SQL for it.
|
|
*/
|
|
var newQuery = (IOrderedQueryable<TSource>)genericMethod
|
|
.Invoke(genericMethod, new object[] { query, selector });
|
|
|
|
//
|
|
return newQuery;
|
|
}
|
|
|
|
/// <summary>
|
|
/// provide a way to Order an IEnumerable by string FieldName ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
/// <param name="propertyName"></param>
|
|
/// <typeparam name="TSource"></typeparam>
|
|
/// <returns></returns>
|
|
public static IOrderedEnumerable<TSource> OrderBy<TSource>(
|
|
this IEnumerable<TSource> query,
|
|
string propertyName
|
|
)
|
|
{
|
|
//
|
|
var entityType = typeof(TSource);
|
|
var propertyInfo = entityType.GetProperty(propertyName);
|
|
if (propertyInfo.IsNull())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
return query.OrderBy(x => propertyInfo.GetValue(x, null));
|
|
}
|
|
|
|
/// <summary>
|
|
/// provide a way to Order an IQueryable by string FieldName ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
/// <param name="propertyName"></param>
|
|
/// <typeparam name="TSource"></typeparam>
|
|
/// <returns></returns>
|
|
public static IOrderedQueryable<TSource> OrderByDescending<TSource>(
|
|
this IQueryable<TSource> query,
|
|
string propertyName
|
|
)
|
|
{
|
|
//
|
|
var entityType = typeof(TSource);
|
|
|
|
//
|
|
// Create x => x.PropName
|
|
var propertyInfo = entityType.GetProperty(propertyName);
|
|
if (propertyInfo.IsNull())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
ParameterExpression arg = Expression.Parameter(entityType, "x");
|
|
MemberExpression property = Expression.Property(arg, propertyName);
|
|
var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
|
|
|
|
//
|
|
// Get System.Linq.Queryable.OrderBy() method.
|
|
var enumarableType = typeof(System.Linq.Queryable);
|
|
var method = enumarableType.GetMethods()
|
|
.Where(m => m.Name == "OrderByDescending" && m.IsGenericMethodDefinition)
|
|
.Where(m =>
|
|
{
|
|
//
|
|
var parameters = m.GetParameters().ToList();
|
|
|
|
//
|
|
// Put more restriction here to ensure selecting the right overload
|
|
return parameters.Count == 2; // overload that has 2 parameters
|
|
}).Single();
|
|
|
|
//
|
|
// The linq's OrderBy<TSource, TKey> has two generic types, which provided here ...
|
|
MethodInfo genericMethod = method
|
|
.MakeGenericMethod(entityType, propertyInfo.PropertyType);
|
|
|
|
/*
|
|
Call query.OrderBy(selector), with query and selector: x=> x.PropName
|
|
Note that we pass the selector as Expression to the method and we don't compile it.
|
|
|
|
By doing so EF can extract "order by" columns and generate SQL for it.
|
|
*/
|
|
var newQuery = (IOrderedQueryable<TSource>)genericMethod
|
|
.Invoke(genericMethod, new object[] { query, selector });
|
|
|
|
//
|
|
return newQuery;
|
|
}
|
|
|
|
/// <summary>
|
|
/// provide a way to Order an IEnumerable by string FieldName ...
|
|
/// </summary>
|
|
/// <param name="query"></param>
|
|
/// <param name="propertyName"></param>
|
|
/// <typeparam name="TSource"></typeparam>
|
|
/// <returns></returns>
|
|
public static IOrderedEnumerable<TSource> OrderByDescending<TSource>(
|
|
this IEnumerable<TSource> query,
|
|
string propertyName
|
|
)
|
|
{
|
|
//
|
|
var entityType = typeof(TSource);
|
|
var propertyInfo = entityType.GetProperty(propertyName);
|
|
if (propertyInfo.IsNull())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
return query.OrderByDescending(x => propertyInfo.GetValue(x, null));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Sorting on a query
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="sortBy"></param>
|
|
/// <param name="isAscending"></param>
|
|
/// <param name="columnsMap"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static IQueryable<T> ApplySorting<T, TKey>(
|
|
this IQueryable<T> source,
|
|
string sortBy,
|
|
bool isAscending,
|
|
IDictionary<string, Expression<Func<T, object>>> columnsMap = null
|
|
)
|
|
where T : XBaseEntity<TKey>
|
|
{
|
|
//
|
|
if (columnsMap == null)
|
|
{
|
|
//
|
|
columnsMap = source.FirstOrDefault()
|
|
.GetDefaultColumnsMap();
|
|
|
|
//
|
|
if (columnsMap == null)
|
|
{
|
|
return source;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Validate SortBy ...
|
|
var sortByItem = columnsMap.Keys
|
|
.FirstOrDefault(p =>
|
|
p.ToNormalString() == sortBy.ToNormalString());
|
|
if (sortByItem.IsNullOrEmpty())
|
|
{
|
|
return source;
|
|
}
|
|
|
|
//
|
|
// Apply Sorting ...
|
|
if (isAscending)
|
|
{
|
|
return source.OrderBy(sortByItem);
|
|
}
|
|
else
|
|
{
|
|
return source.OrderByDescending(sortByItem);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Sorting on a query
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="sortBy"></param>
|
|
/// <param name="isAscending"></param>
|
|
/// <param name="columnsMap"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static IEnumerable<T> ApplySorting<T, TKey>(
|
|
this IEnumerable<T> source,
|
|
string sortBy,
|
|
bool isAscending,
|
|
IDictionary<string, Expression<Func<T, object>>> columnsMap = null
|
|
)
|
|
where T : XBaseEntity<TKey>
|
|
{
|
|
//
|
|
if (columnsMap == null)
|
|
{
|
|
//
|
|
columnsMap = source.FirstOrDefault()
|
|
.GetDefaultColumnsMap();
|
|
|
|
//
|
|
if (columnsMap == null)
|
|
{
|
|
return source;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Validate SortBy ...
|
|
var sortByItem = columnsMap.Keys
|
|
.FirstOrDefault(p =>
|
|
p.ToNormalString() == sortBy.ToNormalString());
|
|
if (sortByItem.IsNullOrEmpty())
|
|
{
|
|
return source;
|
|
}
|
|
|
|
//
|
|
// Apply Sorting ...
|
|
if (isAscending)
|
|
{
|
|
return source.OrderBy(sortByItem);
|
|
}
|
|
else
|
|
{
|
|
return source.OrderByDescending(sortByItem);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Sorting on a Enumerable
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="sortBy"></param>
|
|
/// <param name="isAscending"></param>
|
|
/// <param name="columnsMap"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static IEnumerable<T> ApplySorting<T>(
|
|
this IEnumerable<T> source,
|
|
string sortBy,
|
|
bool isAscending,
|
|
IDictionary<string, Expression<Func<T, object>>> columnsMap = null
|
|
)
|
|
where T : class
|
|
{
|
|
//
|
|
if (columnsMap == null)
|
|
{
|
|
//
|
|
columnsMap = source.FirstOrDefault()
|
|
.GetDefaultColumnsMap();
|
|
|
|
//
|
|
if (columnsMap == null)
|
|
{
|
|
return source;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Validate SortBy ...
|
|
var sortByItem = columnsMap.Keys
|
|
.FirstOrDefault(p =>
|
|
p.ToNormalString() == sortBy.ToNormalString());
|
|
if (sortByItem.IsNullOrEmpty())
|
|
{
|
|
return source;
|
|
}
|
|
|
|
//
|
|
// Apply Sorting ...
|
|
if (isAscending)
|
|
{
|
|
return source.OrderBy(sortByItem);
|
|
}
|
|
else
|
|
{
|
|
return source.OrderByDescending(sortByItem);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Paging on a qury
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="page"></param>
|
|
/// <param name="pageSize"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static IQueryable<T> ApplyPaging<T>(
|
|
this IQueryable<T> source,
|
|
int? page,
|
|
int? pageSize
|
|
)
|
|
{
|
|
//
|
|
if (!page.HasValue ||
|
|
page <= 0)
|
|
{
|
|
page = 1;
|
|
}
|
|
|
|
//
|
|
if (!pageSize.HasValue ||
|
|
pageSize <= 0 ||
|
|
pageSize > XDataServiceConstants.MAX_AVAILABLE_PAGE_SIZE)
|
|
{
|
|
pageSize = XDataServiceConstants.DEFAULT_PAGE_SIZE;
|
|
}
|
|
|
|
//
|
|
var pageVal = page.GetValueOrDefault();
|
|
var pageSizeVal = pageSize.GetValueOrDefault();
|
|
|
|
//
|
|
return source
|
|
.Skip((pageVal - 1) * pageSizeVal)
|
|
.Take(pageSizeVal);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply Paging on a Enumerable
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="page"></param>
|
|
/// <param name="pageSize"></param>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <returns></returns>
|
|
public static IEnumerable<T> ApplyPaging<T>(
|
|
this IEnumerable<T> source,
|
|
int? page,
|
|
int? pageSize
|
|
)
|
|
{
|
|
//
|
|
if (!page.HasValue ||
|
|
page <= 0)
|
|
{
|
|
page = 1;
|
|
}
|
|
|
|
//
|
|
if (!pageSize.HasValue ||
|
|
pageSize <= 0 ||
|
|
pageSize > XDataServiceConstants.MAX_AVAILABLE_PAGE_SIZE)
|
|
{
|
|
pageSize = XDataServiceConstants.DEFAULT_PAGE_SIZE;
|
|
}
|
|
|
|
//
|
|
var pageVal = page.GetValueOrDefault();
|
|
var pageSizeVal = pageSize.GetValueOrDefault();
|
|
|
|
//
|
|
return source
|
|
.Skip((pageVal - 1) * 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;
|
|
}
|
|
}
|
|
} |