Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ namespace Ardalis.Specification
{
public static class SpecificationBuilderExtensions
{
/// <summary>
/// Specify a predicate that will be applied to the query
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
/// <param name="criteria"></param>
public static ISpecificationBuilder<T> Where<T>(
this ISpecificationBuilder<T> specificationBuilder,
Expression<Func<T, bool>> criteria)
Expand All @@ -16,6 +22,12 @@ public static ISpecificationBuilder<T> Where<T>(
return specificationBuilder;
}

/// <summary>
/// Specify the query result will be ordered by <paramref name="orderExpression"/> in an ascending order
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
/// <param name="orderExpression"></param>
public static IOrderedSpecificationBuilder<T> OrderBy<T>(
this ISpecificationBuilder<T> specificationBuilder,
Expression<Func<T, object?>> orderExpression)
Expand All @@ -28,6 +40,12 @@ public static IOrderedSpecificationBuilder<T> OrderBy<T>(
return orderedSpecificationBuilder;
}

/// <summary>
/// Specify the query result will be ordered by <paramref name="orderExpression"/> in a descending order
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
/// <param name="orderExpression"></param>
public static IOrderedSpecificationBuilder<T> OrderByDescending<T>(
this ISpecificationBuilder<T> specificationBuilder,
Expression<Func<T, object?>> orderExpression)
Expand All @@ -40,6 +58,15 @@ public static IOrderedSpecificationBuilder<T> OrderByDescending<T>(
return orderedSpecificationBuilder;
}

/// <summary>
/// Specify an include expression.
/// This information is utilized to build Include function in the query, which ORM tools like Entity Framework use
/// to include related entities (via navigation properties) in the query result.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <param name="specificationBuilder"></param>
/// <param name="includeExpression"></param>
public static IIncludableSpecificationBuilder<T, TProperty> Include<T, TProperty>(
this ISpecificationBuilder<T> specificationBuilder,
Expression<Func<T, TProperty>> includeExpression) where T : class
Expand All @@ -53,6 +80,12 @@ public static IIncludableSpecificationBuilder<T, TProperty> Include<T, TProperty
return includeBuilder;
}

/// <summary>
/// Specify a collection of navigation properties, as strings, to include in the query.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
/// <param name="includeString"></param>
public static ISpecificationBuilder<T> Include<T>(
this ISpecificationBuilder<T> specificationBuilder,
string includeString) where T : class
Expand All @@ -61,7 +94,15 @@ public static ISpecificationBuilder<T> Include<T>(
return specificationBuilder;
}


/// <summary>
/// Specify a 'SQL LIKE' operations for search purposes
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
/// <param name="selector">the property to apply the SQL LIKE against</param>
/// <param name="searchTerm">the value to use for the SQL LIKE</param>
/// <param name="searchGroup">the index used to group sets of Selectors and SearchTerms together</param>
/// <returns></returns>
public static ISpecificationBuilder<T> Search<T>(
this ISpecificationBuilder<T> specificationBuilder,
Expression<Func<T, string>> selector,
Expand All @@ -74,6 +115,9 @@ public static ISpecificationBuilder<T> Search<T>(
return specificationBuilder;
}

/// <summary>
/// Specify the number of elements to return.
/// </summary>
public static ISpecificationBuilder<T> Take<T>(
this ISpecificationBuilder<T> specificationBuilder,
int take)
Expand All @@ -85,6 +129,12 @@ public static ISpecificationBuilder<T> Take<T>(
return specificationBuilder;
}

/// <summary>
/// Specify the number of elements to skip before returning the remaining elements.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
/// <param name="skip">number of elements to skip</param>
public static ISpecificationBuilder<T> Skip<T>(
this ISpecificationBuilder<T> specificationBuilder,
int skip)
Expand All @@ -108,24 +158,36 @@ public static ISpecificationBuilder<T> Paginate<T>(
return specificationBuilder;
}

public static ISpecificationBuilder<T> PostProcessingAction<T>(
this ISpecificationBuilder<T> specificationBuilder,
Func<IEnumerable<T>, IEnumerable<T>> predicate)
/// <summary>
/// Specify a transform function to apply to the <typeparamref name="T"/> element
/// to produce another <typeparamref name="TResult"/> element.
/// </summary>
public static ISpecificationBuilder<T, TResult> Select<T, TResult>(
this ISpecificationBuilder<T, TResult> specificationBuilder,
Expression<Func<T, TResult>> selector)
{
specificationBuilder.Specification.PostProcessingAction = predicate;
specificationBuilder.Specification.Selector = selector;

return specificationBuilder;
}

public static ISpecificationBuilder<T, TResult> Select<T, TResult>(
this ISpecificationBuilder<T, TResult> specificationBuilder,
Expression<Func<T, TResult>> selector)
/// <summary>
/// Specify a transform function to apply to the result of the query
/// and returns the same <typeparamref name="T"/> type
/// </summary>
public static ISpecificationBuilder<T> PostProcessingAction<T>(
this ISpecificationBuilder<T> specificationBuilder,
Func<IEnumerable<T>, IEnumerable<T>> predicate)
{
specificationBuilder.Specification.Selector = selector;
specificationBuilder.Specification.PostProcessingAction = predicate;

return specificationBuilder;
}

/// <summary>
/// Specify a transform function to apply to the result of the query.
/// and returns another <typeparamref name="TResult"/> type
/// </summary>
public static ISpecificationBuilder<T, TResult> PostProcessingAction<T, TResult>(
this ISpecificationBuilder<T, TResult> specificationBuilder,
Func<IEnumerable<TResult>, IEnumerable<TResult>> predicate)
Expand Down Expand Up @@ -158,6 +220,10 @@ public static ICacheSpecificationBuilder<T> EnableCache<T>(
return cacheBuilder;
}

/// <summary>
/// If the entity instances are modified, this will not be detected
/// by the change tracker.
/// </summary>
public static ISpecificationBuilder<T> AsNoTracking<T>(
this ISpecificationBuilder<T> specificationBuilder) where T : class
{
Expand All @@ -166,6 +232,15 @@ public static ISpecificationBuilder<T> AsNoTracking<T>(
return specificationBuilder;
}

/// <summary>
/// The generated sql query will be split into multiple SQL queries
/// </summary>
/// <remarks>
/// This feature was introduced in EF Core 5.0. It only works when using Include
/// for more info: https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
public static ISpecificationBuilder<T> AsSplitQuery<T>(
this ISpecificationBuilder<T> specificationBuilder) where T : class
{
Expand All @@ -174,6 +249,16 @@ public static ISpecificationBuilder<T> AsSplitQuery<T>(
return specificationBuilder;
}

/// <summary>
/// The query will then keep track of returned instances
/// (without tracking them in the normal way)
/// and ensure no duplicates are created in the query results
/// </summary>
/// <remarks>
/// for more info: https://docs.microsoft.com/en-us/ef/core/change-tracking/identity-resolution#identity-resolution-and-queries
/// </remarks>
/// <typeparam name="T"></typeparam>
/// <param name="specificationBuilder"></param>
public static ISpecificationBuilder<T> AsNoTrackingWithIdentityResolution<T>(
this ISpecificationBuilder<T> specificationBuilder) where T : class
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Ardalis.Specification
namespace Ardalis.Specification
{
/// <summary>
/// A marker interface for specifications that are meant to return a single entity. Used to constrain methods
/// that accept a Specification and return a single result rather than a collection of results
/// </summary>
public interface ISingleResultSpecification
{
}
Expand Down

This file was deleted.

31 changes: 31 additions & 0 deletions Specification/src/Ardalis.Specification/ISpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public interface ISpecification<T, TResult> : ISpecification<T>
/// The transform function to apply to the <typeparamref name="T"/> element.
/// </summary>
Expression<Func<T, TResult>>? Selector { get; }

/// <summary>
/// The transform function to apply to the result of the query encapsulated by the <see cref="ISpecification{T, TResult}"/>.
/// </summary>
Expand All @@ -34,22 +35,26 @@ public interface ISpecification<T>
/// The collection of predicates to filter on.
/// </summary>
IEnumerable<Expression<Func<T, bool>>> WhereExpressions { get; }

/// <summary>
/// The collections of functions used to determine the sorting (and subsequent sorting),
/// to apply to the result of the query encapsulated by the <see cref="ISpecification{T}"/>.
/// <para>KeySelector, a function to extract a key from an element.</para>
/// <para>OrderType, whether to (subsequently) sort ascending or descending</para>
/// </summary>
IEnumerable<(Expression<Func<T, object>> KeySelector, OrderTypeEnum OrderType)> OrderExpressions { get; }

/// <summary>
/// The collection of <see cref="IncludeExpressionInfo"/>s describing each include expression.
/// This information is utilized to build Include/ThenInclude functions in the query.
/// </summary>
IEnumerable<IncludeExpressionInfo> IncludeExpressions { get; }

/// <summary>
/// The collection of navigation properties, as strings, to include in the query.
/// </summary>
IEnumerable<string> IncludeStrings { get; }

/// <summary>
/// The collection of 'SQL LIKE' operations, constructed by;
/// <list type="bullet">
Expand All @@ -64,10 +69,12 @@ public interface ISpecification<T>
/// The number of elements to return.
/// </summary>
int? Take { get; }

/// <summary>
/// The number of elements to skip before returning the remaining elements.
/// </summary>
int? Skip { get; }

[Obsolete]
bool IsPagingEnabled { get; }

Expand All @@ -80,6 +87,7 @@ public interface ISpecification<T>
/// Return whether or not the results should be cached.
/// </summary>
bool CacheEnabled { get; }

/// <summary>
/// The identifier to use to store and retrieve results from the cache.
/// </summary>
Expand All @@ -91,9 +99,32 @@ public interface ISpecification<T>
/// by the change tracker.
/// </summary>
bool AsNoTracking { get; }

/// <summary>
/// Returns whether or not the generated sql query should be split into multiple SQL queries
/// </summary>
/// <remarks>
/// This feature was introduced in EF Core 5.0. It only works when using Include
/// for more info: https://docs.microsoft.com/en-us/ef/core/querying/single-split-queries
/// </remarks>
bool AsSplitQuery { get; }

/// <summary>
/// Returns whether or not the query will then keep track of returned instances
/// (without tracking them in the normal way)
/// and ensure no duplicates are created in the query results
/// </summary>
/// <remarks>
/// for more info: https://docs.microsoft.com/en-us/ef/core/change-tracking/identity-resolution#identity-resolution-and-queries
/// </remarks>
bool AsNoTrackingWithIdentityResolution { get; }

/// <summary>
/// Applies the query defined within the specification to the given objects.
/// This is specially helpful when unit testing specification classes
/// </summary>
/// <param name="entities">the list of entities to which the specification will be applied</param>
/// <returns></returns>
IEnumerable<T> Evaluate(IEnumerable<T> entities);
}
}
8 changes: 5 additions & 3 deletions Specification/src/Ardalis.Specification/Specification.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

namespace Ardalis.Specification
{
Expand Down Expand Up @@ -49,6 +48,7 @@ protected Specification(IInMemorySpecificationEvaluator inMemorySpecificationEva
this.Query = new SpecificationBuilder<T>(this);
}

/// <inheritdoc/>
public virtual IEnumerable<T> Evaluate(IEnumerable<T> entities)
{
return Evaluator.Evaluate(entities, this);
Expand All @@ -70,7 +70,6 @@ public virtual IEnumerable<T> Evaluate(IEnumerable<T> entities)
public IEnumerable<(Expression<Func<T, string>> Selector, string SearchTerm, int SearchGroup)> SearchCriterias { get; } =
new List<(Expression<Func<T, string>> Selector, string SearchTerm, int SearchGroup)>();


/// <inheritdoc/>
public int? Take { get; internal set; } = null;

Expand All @@ -80,7 +79,6 @@ public virtual IEnumerable<T> Evaluate(IEnumerable<T> entities)
/// <inheritdoc/>
public bool IsPagingEnabled { get; internal set; } = false;


/// <inheritdoc/>
public Func<IEnumerable<T>, IEnumerable<T>>? PostProcessingAction { get; internal set; } = null;

Expand All @@ -92,7 +90,11 @@ public virtual IEnumerable<T> Evaluate(IEnumerable<T> entities)

/// <inheritdoc/>
public bool AsNoTracking { get; internal set; } = false;

/// <inheritdoc/>
public bool AsSplitQuery { get; internal set; } = false;

/// <inheritdoc/>
public bool AsNoTrackingWithIdentityResolution { get; internal set; } = false;
}
}