Skip to content

Update Specification extensions. #1111

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

Open
wants to merge 1 commit into
base: main
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
4 changes: 2 additions & 2 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ardalis.Specification" Version="8.0.0" />
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
<PackageVersion Include="Ardalis.Specification" Version="9.0.1" />
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="9.0.1" />
<PackageVersion Include="Asp.Versioning.Http" Version="8.1.0" />
<PackageVersion Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.1.0" />
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="9.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

namespace FSH.Framework.Core.Specifications;

public class EntitiesByBaseFilterSpec<T, TResult> : Specification<T, TResult>
public class EntitiesByBaseFilterSpec<T, TResult> : Specification<T, TResult> where T : class
{
public EntitiesByBaseFilterSpec(BaseFilter filter) =>
Query.SearchBy(filter);
}

public class EntitiesByBaseFilterSpec<T> : Specification<T>
public class EntitiesByBaseFilterSpec<T> : Specification<T> where T : class
{
public EntitiesByBaseFilterSpec(BaseFilter filter) =>
Query.SearchBy(filter);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

namespace FSH.Framework.Core.Specifications;

public class EntitiesByPaginationFilterSpec<T, TResult> : EntitiesByBaseFilterSpec<T, TResult>
public class EntitiesByPaginationFilterSpec<T, TResult> : EntitiesByBaseFilterSpec<T, TResult> where T : class
{
public EntitiesByPaginationFilterSpec(PaginationFilter filter)
: base(filter) =>
Query.PaginateBy(filter);
}

public class EntitiesByPaginationFilterSpec<T> : EntitiesByBaseFilterSpec<T>
public class EntitiesByPaginationFilterSpec<T> : EntitiesByBaseFilterSpec<T> where T : class
{
public EntitiesByPaginationFilterSpec(PaginationFilter filter)
: base(filter) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@

namespace FSH.Framework.Core.Specifications;

// See https://github.com/ardalis/Specification/issues/53
public static class SpecificationBuilderExtensions
{
public static ISpecificationBuilder<T> SearchBy<T>(this ISpecificationBuilder<T> query, BaseFilter filter) =>
public static ISpecificationBuilder<T> SearchBy<T>(this ISpecificationBuilder<T> query, BaseFilter filter) where T : class =>
query
.SearchByKeyword(filter.Keyword)
.AdvancedSearch(filter.AdvancedSearch)
Expand Down Expand Up @@ -38,20 +37,20 @@ public static ISpecificationBuilder<T> PaginateBy<T>(this ISpecificationBuilder<
.OrderBy(filter.OrderBy);
}

public static IOrderedSpecificationBuilder<T> SearchByKeyword<T>(
public static ISpecificationBuilder<T> SearchByKeyword<T>(
this ISpecificationBuilder<T> specificationBuilder,
string? keyword) =>
string? keyword) where T : class =>
specificationBuilder.AdvancedSearch(new Search { Keyword = keyword });

public static IOrderedSpecificationBuilder<T> AdvancedSearch<T>(
public static ISpecificationBuilder<T> AdvancedSearch<T>(
this ISpecificationBuilder<T> specificationBuilder,
Search? search)
Search? search) where T : class
{
if (!string.IsNullOrEmpty(search?.Keyword))
{
if (search.Fields?.Any() is true)
{
// search seleted fields (can contain deeper nested fields)
// search selected fields (can contain deeper nested fields)
foreach (string field in search.Fields)
{
var paramExpr = Expression.Parameter(typeof(T));
Expand All @@ -76,15 +75,15 @@ public static IOrderedSpecificationBuilder<T> AdvancedSearch<T>(
}
}

return new OrderedSpecificationBuilder<T>(specificationBuilder.Specification);
return specificationBuilder;
}

private static void AddSearchPropertyByKeyword<T>(
this ISpecificationBuilder<T> specificationBuilder,
Expression propertyExpr,
ParameterExpression paramExpr,
string keyword,
string operatorSearch = FilterOperator.CONTAINS)
string operatorSearch = FilterOperator.CONTAINS) where T : class
{
if (propertyExpr is not MemberExpression memberExpr || memberExpr.Member is not PropertyInfo property)
{
Expand Down Expand Up @@ -112,38 +111,37 @@ private static void AddSearchPropertyByKeyword<T>(
var toLowerMethod = typeof(string).GetMethod("ToLower", Type.EmptyTypes);
Expression callToLowerMethod = Expression.Call(selectorExpr, toLowerMethod!);

var selector = Expression.Lambda<Func<T, string>>(callToLowerMethod, paramExpr);
var selector = Expression.Lambda<Func<T, string?>>(callToLowerMethod, paramExpr);

((List<SearchExpressionInfo<T>>)specificationBuilder.Specification.SearchCriterias)
.Add(new SearchExpressionInfo<T>(selector, searchTerm, 1));
specificationBuilder.Search(selector, searchTerm, 1);
}

public static IOrderedSpecificationBuilder<T> AdvancedFilter<T>(
public static ISpecificationBuilder<T> AdvancedFilter<T>(
this ISpecificationBuilder<T> specificationBuilder,
Filter? filter)
{
if (filter is not null)
{
var parameter = Expression.Parameter(typeof(T));

Expression binaryExpresioFilter;
Expression binaryExpressionFilter;

if (!string.IsNullOrEmpty(filter.Logic))
{
if (filter.Filters is null) throw new CustomException("The Filters attribute is required when declaring a logic");
binaryExpresioFilter = CreateFilterExpression(filter.Logic, filter.Filters, parameter);
binaryExpressionFilter = CreateFilterExpression(filter.Logic, filter.Filters, parameter);
}
else
{
var filterValid = GetValidFilter(filter);
binaryExpresioFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator!, filterValid.Value, parameter);
binaryExpressionFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator!, filterValid.Value, parameter);
}

((List<WhereExpressionInfo<T>>)specificationBuilder.Specification.WhereExpressions)
.Add(new WhereExpressionInfo<T>(Expression.Lambda<Func<T, bool>>(binaryExpresioFilter, parameter)));
var expr = Expression.Lambda<Func<T, bool>>(binaryExpressionFilter, parameter);
specificationBuilder.Where(expr);
}

return new OrderedSpecificationBuilder<T>(specificationBuilder.Specification);
return specificationBuilder;
}

private static Expression CreateFilterExpression(
Expand All @@ -155,20 +153,20 @@ private static Expression CreateFilterExpression(

foreach (var filter in filters)
{
Expression bExpresionFilter;
Expression bExpressionFilter;

if (!string.IsNullOrEmpty(filter.Logic))
{
if (filter.Filters is null) throw new CustomException("The Filters attribute is required when declaring a logic");
bExpresionFilter = CreateFilterExpression(filter.Logic, filter.Filters, parameter);
bExpressionFilter = CreateFilterExpression(filter.Logic, filter.Filters, parameter);
}
else
{
var filterValid = GetValidFilter(filter);
bExpresionFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator!, filterValid.Value, parameter);
bExpressionFilter = CreateFilterExpression(filterValid.Field!, filterValid.Operator!, filterValid.Value, parameter);
}

filterExpression = filterExpression is null ? bExpresionFilter : CombineFilter(logic, filterExpression, bExpresionFilter);
filterExpression = filterExpression is null ? bExpressionFilter : CombineFilter(logic, filterExpression, bExpressionFilter);
}

return filterExpression;
Expand All @@ -180,9 +178,9 @@ private static Expression CreateFilterExpression(
object? value,
ParameterExpression parameter)
{
var propertyExpresion = GetPropertyExpression(field, parameter);
var valueExpresion = GeValuetExpression(field, value, propertyExpresion.Type);
return CreateFilterExpression(propertyExpresion, valueExpresion, filterOperator);
var propertyExpression = GetPropertyExpression(field, parameter);
var valueExpression = GeValueExpression(field, value, propertyExpression.Type);
return CreateFilterExpression(propertyExpression, valueExpression, filterOperator);
}

private static Expression CreateFilterExpression(
Expand Down Expand Up @@ -211,14 +209,14 @@ private static Expression CreateFilterExpression(
};
}

private static Expression CombineFilter(
private static BinaryExpression CombineFilter(
string filterOperator,
Expression bExpresionBase,
Expression bExpresion) => filterOperator switch
Expression bExpressionBase,
Expression bExpression) => filterOperator switch
{
FilterLogic.AND => Expression.And(bExpresionBase, bExpresion),
FilterLogic.OR => Expression.Or(bExpresionBase, bExpresion),
FilterLogic.XOR => Expression.ExclusiveOr(bExpresionBase, bExpresion),
FilterLogic.AND => Expression.And(bExpressionBase, bExpression),
FilterLogic.OR => Expression.Or(bExpressionBase, bExpression),
FilterLogic.XOR => Expression.ExclusiveOr(bExpressionBase, bExpression),
_ => throw new ArgumentException("FilterLogic is not valid."),
};

Expand All @@ -238,7 +236,7 @@ private static MemberExpression GetPropertyExpression(
private static string GetStringFromJsonElement(object value)
=> ((JsonElement)value).GetString()!;

private static ConstantExpression GeValuetExpression(
private static ConstantExpression GeValueExpression(
string field,
object? value,
Type propertyType)
Expand Down Expand Up @@ -303,10 +301,12 @@ private static Filter GetValidFilter(Filter filter)
return filter;
}

public static IOrderedSpecificationBuilder<T> OrderBy<T>(
public static ISpecificationBuilder<T> OrderBy<T>(
this ISpecificationBuilder<T> specificationBuilder,
string[]? orderByFields)
{
IOrderedSpecificationBuilder<T> orderedBuilder = null!;

if (orderByFields is not null)
{
foreach (var field in ParseOrderBy(orderByFields))
Expand All @@ -323,12 +323,18 @@ public static IOrderedSpecificationBuilder<T> OrderBy<T>(
Expression.Convert(propertyExpr, typeof(object)),
paramExpr);

((List<OrderExpressionInfo<T>>)specificationBuilder.Specification.OrderExpressions)
.Add(new OrderExpressionInfo<T>(keySelector, field.Value));
orderedBuilder = field.Value switch
{
OrderTypeEnum.OrderBy => specificationBuilder.OrderBy(keySelector),
OrderTypeEnum.OrderByDescending => specificationBuilder.OrderByDescending(keySelector),
OrderTypeEnum.ThenBy => orderedBuilder.ThenBy(keySelector),
OrderTypeEnum.ThenByDescending => orderedBuilder.ThenByDescending(keySelector),
_ => throw new CustomException("OrderTypeEnum is not valid."),
};
}
}

return new OrderedSpecificationBuilder<T>(specificationBuilder.Specification);
return specificationBuilder;
}

private static Dictionary<string, OrderTypeEnum> ParseOrderBy(string[] orderByFields) =>
Expand Down