Fix: Rename Generic.CL folder to flat structure, update csproj and references
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class AdvancedFilterExtension
|
||||
{
|
||||
public static IQueryable<T> ApplyAdvancedFilter<T, TFilter>(this IQueryable<T> query, TFilter filter)
|
||||
where TFilter : class
|
||||
{
|
||||
if (filter == null) return query;
|
||||
|
||||
var entityType = typeof(T);
|
||||
var filterProperties = typeof(TFilter).GetProperties()
|
||||
.Where(p => p.GetValue(filter) != null)
|
||||
.ToDictionary(p => p.Name, p => p.GetValue(filter));
|
||||
|
||||
if (!filterProperties.Any()) return query;
|
||||
|
||||
// 1. Map Entity Properties with their Paths and Levels
|
||||
var entityMap = ScanEntity(entityType);
|
||||
|
||||
// 2. Process by Level (Priority)
|
||||
var maxLevel = entityMap.Any() ? entityMap.Max(m => m.Level) : 0;
|
||||
|
||||
var parameter = Expression.Parameter(entityType, "x");
|
||||
|
||||
// We keep track of which filter properties we've already applied
|
||||
var appliedFilterKeys = new HashSet<string>();
|
||||
|
||||
for (int level = 1; level <= maxLevel; level++)
|
||||
{
|
||||
var levelProperties = entityMap.Where(m => m.Level == level).ToList();
|
||||
|
||||
foreach (var meta in levelProperties)
|
||||
{
|
||||
// If this property name exists in our filter and hasn't been applied yet
|
||||
if (filterProperties.TryGetValue(meta.Name, out var filterValue) && !appliedFilterKeys.Contains(meta.Name))
|
||||
{
|
||||
// If it's a root property (Level 1)
|
||||
if (meta.Level == 1)
|
||||
{
|
||||
var comparison = BuildComparison(parameter, meta.PropertyInfo, filterValue);
|
||||
if (comparison != null)
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);
|
||||
query = query.Where(lambda);
|
||||
appliedFilterKeys.Add(meta.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's a nested property. We need to find the "Branch Root" (the first collection in the path)
|
||||
var branchInfo = FindBranchRoot(meta);
|
||||
if (branchInfo != null)
|
||||
{
|
||||
// Discover all other filter properties that belong to this same branch
|
||||
var branchFilters = DiscoverBranchFilters(branchInfo, entityMap, filterProperties, appliedFilterKeys);
|
||||
|
||||
// Apply them all together in one .Any() or nested .Where()
|
||||
query = ApplyBranchFilter(query, parameter, branchInfo, branchFilters);
|
||||
|
||||
foreach (var usedKey in branchFilters.Keys) appliedFilterKeys.Add(usedKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static List<PropertyMetadata> ScanEntity(Type type, int level = 1, string path = "", PropertyMetadata parent = null)
|
||||
{
|
||||
var metaList = new List<PropertyMetadata>();
|
||||
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
foreach (var p in props)
|
||||
{
|
||||
var currentPath = string.IsNullOrEmpty(path) ? p.Name : $"{path}.{p.Name}";
|
||||
var meta = new PropertyMetadata
|
||||
{
|
||||
Name = p.Name,
|
||||
Path = currentPath,
|
||||
Level = level,
|
||||
PropertyInfo = p,
|
||||
PropertyType = p.PropertyType,
|
||||
Parent = parent
|
||||
};
|
||||
|
||||
metaList.Add(meta);
|
||||
|
||||
// If it's a complex type (not a system type or enum), dive deeper
|
||||
if (IsComplexType(p.PropertyType))
|
||||
{
|
||||
var nextType = GetUnderlyingType(p.PropertyType);
|
||||
if (nextType != type) // Prevent infinite recursion for parent/child relations
|
||||
{
|
||||
metaList.AddRange(ScanEntity(nextType, level + 1, currentPath, meta));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metaList;
|
||||
}
|
||||
|
||||
private static bool IsComplexType(Type type)
|
||||
{
|
||||
if (type.IsPrimitive || type == typeof(string) || type == typeof(Guid) || type == typeof(DateTime) || type == typeof(decimal) || type == typeof(double) || type.IsEnum)
|
||||
return false;
|
||||
|
||||
var underlying = GetUnderlyingType(type);
|
||||
return underlying != type || (!underlying.Namespace?.StartsWith("System") ?? true);
|
||||
}
|
||||
|
||||
private static Type GetUnderlyingType(Type type)
|
||||
{
|
||||
if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>) || type.GetGenericTypeDefinition() == typeof(ICollection<>) || type.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
||||
return type.GetGenericArguments()[0];
|
||||
|
||||
if (type.IsArray) return type.GetElementType();
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private static PropertyMetadata FindBranchRoot(PropertyMetadata meta)
|
||||
{
|
||||
var current = meta;
|
||||
PropertyMetadata collectionRoot = null;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
if (IsCollection(current.PropertyType))
|
||||
collectionRoot = current;
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return collectionRoot;
|
||||
}
|
||||
|
||||
private static bool IsCollection(Type type) => type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type);
|
||||
|
||||
private static Dictionary<string, (PropertyMetadata Meta, object Value)> DiscoverBranchFilters(
|
||||
PropertyMetadata branchRoot,
|
||||
List<PropertyMetadata> allMeta,
|
||||
Dictionary<string, object> filterValues,
|
||||
HashSet<string> appliedKeys)
|
||||
{
|
||||
var results = new Dictionary<string, (PropertyMetadata, object)>();
|
||||
|
||||
// Find all metadata items that are descendants of this branch root
|
||||
var descendants = allMeta.Where(m => m.Path.StartsWith(branchRoot.Path + ".")).ToList();
|
||||
|
||||
foreach (var d in descendants)
|
||||
{
|
||||
if (filterValues.TryGetValue(d.Name, out var val) && !appliedKeys.Contains(d.Name))
|
||||
{
|
||||
results[d.Name] = (d, val);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static IQueryable<T> ApplyBranchFilter<T>(
|
||||
IQueryable<T> query,
|
||||
ParameterExpression rootParam,
|
||||
PropertyMetadata branchRoot,
|
||||
Dictionary<string, (PropertyMetadata Meta, object Value)> branchFilters)
|
||||
{
|
||||
var itemType = GetUnderlyingType(branchRoot.PropertyType);
|
||||
var itemParam = Expression.Parameter(itemType, "t");
|
||||
|
||||
Expression branchExpression = null;
|
||||
|
||||
foreach (var bf in branchFilters.Values)
|
||||
{
|
||||
// Resolve path relative to the collection item
|
||||
// Example: branchRoot is "Translations", bf.Meta.Path is "Translations.Info.Name"
|
||||
// Relative path is "Info.Name"
|
||||
var relativePath = bf.Meta.Path.Substring(branchRoot.Path.Length + 1);
|
||||
var propertyExpr = BuildRelativePropertyExpression(itemParam, relativePath);
|
||||
|
||||
var comparison = BuildComparisonFromExpression(propertyExpr, bf.Value);
|
||||
|
||||
if (comparison != null)
|
||||
{
|
||||
branchExpression = branchExpression == null
|
||||
? comparison
|
||||
: Expression.AndAlso(branchExpression, comparison);
|
||||
}
|
||||
}
|
||||
|
||||
if (branchExpression == null) return query;
|
||||
|
||||
// Build: x => x.Translations.Any(t => t.Info.Name == "..." && t.Language == "...")
|
||||
var anyMethod = typeof(Enumerable).GetMethods()
|
||||
.First(m => m.Name == "Any" && m.GetParameters().Length == 2)
|
||||
.MakeGenericMethod(itemType);
|
||||
|
||||
var collectionExpr = Expression.Property(rootParam, branchRoot.PropertyInfo);
|
||||
var lambda = Expression.Lambda(branchExpression, itemParam);
|
||||
var anyCall = Expression.Call(null, anyMethod, collectionExpr, lambda);
|
||||
|
||||
var finalLambda = Expression.Lambda<Func<T, bool>>(anyCall, rootParam);
|
||||
return query.Where(finalLambda);
|
||||
}
|
||||
|
||||
private static Expression BuildRelativePropertyExpression(Expression param, string path)
|
||||
{
|
||||
Expression expr = param;
|
||||
foreach (var part in path.Split('.'))
|
||||
{
|
||||
expr = Expression.Property(expr, part);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private static Expression? BuildComparison(Expression param, PropertyInfo prop, object? value)
|
||||
{
|
||||
var propertyExpr = Expression.Property(param, prop);
|
||||
return BuildComparisonFromExpression(propertyExpr, value);
|
||||
}
|
||||
|
||||
private static Expression? BuildComparisonFromExpression(Expression propertyExpr, object? value)
|
||||
{
|
||||
if (value == null) return null;
|
||||
|
||||
if (propertyExpr.Type == typeof(string))
|
||||
{
|
||||
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
|
||||
|
||||
// x => x.Property.Contains(value)
|
||||
var searchValue = Expression.Constant(value.ToString() ?? string.Empty, typeof(string));
|
||||
|
||||
return Expression.Call(propertyExpr, containsMethod, searchValue);
|
||||
}
|
||||
|
||||
if (IsCollection(propertyExpr.Type) && GetUnderlyingType(propertyExpr.Type) == typeof(string))
|
||||
{
|
||||
var anyMethod = typeof(Enumerable).GetMethods()
|
||||
.First(m => m.Name == "Any" && m.GetParameters().Length == 2)
|
||||
.MakeGenericMethod(typeof(string));
|
||||
|
||||
var itemParam = Expression.Parameter(typeof(string), "s");
|
||||
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) })!;
|
||||
|
||||
var searchValue = Expression.Constant(value.ToString() ?? string.Empty, typeof(string));
|
||||
var itemContains = Expression.Call(itemParam, containsMethod, searchValue);
|
||||
|
||||
var lambda = Expression.Lambda(itemContains, itemParam);
|
||||
return Expression.Call(null, anyMethod, propertyExpr, lambda);
|
||||
}
|
||||
|
||||
// Handle Nullable types for equality
|
||||
var memberType = propertyExpr.Type;
|
||||
if (memberType.IsGenericType && memberType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
propertyExpr = Expression.Convert(propertyExpr, memberType.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var convertedValue = Convert.ChangeType(value, propertyExpr.Type);
|
||||
return Expression.Equal(propertyExpr, Expression.Constant(convertedValue, propertyExpr.Type));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Expression.Equal(propertyExpr, Expression.Constant(value, propertyExpr.Type));
|
||||
}
|
||||
}
|
||||
|
||||
private class PropertyMetadata
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public int Level { get; set; }
|
||||
public PropertyInfo PropertyInfo { get; set; }
|
||||
public Type PropertyType { get; set; }
|
||||
public PropertyMetadata Parent { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public class AdvancedQueryBuilder
|
||||
{
|
||||
private readonly StringBuilder _builder;
|
||||
private string _preText = string.Empty;
|
||||
private string _afterText = string.Empty;
|
||||
private string _separator = "&";
|
||||
|
||||
public AdvancedQueryBuilder()
|
||||
{
|
||||
_builder = new StringBuilder();
|
||||
}
|
||||
|
||||
public AdvancedQueryBuilder AddPreText(string preText)
|
||||
{
|
||||
_preText = preText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedQueryBuilder AddAfterText(string afterText)
|
||||
{
|
||||
_afterText = afterText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedQueryBuilder AddSeprator(string separator)
|
||||
{
|
||||
_separator = separator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedQueryBuilder AddModels(params object?[] models)
|
||||
{
|
||||
if (models == null)
|
||||
return this;
|
||||
|
||||
foreach (var model in models)
|
||||
{
|
||||
if (model == null)
|
||||
continue;
|
||||
|
||||
var properties = model.GetType().GetProperties();
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var value = prop.GetValue(model)?.ToString();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
AppendParameter(prop.Name.ToLower(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void AppendParameter(string key, string value)
|
||||
{
|
||||
if (_builder.Length > 0)
|
||||
_builder.Append(_separator);
|
||||
_builder.Append($"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(value)}");
|
||||
}
|
||||
|
||||
public override string ToString() => $"{_preText}{_builder}{_afterText}";
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class EnumExtensions
|
||||
{
|
||||
public static string GetDisplayName(this Enum value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
// Get the enum field name
|
||||
string name = value.ToString();
|
||||
|
||||
// Get the field info for the enum value
|
||||
FieldInfo? field = value.GetType().GetField(name);
|
||||
if (field == null)
|
||||
return name; // Not a defined enum value (e.g., flags combination)
|
||||
|
||||
// Check for the [Display] attribute
|
||||
DisplayAttribute? displayAttribute = field.GetCustomAttribute<DisplayAttribute>();
|
||||
return displayAttribute?.GetName() ?? name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class FilterExtension
|
||||
{
|
||||
public static IQueryable<T> ApplyFilter<T, TFilter>(this IQueryable<T> query, TFilter filter)
|
||||
where TFilter : class
|
||||
{
|
||||
if (filter == null)
|
||||
return query;
|
||||
|
||||
var parameter = Expression.Parameter(typeof(T), "x");
|
||||
Expression finalExpression = null;
|
||||
|
||||
var filterProperties = typeof(TFilter)
|
||||
.GetProperties()
|
||||
.Where(p => p.GetValue(filter) != null);
|
||||
|
||||
foreach (var filterProperty in filterProperties)
|
||||
{
|
||||
var filterValue = filterProperty.GetValue(filter);
|
||||
if (filterValue == null)
|
||||
continue;
|
||||
|
||||
var propertyName = filterProperty.Name;
|
||||
PropertyInfo entityProperty;
|
||||
|
||||
Expression propertyExpression = null;
|
||||
Expression comparison = null;
|
||||
|
||||
// Handle special suffix cases
|
||||
if (propertyName.EndsWith("_Start") || propertyName.EndsWith("_Min"))
|
||||
{
|
||||
entityProperty = typeof(T).GetProperty(
|
||||
propertyName.Substring(0, propertyName.LastIndexOf('_'))
|
||||
);
|
||||
if (entityProperty != null)
|
||||
{
|
||||
propertyExpression = Expression.Property(parameter, entityProperty);
|
||||
comparison = Expression.GreaterThanOrEqual(
|
||||
propertyExpression,
|
||||
Expression.Constant(filterValue, entityProperty.PropertyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (propertyName.EndsWith("_End") || propertyName.EndsWith("_Max"))
|
||||
{
|
||||
entityProperty = typeof(T).GetProperty(
|
||||
propertyName.Substring(0, propertyName.LastIndexOf('_'))
|
||||
);
|
||||
if (entityProperty != null)
|
||||
{
|
||||
propertyExpression = Expression.Property(parameter, entityProperty);
|
||||
comparison = Expression.LessThanOrEqual(
|
||||
propertyExpression,
|
||||
Expression.Constant(filterValue, entityProperty.PropertyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (propertyName.EndsWith("Name", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
entityProperty = typeof(T).GetProperty(propertyName);
|
||||
if (entityProperty != null && entityProperty.PropertyType == typeof(string))
|
||||
{
|
||||
propertyExpression = Expression.Property(parameter, entityProperty);
|
||||
|
||||
// For string Contains operation
|
||||
var containsMethod = typeof(string).GetMethod(
|
||||
"Contains",
|
||||
new[] { typeof(string) }
|
||||
);
|
||||
var filterValueString = filterValue.ToString();
|
||||
|
||||
comparison = Expression.Call(
|
||||
propertyExpression,
|
||||
containsMethod,
|
||||
Expression.Constant(filterValueString, typeof(string))
|
||||
);
|
||||
}
|
||||
else if (entityProperty != null)
|
||||
{
|
||||
// Fall back to equality comparison if it's not a string
|
||||
propertyExpression = Expression.Property(parameter, entityProperty);
|
||||
comparison = Expression.Equal(
|
||||
propertyExpression,
|
||||
Expression.Constant(filterValue, entityProperty.PropertyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
entityProperty = typeof(T).GetProperty(propertyName);
|
||||
if (entityProperty != null)
|
||||
{
|
||||
propertyExpression = Expression.Property(parameter, entityProperty);
|
||||
comparison = Expression.Equal(
|
||||
propertyExpression,
|
||||
Expression.Constant(filterValue, entityProperty.PropertyType)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (comparison != null)
|
||||
{
|
||||
finalExpression =
|
||||
finalExpression == null
|
||||
? comparison
|
||||
: Expression.AndAlso(finalExpression, comparison);
|
||||
}
|
||||
}
|
||||
|
||||
if (finalExpression != null)
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<T, bool>>(finalExpression, parameter);
|
||||
query = query.Where(lambda);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
public static IEnumerable<List<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
|
||||
{
|
||||
var batch = new List<T>(batchSize);
|
||||
foreach (var item in source)
|
||||
{
|
||||
batch.Add(item);
|
||||
if (batch.Count == batchSize)
|
||||
{
|
||||
yield return batch;
|
||||
batch = new List<T>(batchSize);
|
||||
}
|
||||
}
|
||||
if (batch.Count > 0)
|
||||
{
|
||||
yield return batch;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Generic.Contracts.Generics;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class Pagination<T>
|
||||
where T : class
|
||||
{
|
||||
public static IQueryable<T> Paginate(IQueryable<T> query, Pagination pagination)
|
||||
{
|
||||
return query
|
||||
.Skip((pagination.CurrentPage.Value - 1) * pagination.PageSize.Value)
|
||||
.Take(pagination.PageSize.Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Generic.Contracts.Generics;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class PaginationExtension
|
||||
{
|
||||
public static IQueryable<T> Paginate<T>(this IQueryable<T> query, Pagination pagination)
|
||||
{
|
||||
if (pagination.CurrentPage.HasValue && pagination.PageSize.HasValue)
|
||||
return query
|
||||
.Skip((pagination.CurrentPage.Value - 1) * pagination.PageSize.Value)
|
||||
.Take(pagination.PageSize.Value);
|
||||
else
|
||||
return query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Generic.Contracts.Generics;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public class QueryBuilder
|
||||
{
|
||||
private readonly StringBuilder _builder;
|
||||
|
||||
public QueryBuilder()
|
||||
{
|
||||
_builder = new StringBuilder();
|
||||
}
|
||||
|
||||
public QueryBuilder AddBase(string baseString)
|
||||
{
|
||||
_builder.Insert(0, baseString);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder AddPagination(Pagination pagination)
|
||||
{
|
||||
if (pagination != null)
|
||||
{
|
||||
AppendParameter("currentPage", pagination.CurrentPage.Value);
|
||||
AppendParameter("pageSize", pagination.PageSize.Value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder AddSort(SortModel sort)
|
||||
{
|
||||
if (sort != null && !string.IsNullOrEmpty(sort.SortBy))
|
||||
{
|
||||
AppendParameter("Column", sort.SortBy);
|
||||
AppendParameter("sortDirection", sort.SortDirection.ToString());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryBuilder AddModelProperties<TModel>(TModel model)
|
||||
{
|
||||
if (model == null)
|
||||
return this;
|
||||
|
||||
var properties = typeof(TModel).GetProperties();
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
var value = prop.GetValue(model)?.ToString();
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
AppendParameter(prop.Name.ToLower(), value);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private void AppendParameter(string key, string value)
|
||||
{
|
||||
if (_builder.Length > 0)
|
||||
_builder.Append('&');
|
||||
_builder.Append($"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(value)}");
|
||||
}
|
||||
|
||||
private void AppendParameter(string key, int value)
|
||||
{
|
||||
AppendParameter(key, value.ToString());
|
||||
}
|
||||
|
||||
public override string ToString() => _builder.ToString();
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Generic.Contracts.Generics;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class QueryableExtensions
|
||||
{
|
||||
public static IQueryable<T> ApplySorting<T>(this IQueryable<T> source, SortModel sortBy)
|
||||
{
|
||||
// Parameter for lambda expression, e.g., "p" in "p => p.Property"
|
||||
var parameter = Expression.Parameter(typeof(T), "p");
|
||||
|
||||
// Access the property on the parameter, e.g., "p.Property"
|
||||
var property = Expression.Property(parameter, sortBy.SortBy);
|
||||
|
||||
// Cast property access to an object (boxing value types)
|
||||
var converted = Expression.Convert(property, typeof(object));
|
||||
|
||||
// Create the lambda expression, e.g., "p => (object)p.Property"
|
||||
var keySelector = Expression.Lambda<Func<T, object>>(converted, parameter);
|
||||
|
||||
// Apply OrderBy or OrderByDescending based on the ascending flag
|
||||
return sortBy.SortDirection == SortDir.Ascending
|
||||
? source.OrderBy(keySelector)
|
||||
: source.OrderByDescending(keySelector);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Generic.Contracts.Generics;
|
||||
|
||||
namespace Generic.Services;
|
||||
|
||||
public static class SortExt
|
||||
{
|
||||
public static IQueryable<T> ApplySimpleSort<T>(this IQueryable<T> query, SortModel sortModel)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sortModel?.SortBy))
|
||||
return query;
|
||||
|
||||
return ApplySortInternal(query, new[] { sortModel });
|
||||
}
|
||||
|
||||
public static IQueryable<T> ApplyComplexSort<T>(this IQueryable<T> query, IEnumerable<SortModel> sortModels)
|
||||
{
|
||||
var models = sortModels?.Where(x => !string.IsNullOrWhiteSpace(x.SortBy)).ToList();
|
||||
if (models == null || !models.Any())
|
||||
return query;
|
||||
|
||||
return ApplySortInternal(query, models);
|
||||
}
|
||||
|
||||
private static IQueryable<T> ApplySortInternal<T>(IQueryable<T> query, IEnumerable<SortModel> sortModels)
|
||||
{
|
||||
var first = true;
|
||||
IOrderedQueryable<T> orderedQuery = null;
|
||||
|
||||
foreach (var model in sortModels)
|
||||
{
|
||||
var propertyInfo = typeof(T).GetProperty(
|
||||
model.SortBy,
|
||||
BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance
|
||||
);
|
||||
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
// Decide whether to throw or ignore. For now, letting it throw or we could ignore.
|
||||
// Original code threw exception, so we will throw to be safe/consistent.
|
||||
throw new ArgumentException(
|
||||
$"No property '{model.SortBy}' on type '{typeof(T).Name}'"
|
||||
);
|
||||
}
|
||||
|
||||
var parameter = Expression.Parameter(typeof(T), "x");
|
||||
var property = Expression.Property(parameter, propertyInfo);
|
||||
var lambda = Expression.Lambda(property, parameter);
|
||||
|
||||
string methodName = "";
|
||||
|
||||
if (first)
|
||||
{
|
||||
methodName = model.SortDirection == SortDir.Descending ? "OrderByDescending" : "OrderBy";
|
||||
|
||||
var method = typeof(Queryable).GetMethods()
|
||||
.First(m => m.Name == methodName && m.GetParameters().Length == 2)
|
||||
.MakeGenericMethod(typeof(T), propertyInfo.PropertyType);
|
||||
|
||||
orderedQuery = (IOrderedQueryable<T>)method.Invoke(null, new object[] { query, lambda });
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
methodName = model.SortDirection == SortDir.Descending ? "ThenByDescending" : "ThenBy";
|
||||
|
||||
var method = typeof(Queryable).GetMethods()
|
||||
.First(m => m.Name == methodName && m.GetParameters().Length == 2)
|
||||
.MakeGenericMethod(typeof(T), propertyInfo.PropertyType);
|
||||
|
||||
orderedQuery = (IOrderedQueryable<T>)method.Invoke(null, new object[] { orderedQuery, lambda });
|
||||
}
|
||||
}
|
||||
|
||||
return orderedQuery ?? query;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user