Skip to content

Commit

Permalink
Merge pull request #96 from ardalis/v5
Browse files Browse the repository at this point in the history
V5
  • Loading branch information
fiseni committed Mar 5, 2021
2 parents 4f3ff0f 176a25b commit 6aff64e
Show file tree
Hide file tree
Showing 78 changed files with 1,005 additions and 529 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 5,16 @@
<PackageId>Ardalis.Specification</PackageId>
<Title>Ardalis.Specification</Title>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Steve Smith (@ardalis); Scott DePouw; Fati Iseni</Authors>
<Authors>Steve Smith (@ardalis); Scott DePouw; Fati Iseni (@fiseni)</Authors>
<Company>Ardalis.com</Company>
<PackageProjectUrl>https://github.com/ardalis/specification</PackageProjectUrl>
<Description>A simple package with a base Specification class, for use in creating queries that work with Repository types.</Description>
<Summary>A simple package with a base Specification class, for use in creating queries that work with Repository types.</Summary>
<RepositoryUrl>https://github.com/ardalis/specification</RepositoryUrl>
<PackageTags>spec;specification;repository;ddd</PackageTags>
<Version>4.2.0</Version>
<PackageReleaseNotes>Refactoring and chaining improvements; added Search functionality; added InMemory post processing functionality.</PackageReleaseNotes>
<Version>5.0.3</Version>
<PackageReleaseNotes>
Major refactoring. Updated to .NET 5. DbSet extensions. Post-processor support. AsSplitQuery support. AsNoTrackingWithIdentityResolution support. Generic constraints. New Evaluate method. New single result interface. New IReadRepositoryOfT interface.</PackageReleaseNotes>
<AssemblyName>Ardalis.Specification</AssemblyName>
<PackageIcon>icon.png</PackageIcon>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 4,7 @@

namespace Ardalis.Specification
{
public interface IIncludableSpecificationBuilder<T, out TProperty> : ISpecificationBuilder<T>
public interface IIncludableSpecificationBuilder<T, out TProperty> : ISpecificationBuilder<T> where T : class
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 4,7 @@

namespace Ardalis.Specification
{
public class IncludableSpecificationBuilder<T, TProperty> : IIncludableSpecificationBuilder<T, TProperty>
public class IncludableSpecificationBuilder<T, TProperty> : IIncludableSpecificationBuilder<T, TProperty> where T : class
{
public Specification<T> Specification { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 43,7 @@ public static IOrderedSpecificationBuilder<T> OrderByDescending<T>(

public static IIncludableSpecificationBuilder<T, TProperty> Include<T, TProperty>(
this ISpecificationBuilder<T> specificationBuilder,
Expression<Func<T, TProperty>> includeExpression)
Expression<Func<T, TProperty>> includeExpression) where T : class
{
var info = new IncludeExpressionInfo(includeExpression, typeof(T), typeof(TProperty));

Expand All @@ -56,7 56,7 @@ public static IIncludableSpecificationBuilder<T, TProperty> Include<T, TProperty

public static ISpecificationBuilder<T> Include<T>(
this ISpecificationBuilder<T> specificationBuilder,
string includeString)
string includeString) where T : class
{
((List<string>)specificationBuilder.Specification.IncludeStrings).Add(includeString);
return specificationBuilder;
Expand All @@ -67,7 67,7 @@ public static ISpecificationBuilder<T> Search<T>(
this ISpecificationBuilder<T> specificationBuilder,
Expression<Func<T, string>> selector,
string searchTerm,
int searchGroup = 1)
int searchGroup = 1) where T : class
{
((List<(Expression<Func<T, string>> Selector, string SearchTerm, int SearchGroup)>)specificationBuilder.Specification.SearchCriterias)
.Add((selector, searchTerm, searchGroup));
Expand Down Expand Up @@ -109,11 109,11 @@ public static ISpecificationBuilder<T> Paginate<T>(
return specificationBuilder;
}

public static ISpecificationBuilder<T> InMemory<T>(
public static ISpecificationBuilder<T> PostProcessingAction<T>(
this ISpecificationBuilder<T> specificationBuilder,
Func<List<T>, List<T>> predicate)
Func<IEnumerable<T>, IEnumerable<T>> predicate)
{
specificationBuilder.Specification.InMemory = predicate;
specificationBuilder.Specification.PostProcessingAction = predicate;

return specificationBuilder;
}
Expand All @@ -127,11 127,11 @@ public static ISpecificationBuilder<T, TResult> Select<T, TResult>(
return specificationBuilder;
}

public static ISpecificationBuilder<T, TResult> InMemory<T, TResult>(
public static ISpecificationBuilder<T, TResult> PostProcessingAction<T, TResult>(
this ISpecificationBuilder<T, TResult> specificationBuilder,
Func<List<TResult>, List<TResult>> predicate)
Func<IEnumerable<TResult>, IEnumerable<TResult>> predicate)
{
specificationBuilder.Specification.InMemory = predicate;
specificationBuilder.Specification.PostProcessingAction = predicate;

return specificationBuilder;
}
Expand All @@ -143,10 143,9 @@ public static ISpecificationBuilder<T, TResult> InMemory<T, TResult>(
/// <param name="args">Any arguments used in defining the specification</param>
public static ISpecificationBuilder<T> EnableCache<T>(
this ISpecificationBuilder<T> specificationBuilder,
string specificationName, params object[] args)
string specificationName, params object[] args) where T : class
{
Guard.Against.NullOrEmpty(specificationName, nameof(specificationName));
Guard.Against.NullOrEmpty(specificationBuilder.Specification.WhereExpressions, nameof(specificationBuilder.Specification.WhereExpressions));

specificationBuilder.Specification.CacheKey = $"{specificationName}-{string.Join("-", args)}";

Expand All @@ -156,9 155,26 @@ public static ISpecificationBuilder<T> EnableCache<T>(
}

public static ISpecificationBuilder<T> AsNoTracking<T>(
this ISpecificationBuilder<T> specificationBuilder)
this ISpecificationBuilder<T> specificationBuilder) where T : class
{
specificationBuilder.Specification.AsNoTracking = true;

return specificationBuilder;
}

public static ISpecificationBuilder<T> AsSplitQuery<T>(
this ISpecificationBuilder<T> specificationBuilder) where T : class
{
specificationBuilder.Specification.AsSplitQuery = true;

return specificationBuilder;
}

public static ISpecificationBuilder<T> AsNoTrackingWithIdentityResolution<T>(
this ISpecificationBuilder<T> specificationBuilder) where T : class
{
specificationBuilder.Specification.AsNoTrackingWithIdentityResolution = true;

return specificationBuilder;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ardalis.Specification
{
public interface IEvaluator
{
bool IsCriteriaEvaluator { get; }

IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ardalis.Specification
{
public interface IInMemoryEvaluator
{
IEnumerable<T> Evaluate<T>(IEnumerable<T> query, ISpecification<T> specification);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ardalis.Specification
{
public interface IInMemorySpecificationEvaluator
{
IEnumerable<TResult> Evaluate<T, TResult>(IEnumerable<T> source, ISpecification<T, TResult> specification);
IEnumerable<T> Evaluate<T>(IEnumerable<T> source, ISpecification<T> specification);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 8,7 @@ namespace Ardalis.Specification
/// <summary>
/// Evaluates the logic encapsulated by an <see cref="ISpecification{T}"/>.
/// </summary>
/// <typeparam name="T">The type of the <see cref="ISpecification{T}"/> being evaluated.</typeparam>
public interface ISpecificationEvaluator<T> where T : class
public interface ISpecificationEvaluator
{
/// <summary>
/// Applies the logic encapsulated by <paramref name="specification"/> to given <paramref name="inputQuery"/>,
Expand All @@ -19,13 18,13 @@ public interface ISpecificationEvaluator<T> where T : class
/// <param name="inputQuery">The sequence of <typeparamref name="T"/></param>
/// <param name="specification">The encapsulated query logic.</param>
/// <returns>A filtered sequence of <typeparamref name="TResult"/></returns>
IQueryable<TResult> GetQuery<TResult>(IQueryable<T> inputQuery, ISpecification<T, TResult> specification);
IQueryable<TResult> GetQuery<T, TResult>(IQueryable<T> inputQuery, ISpecification<T, TResult> specification) where T : class;
/// <summary>
/// Applies the logic encapsulated by <paramref name="specification"/> to given <paramref name="inputQuery"/>.
/// </summary>
/// <param name="inputQuery">The sequence of <typeparamref name="T"/></param>
/// <param name="specification">The encapsulated query logic.</param>
/// <returns>A filtered sequence of <typeparamref name="T"/></returns>
IQueryable<T> GetQuery(IQueryable<T> inputQuery, ISpecification<T> specification);
IQueryable<T> GetQuery<T>(IQueryable<T> inputQuery, ISpecification<T> specification, bool evaluateCriteriaOnly = false) where T : class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Ardalis.Specification
{
public class InMemorySpecificationEvaluator : IInMemorySpecificationEvaluator
{
// Will use singleton for default configuration. Yet, it can be instantiated if necessary, with default or provided evaluators.
public static InMemorySpecificationEvaluator Default { get; } = new InMemorySpecificationEvaluator();

private readonly List<IInMemoryEvaluator> evaluators = new List<IInMemoryEvaluator>();

public InMemorySpecificationEvaluator()
{
this.evaluators.AddRange(new IInMemoryEvaluator[]
{
WhereEvaluator.Instance,
OrderEvaluator.Instance,
PaginationEvaluator.Instance
});
}
public InMemorySpecificationEvaluator(IEnumerable<IInMemoryEvaluator> evaluators)
{
this.evaluators.AddRange(evaluators);
}

public virtual IEnumerable<TResult> Evaluate<T, TResult>(IEnumerable<T> source, ISpecification<T, TResult> specification)
{
_ = specification.Selector ?? throw new SelectorNotFoundException();

var baseQuery = Evaluate(source, (ISpecification<T>)specification);

var resultQuery = baseQuery.Select(specification.Selector.Compile());

return specification.PostProcessingAction == null
? resultQuery
: specification.PostProcessingAction(resultQuery);
}

public virtual IEnumerable<T> Evaluate<T>(IEnumerable<T> source, ISpecification<T> specification)
{
if (specification.SearchCriterias.Count() > 0)
{
throw new NotSupportedException("The specification contains Search expressions and can't be evaluated with in-memory evaluator.");
}

foreach (var evaluator in evaluators)
{
source = evaluator.Evaluate(source, specification);
}

return specification.PostProcessingAction == null
? source
: specification.PostProcessingAction(source);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ardalis.Specification
{
public class OrderEvaluator : IEvaluator, IInMemoryEvaluator
{
private OrderEvaluator() { }
public static OrderEvaluator Instance { get; } = new OrderEvaluator();

public bool IsCriteriaEvaluator { get; } = false;

public IQueryable<T> GetQuery<T>(IQueryable<T> query, ISpecification<T> specification) where T : class
{
if (specification.OrderExpressions != null)
{
if (specification.OrderExpressions.Where(x => x.OrderType == OrderTypeEnum.OrderBy ||
x.OrderType == OrderTypeEnum.OrderByDescending).Count() > 1)
{
throw new DuplicateOrderChainException();
}

IOrderedQueryable<T>? orderedQuery = null;
foreach (var orderExpression in specification.OrderExpressions)
{
if (orderExpression.OrderType == OrderTypeEnum.OrderBy)
{
orderedQuery = query.OrderBy(orderExpression.KeySelector);
}
else if (orderExpression.OrderType == OrderTypeEnum.OrderByDescending)
{
orderedQuery = query.OrderByDescending(orderExpression.KeySelector);
}
else if (orderExpression.OrderType == OrderTypeEnum.ThenBy)
{
orderedQuery = orderedQuery.ThenBy(orderExpression.KeySelector);
}
else if (orderExpression.OrderType == OrderTypeEnum.ThenByDescending)
{
orderedQuery = orderedQuery.ThenByDescending(orderExpression.KeySelector);
}
}

if (orderedQuery != null)
{
query = orderedQuery;
}
}

return query;
}

public IEnumerable<T> Evaluate<T>(IEnumerable<T> query, ISpecification<T> specification)
{
if (specification.OrderExpressions != null)
{
if (specification.OrderExpressions.Where(x => x.OrderType == OrderTypeEnum.OrderBy ||
x.OrderType == OrderTypeEnum.OrderByDescending).Count() > 1)
{
throw new DuplicateOrderChainException();
}

IOrderedEnumerable<T>? orderedQuery = null;
foreach (var orderExpression in specification.OrderExpressions)
{
if (orderExpression.OrderType == OrderTypeEnum.OrderBy)
{
orderedQuery = query.OrderBy(orderExpression.KeySelector.Compile());
}
else if (orderExpression.OrderType == OrderTypeEnum.OrderByDescending)
{
orderedQuery = query.OrderByDescending(orderExpression.KeySelector.Compile());
}
else if (orderExpression.OrderType == OrderTypeEnum.ThenBy)
{
orderedQuery = orderedQuery.ThenBy(orderExpression.KeySelector.Compile());
}
else if (orderExpression.OrderType == OrderTypeEnum.ThenByDescending)
{
orderedQuery = orderedQuery.ThenByDescending(orderExpression.KeySelector.Compile());
}
}

if (orderedQuery != null)
{
query = orderedQuery;
}
}

return query;
}
}
}
Loading

0 comments on commit 6aff64e

Please sign in to comment.