diff --git a/src/Ardalis.Specification/Builder/CacheSpecificationBuilder.cs b/src/Ardalis.Specification/Builder/CacheSpecificationBuilder.cs deleted file mode 100644 index c5691ecd..00000000 --- a/src/Ardalis.Specification/Builder/CacheSpecificationBuilder.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ardalis.Specification; - -public class CacheSpecificationBuilder : ICacheSpecificationBuilder where T : class -{ - public Specification Specification { get; } - public bool IsChainDiscarded { get; set; } - - public CacheSpecificationBuilder(Specification specification) - : this(specification, false) - { - } - - public CacheSpecificationBuilder(Specification specification, bool isChainDiscarded) - { - Specification = specification; - IsChainDiscarded = isChainDiscarded; - } -} diff --git a/src/Ardalis.Specification/Builder/ICacheSpecificationBuilder.cs b/src/Ardalis.Specification/Builder/ICacheSpecificationBuilder.cs deleted file mode 100644 index 460deac7..00000000 --- a/src/Ardalis.Specification/Builder/ICacheSpecificationBuilder.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ardalis.Specification; - -public interface ICacheSpecificationBuilder : ISpecificationBuilder where T : class -{ - bool IsChainDiscarded { get; set; } -} diff --git a/src/Ardalis.Specification/Builder/IIncludableSpecificationBuilder.cs b/src/Ardalis.Specification/Builder/IIncludableSpecificationBuilder.cs deleted file mode 100644 index 07857e19..00000000 --- a/src/Ardalis.Specification/Builder/IIncludableSpecificationBuilder.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ardalis.Specification; - -public interface IIncludableSpecificationBuilder : ISpecificationBuilder where T : class -{ - bool IsChainDiscarded { get; set; } -} diff --git a/src/Ardalis.Specification/Builder/IOrderedSpecificationBuilder.cs b/src/Ardalis.Specification/Builder/IOrderedSpecificationBuilder.cs deleted file mode 100644 index 0039dd79..00000000 --- a/src/Ardalis.Specification/Builder/IOrderedSpecificationBuilder.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Ardalis.Specification; - -public interface IOrderedSpecificationBuilder : ISpecificationBuilder -{ - bool IsChainDiscarded { get; set; } -} diff --git a/src/Ardalis.Specification/Builder/ISpecificationBuilder.cs b/src/Ardalis.Specification/Builder/ISpecificationBuilder.cs deleted file mode 100644 index dfeba22c..00000000 --- a/src/Ardalis.Specification/Builder/ISpecificationBuilder.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Ardalis.Specification; - -public interface ISpecificationBuilder : ISpecificationBuilder -{ - new Specification Specification { get; } -} - -public interface ISpecificationBuilder -{ - Specification Specification { get; } -} diff --git a/src/Ardalis.Specification/Builder/IncludableBuilderExtensions.cs b/src/Ardalis.Specification/Builder/IncludableBuilderExtensions.cs deleted file mode 100644 index d9d80b8e..00000000 --- a/src/Ardalis.Specification/Builder/IncludableBuilderExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Ardalis.Specification; - -public static class IncludableBuilderExtensions -{ - public static IIncludableSpecificationBuilder ThenInclude( - this IIncludableSpecificationBuilder previousBuilder, - Expression> thenIncludeExpression) - where TEntity : class - => ThenInclude(previousBuilder, thenIncludeExpression, true); - - public static IIncludableSpecificationBuilder ThenInclude( - this IIncludableSpecificationBuilder previousBuilder, - Expression> thenIncludeExpression, - bool condition) - where TEntity : class - { - if (condition && !previousBuilder.IsChainDiscarded) - { - var expr = new IncludeExpressionInfo(thenIncludeExpression, typeof(TEntity), typeof(TProperty), typeof(TPreviousProperty)); - previousBuilder.Specification.Add(expr); - } - - var includeBuilder = new IncludableSpecificationBuilder(previousBuilder.Specification, !condition || previousBuilder.IsChainDiscarded); - - return includeBuilder; - } - - public static IIncludableSpecificationBuilder ThenInclude( - this IIncludableSpecificationBuilder> previousBuilder, - Expression> thenIncludeExpression) - where TEntity : class - => ThenInclude(previousBuilder, thenIncludeExpression, true); - - public static IIncludableSpecificationBuilder ThenInclude( - this IIncludableSpecificationBuilder> previousBuilder, - Expression> thenIncludeExpression, - bool condition) - where TEntity : class - { - if (condition && !previousBuilder.IsChainDiscarded) - { - var expr = new IncludeExpressionInfo(thenIncludeExpression, typeof(TEntity), typeof(TProperty), typeof(IEnumerable)); - previousBuilder.Specification.Add(expr); - } - - var includeBuilder = new IncludableSpecificationBuilder(previousBuilder.Specification, !condition || previousBuilder.IsChainDiscarded); - - return includeBuilder; - } -} diff --git a/src/Ardalis.Specification/Builder/IncludableSpecificationBuilder.cs b/src/Ardalis.Specification/Builder/IncludableSpecificationBuilder.cs deleted file mode 100644 index f1686f50..00000000 --- a/src/Ardalis.Specification/Builder/IncludableSpecificationBuilder.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ardalis.Specification; - -public class IncludableSpecificationBuilder : IIncludableSpecificationBuilder where T : class -{ - public Specification Specification { get; } - public bool IsChainDiscarded { get; set; } - - public IncludableSpecificationBuilder(Specification specification) - : this(specification, false) - { - } - - public IncludableSpecificationBuilder(Specification specification, bool isChainDiscarded) - { - Specification = specification; - IsChainDiscarded = isChainDiscarded; - } -} diff --git a/src/Ardalis.Specification/Builder/OrderedBuilderExtensions.cs b/src/Ardalis.Specification/Builder/OrderedBuilderExtensions.cs deleted file mode 100644 index 69c8073e..00000000 --- a/src/Ardalis.Specification/Builder/OrderedBuilderExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace Ardalis.Specification; - -public static class OrderedBuilderExtensions -{ - public static IOrderedSpecificationBuilder ThenBy( - this IOrderedSpecificationBuilder orderedBuilder, - Expression> orderExpression) - => ThenBy(orderedBuilder, orderExpression, true); - - public static IOrderedSpecificationBuilder ThenBy( - this IOrderedSpecificationBuilder orderedBuilder, - Expression> orderExpression, - bool condition) - { - if (condition && !orderedBuilder.IsChainDiscarded) - { - var expr = new OrderExpressionInfo(orderExpression, OrderTypeEnum.ThenBy); - orderedBuilder.Specification.Add(expr); - } - else - { - orderedBuilder.IsChainDiscarded = true; - } - - return orderedBuilder; - } - - public static IOrderedSpecificationBuilder ThenByDescending( - this IOrderedSpecificationBuilder orderedBuilder, - Expression> orderExpression) - => ThenByDescending(orderedBuilder, orderExpression, true); - - public static IOrderedSpecificationBuilder ThenByDescending( - this IOrderedSpecificationBuilder orderedBuilder, - Expression> orderExpression, - bool condition) - { - if (condition && !orderedBuilder.IsChainDiscarded) - { - var expr = new OrderExpressionInfo(orderExpression, OrderTypeEnum.ThenByDescending); - orderedBuilder.Specification.Add(expr); - } - else - { - orderedBuilder.IsChainDiscarded = true; - } - - return orderedBuilder; - } -} diff --git a/src/Ardalis.Specification/Builder/OrderedSpecificationBuilder.cs b/src/Ardalis.Specification/Builder/OrderedSpecificationBuilder.cs deleted file mode 100644 index 7a319030..00000000 --- a/src/Ardalis.Specification/Builder/OrderedSpecificationBuilder.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Ardalis.Specification; - -public class OrderedSpecificationBuilder : IOrderedSpecificationBuilder -{ - public Specification Specification { get; } - public bool IsChainDiscarded { get; set; } - - public OrderedSpecificationBuilder(Specification specification) - : this(specification, false) - { - } - - public OrderedSpecificationBuilder(Specification specification, bool isChainDiscarded) - { - Specification = specification; - IsChainDiscarded = isChainDiscarded; - } -} diff --git a/src/Ardalis.Specification/Builder/SpecificationBuilder.cs b/src/Ardalis.Specification/Builder/SpecificationBuilder.cs deleted file mode 100644 index b23eed0b..00000000 --- a/src/Ardalis.Specification/Builder/SpecificationBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Ardalis.Specification; - -public class SpecificationBuilder : SpecificationBuilder, ISpecificationBuilder -{ - public new Specification Specification { get; } - - public SpecificationBuilder(Specification specification) - : base(specification) - { - Specification = specification; - } -} - -public class SpecificationBuilder : ISpecificationBuilder -{ - public Specification Specification { get; } - - public SpecificationBuilder(Specification specification) - { - Specification = specification; - } -} diff --git a/src/Ardalis.Specification/Builder/SpecificationBuilderExtensions.cs b/src/Ardalis.Specification/Builder/SpecificationBuilderExtensions.cs deleted file mode 100644 index 21f59751..00000000 --- a/src/Ardalis.Specification/Builder/SpecificationBuilderExtensions.cs +++ /dev/null @@ -1,534 +0,0 @@ -namespace Ardalis.Specification; - -public static class SpecificationBuilderExtensions -{ - /// - /// Specify a predicate that will be applied to the query - /// - /// - /// - /// - public static ISpecificationBuilder Where( - this ISpecificationBuilder specificationBuilder, - Expression> criteria) - => Where(specificationBuilder, criteria, true); - - /// - /// Specify a predicate that will be applied to the query - /// - /// - /// - /// - /// If false, the criteria won't be added. - public static ISpecificationBuilder Where( - this ISpecificationBuilder specificationBuilder, - Expression> criteria, - bool condition) - { - if (condition) - { - var expr = new WhereExpressionInfo(criteria); - specificationBuilder.Specification.Add(expr); - } - - return specificationBuilder; - } - - /// - /// Specify the query result will be ordered by in an ascending order - /// - /// - /// - /// - public static IOrderedSpecificationBuilder OrderBy( - this ISpecificationBuilder specificationBuilder, - Expression> orderExpression) - => OrderBy(specificationBuilder, orderExpression, true); - - /// - /// Specify the query result will be ordered by in an ascending order - /// - /// - /// - /// - /// If false, the expression won't be added. The whole Order chain will be discarded. - public static IOrderedSpecificationBuilder OrderBy( - this ISpecificationBuilder specificationBuilder, - Expression> orderExpression, - bool condition) - { - if (condition) - { - var expr = new OrderExpressionInfo(orderExpression, OrderTypeEnum.OrderBy); - specificationBuilder.Specification.Add(expr); - } - - var orderedSpecificationBuilder = new OrderedSpecificationBuilder(specificationBuilder.Specification, !condition); - - return orderedSpecificationBuilder; - } - - /// - /// Specify the query result will be ordered by in a descending order - /// - /// - /// - /// - public static IOrderedSpecificationBuilder OrderByDescending( - this ISpecificationBuilder specificationBuilder, - Expression> orderExpression) - => OrderByDescending(specificationBuilder, orderExpression, true); - - /// - /// Specify the query result will be ordered by in a descending order - /// - /// - /// - /// - /// If false, the expression won't be added. The whole Order chain will be discarded. - public static IOrderedSpecificationBuilder OrderByDescending( - this ISpecificationBuilder specificationBuilder, - Expression> orderExpression, - bool condition) - { - if (condition) - { - var expr = new OrderExpressionInfo(orderExpression, OrderTypeEnum.OrderByDescending); - specificationBuilder.Specification.Add(expr); - } - - var orderedSpecificationBuilder = new OrderedSpecificationBuilder(specificationBuilder.Specification, !condition); - - return orderedSpecificationBuilder; - } - - /// - /// 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. - /// - /// - /// - /// - /// - public static IIncludableSpecificationBuilder Include( - this ISpecificationBuilder specificationBuilder, - Expression> includeExpression) where T : class - => Include(specificationBuilder, includeExpression, true); - - /// - /// 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. - /// - /// - /// - /// - /// - /// If false, the expression won't be added. The whole Include chain will be discarded. - public static IIncludableSpecificationBuilder Include( - this ISpecificationBuilder specificationBuilder, - Expression> includeExpression, - bool condition) where T : class - { - if (condition) - { - var expr = new IncludeExpressionInfo(includeExpression, typeof(T), typeof(TProperty)); - specificationBuilder.Specification.Add(expr); - } - - var includeBuilder = new IncludableSpecificationBuilder(specificationBuilder.Specification, !condition); - - return includeBuilder; - } - - /// - /// Specify a collection of navigation properties, as strings, to include in the query. - /// - /// - /// - /// - public static ISpecificationBuilder Include( - this ISpecificationBuilder specificationBuilder, - string includeString) where T : class - => Include(specificationBuilder, includeString, true); - - /// - /// Specify a collection of navigation properties, as strings, to include in the query. - /// - /// - /// - /// - /// If false, the include expression won't be added. - public static ISpecificationBuilder Include( - this ISpecificationBuilder specificationBuilder, - string includeString, - bool condition) where T : class - { - if (condition) - { - specificationBuilder.Specification.Add(includeString); - } - - return specificationBuilder; - } - - /// - /// Specify a 'SQL LIKE' operations for search purposes - /// - /// - /// - /// the property to apply the SQL LIKE against - /// the value to use for the SQL LIKE - /// the index used to group sets of Selectors and SearchTerms together - public static ISpecificationBuilder Search( - this ISpecificationBuilder specificationBuilder, - Expression> selector, - string searchTerm, - int searchGroup = 1) where T : class - => Search(specificationBuilder, selector, searchTerm, true, searchGroup); - - /// - /// Specify a 'SQL LIKE' operations for search purposes - /// - /// - /// - /// the property to apply the SQL LIKE against - /// the value to use for the SQL LIKE - /// If false, the expression won't be added. - /// the index used to group sets of Selectors and SearchTerms together - public static ISpecificationBuilder Search( - this ISpecificationBuilder specificationBuilder, - Expression> selector, - string searchTerm, - bool condition, - int searchGroup = 1) where T : class - { - if (condition) - { - var expr = new SearchExpressionInfo(selector, searchTerm, searchGroup); - specificationBuilder.Specification.Add(expr); - } - - return specificationBuilder; - } - - /// - /// Specify the number of elements to return. - /// - /// - /// number of elements to take - public static ISpecificationBuilder Take( - this ISpecificationBuilder specificationBuilder, - int take) - => Take(specificationBuilder, take, true); - - /// - /// Specify the number of elements to return. - /// - /// - /// number of elements to take - /// If false, the value will be discarded. - public static ISpecificationBuilder Take( - this ISpecificationBuilder specificationBuilder, - int take, - bool condition) - { - if (condition) - { - if (specificationBuilder.Specification.Take != -1) throw new DuplicateTakeException(); - - specificationBuilder.Specification.Take = take; - } - - return specificationBuilder; - } - - /// - /// Specify the number of elements to skip before returning the remaining elements. - /// - /// - /// - /// number of elements to skip - public static ISpecificationBuilder Skip( - this ISpecificationBuilder specificationBuilder, - int skip) - => Skip(specificationBuilder, skip, true); - - /// - /// Specify the number of elements to skip before returning the remaining elements. - /// - /// - /// - /// number of elements to skip - /// If false, the value will be discarded. - public static ISpecificationBuilder Skip( - this ISpecificationBuilder specificationBuilder, - int skip, - bool condition) - { - if (condition) - { - if (specificationBuilder.Specification.Skip != -1) throw new DuplicateSkipException(); - - specificationBuilder.Specification.Skip = skip; - } - - return specificationBuilder; - } - - /// - /// Specify a transform function to apply to the element - /// to produce another element. - /// - public static ISpecificationBuilder Select( - this ISpecificationBuilder specificationBuilder, - Expression> selector) - { - specificationBuilder.Specification.Selector = selector; - - return specificationBuilder; - } - - /// - /// Specify a transform function to apply to the element - /// to produce a flattened sequence of elements. - /// - public static ISpecificationBuilder SelectMany( - this ISpecificationBuilder specificationBuilder, - Expression>> selector) - { - specificationBuilder.Specification.SelectorMany = selector; - - return specificationBuilder; - } - - /// - /// Specify a transform function to apply to the result of the query - /// and returns the same type - /// - public static ISpecificationBuilder PostProcessingAction( - this ISpecificationBuilder specificationBuilder, - Func, IEnumerable> predicate) - { - specificationBuilder.Specification.PostProcessingAction = predicate; - - return specificationBuilder; - } - - /// - /// Specify a transform function to apply to the result of the query. - /// and returns another type - /// - public static ISpecificationBuilder PostProcessingAction( - this ISpecificationBuilder specificationBuilder, - Func, IEnumerable> predicate) - { - specificationBuilder.Specification.PostProcessingAction = predicate; - - return specificationBuilder; - } - - /// - /// Must be called after specifying criteria - /// - /// - /// Any arguments used in defining the specification - public static ICacheSpecificationBuilder EnableCache( - this ISpecificationBuilder specificationBuilder, - string specificationName, - params object[] args) where T : class - => EnableCache(specificationBuilder, specificationName, true, args); - - /// - /// Must be called after specifying criteria - /// - /// - /// Any arguments used in defining the specification - /// If false, the caching won't be enabled. - public static ICacheSpecificationBuilder EnableCache( - this ISpecificationBuilder specificationBuilder, - string specificationName, - bool condition, - params object[] args) where T : class - { - if (condition) - { - if (string.IsNullOrEmpty(specificationName)) - { - throw new ArgumentException($"Required input {specificationName} was null or empty.", specificationName); - } - - specificationBuilder.Specification.CacheKey = $"{specificationName}-{string.Join("-", args)}"; - } - - var cacheBuilder = new CacheSpecificationBuilder(specificationBuilder.Specification, !condition); - - return cacheBuilder; - } - - /// - /// If the entity instances are modified, this will be detected - /// by the change tracker. - /// - /// - public static ISpecificationBuilder AsTracking( - this ISpecificationBuilder specificationBuilder) where T : class - => AsTracking(specificationBuilder, true); - - /// - /// If the entity instances are modified, this will be detected - /// by the change tracker. - /// - /// - /// If false, the setting will be discarded. - public static ISpecificationBuilder AsTracking( - this ISpecificationBuilder specificationBuilder, - bool condition) where T : class - { - if (condition) - { - specificationBuilder.Specification.AsNoTracking = false; - specificationBuilder.Specification.AsNoTrackingWithIdentityResolution = false; - specificationBuilder.Specification.AsTracking = true; - } - - return specificationBuilder; - } - - /// - /// If the entity instances are modified, this will not be detected - /// by the change tracker. - /// - /// - public static ISpecificationBuilder AsNoTracking( - this ISpecificationBuilder specificationBuilder) where T : class - => AsNoTracking(specificationBuilder, true); - - /// - /// If the entity instances are modified, this will not be detected - /// by the change tracker. - /// - /// - /// If false, the setting will be discarded. - public static ISpecificationBuilder AsNoTracking( - this ISpecificationBuilder specificationBuilder, - bool condition) where T : class - { - if (condition) - { - specificationBuilder.Specification.AsTracking = false; - specificationBuilder.Specification.AsNoTrackingWithIdentityResolution = false; - specificationBuilder.Specification.AsNoTracking = true; - } - - return specificationBuilder; - } - - /// - /// The generated sql query will be split into multiple SQL queries - /// - /// - /// 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 - /// - /// - /// - public static ISpecificationBuilder AsSplitQuery( - this ISpecificationBuilder specificationBuilder) where T : class - => AsSplitQuery(specificationBuilder, true); - - /// - /// The generated sql query will be split into multiple SQL queries - /// - /// - /// 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 - /// - /// - /// - /// If false, the setting will be discarded. - public static ISpecificationBuilder AsSplitQuery( - this ISpecificationBuilder specificationBuilder, - bool condition) where T : class - { - if (condition) - { - specificationBuilder.Specification.AsSplitQuery = true; - } - - return specificationBuilder; - } - - /// - /// 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 - /// - /// - /// for more info: https://docs.microsoft.com/en-us/ef/core/change-tracking/identity-resolution#identity-resolution-and-queries - /// - /// - /// - public static ISpecificationBuilder AsNoTrackingWithIdentityResolution( - this ISpecificationBuilder specificationBuilder) where T : class - => AsNoTrackingWithIdentityResolution(specificationBuilder, true); - - /// - /// 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 - /// - /// - /// for more info: https://docs.microsoft.com/en-us/ef/core/change-tracking/identity-resolution#identity-resolution-and-queries - /// - /// - /// - /// If false, the setting will be discarded. - public static ISpecificationBuilder AsNoTrackingWithIdentityResolution( - this ISpecificationBuilder specificationBuilder, - bool condition) where T : class - { - if (condition) - { - specificationBuilder.Specification.AsTracking = false; - specificationBuilder.Specification.AsNoTracking = false; - specificationBuilder.Specification.AsNoTrackingWithIdentityResolution = true; - } - - return specificationBuilder; - } - - /// - /// The query will ignore the defined global query filters - /// - /// - /// for more info: https://docs.microsoft.com/en-us/ef/core/querying/filters - /// - /// - /// - public static ISpecificationBuilder IgnoreQueryFilters( - this ISpecificationBuilder specificationBuilder) where T : class - => IgnoreQueryFilters(specificationBuilder, true); - - /// - /// The query will ignore the defined global query filters - /// - /// - /// for more info: https://docs.microsoft.com/en-us/ef/core/querying/filters - /// - /// - /// - /// If false, the setting will be discarded. - public static ISpecificationBuilder IgnoreQueryFilters( - this ISpecificationBuilder specificationBuilder, - bool condition) where T : class - { - if (condition) - { - specificationBuilder.Specification.IgnoreQueryFilters = true; - } - - return specificationBuilder; - } -} diff --git a/src/Ardalis.Specification/Builders/Builder_Cache.cs b/src/Ardalis.Specification/Builders/Builder_Cache.cs new file mode 100644 index 00000000..d1b098c8 --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Cache.cs @@ -0,0 +1,96 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Set's the cache key for the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// Used as prefix for the cache key + /// To be appended to the cache key, separated by dash. + /// The updated ordered specification builder. + /// If specificationName is null or empty. + public static ICacheSpecificationBuilder EnableCache( + this ISpecificationBuilder builder, + string specificationName, + params object[] args) where T : class + => EnableCache(builder, specificationName, true, args); + + /// + /// Set's the cache key for the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// Used as prefix for the cache key + /// The condition to evaluate. + /// To be appended to the cache key, separated by dash. + /// The updated ordered specification builder. + /// If specificationName is null or empty. + public static ICacheSpecificationBuilder EnableCache( + this ISpecificationBuilder builder, + string specificationName, + bool condition, + params object[] args) where T : class + { + if (condition) + { + if (string.IsNullOrEmpty(specificationName)) + { + throw new ArgumentException($"Required input {specificationName} was null or empty.", specificationName); + } + + builder.Specification.CacheKey = $"{specificationName}-{string.Join("-", args)}"; + } + + Specification.IsChainDiscarded = !condition; + return (SpecificationBuilder)builder; + } + + /// + /// Set's the cache key for the specification. + /// + /// The type of the entity. + /// The specification builder. + /// Used as prefix for the cache key + /// To be appended to the cache key, separated by dash. + /// The updated ordered specification builder. + /// If specificationName is null or empty. + public static ICacheSpecificationBuilder EnableCache( + this ISpecificationBuilder builder, + string specificationName, + params object[] args) where T : class + => EnableCache(builder, specificationName, true, args); + + /// + /// Set's the cache key for the specification. + /// + /// The type of the entity. + /// The specification builder. + /// Used as prefix for the cache key + /// The condition to evaluate. + /// To be appended to the cache key, separated by dash. + /// The updated ordered specification builder. + /// If specificationName is null or empty. + public static ICacheSpecificationBuilder EnableCache( + this ISpecificationBuilder builder, + string specificationName, + bool condition, + params object[] args) where T : class + { + if (condition) + { + if (string.IsNullOrEmpty(specificationName)) + { + throw new ArgumentException($"Required input {specificationName} was null or empty.", specificationName); + } + + builder.Specification.CacheKey = $"{specificationName}-{string.Join("-", args)}"; + } + + Specification.IsChainDiscarded = !condition; + return (SpecificationBuilder)builder; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_Flags.cs b/src/Ardalis.Specification/Builders/Builder_Flags.cs new file mode 100644 index 00000000..62a84228 --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Flags.cs @@ -0,0 +1,328 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Configures the specification to ignore query filters. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreQueryFilters( + this ISpecificationBuilder builder) where T : class + => IgnoreQueryFilters(builder, true); + + /// + /// Configures the specification to ignore query filters if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreQueryFilters( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.IgnoreQueryFilters = true; + } + + return builder; + } + + /// + /// Configures the specification to ignore query filters. + /// + /// The type of the entity. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreQueryFilters( + this ISpecificationBuilder builder) where T : class + => IgnoreQueryFilters(builder, true); + + /// + /// Configures the specification to ignore query filters if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder IgnoreQueryFilters( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.IgnoreQueryFilters = true; + } + + return builder; + } + + /// + /// Configures the specification to use split queries. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsSplitQuery( + this ISpecificationBuilder builder) where T : class + => AsSplitQuery(builder, true); + + /// + /// Configures the specification to use split queries if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsSplitQuery( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsSplitQuery = true; + } + + return builder; + } + + /// + /// Configures the specification to use split queries. + /// + /// The type of the entity. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsSplitQuery( + this ISpecificationBuilder builder) where T : class + => AsSplitQuery(builder, true); + + /// + /// Configures the specification to use split queries if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsSplitQuery( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsSplitQuery = true; + } + + return builder; + } + + /// + /// Configures the specification to apply NoTracking behavior. + /// It will also disable AsNoTrackingWithIdentityResolution and AsTracking flags. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTracking( + this ISpecificationBuilder builder) where T : class + => AsNoTracking(builder, true); + + /// + /// Configures the specification to apply NoTracking behavior if the condition is true. + /// It will also disable AsNoTrackingWithIdentityResolution and AsTracking flags. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTracking( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsTracking = false; + builder.Specification.AsNoTrackingWithIdentityResolution = false; + builder.Specification.AsNoTracking = true; + } + + return builder; + } + + /// + /// Configures the specification to apply NoTracking behavior. + /// It will also disable AsNoTrackingWithIdentityResolution and AsTracking flags. + /// + /// The type of the entity. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTracking( + this ISpecificationBuilder builder) where T : class + => AsNoTracking(builder, true); + + /// + /// Configures the specification to apply NoTracking behavior if the condition is true. + /// It will also disable AsNoTrackingWithIdentityResolution and AsTracking flags. + /// + /// The type of the entity. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTracking( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsTracking = false; + builder.Specification.AsNoTrackingWithIdentityResolution = false; + builder.Specification.AsNoTracking = true; + } + + return builder; + } + + /// + /// Configures the specification to apply AsNoTrackingWithIdentityResolution behavior. + /// It will also disable AsNoTracking and AsTracking flags. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTrackingWithIdentityResolution( + this ISpecificationBuilder builder) where T : class + => AsNoTrackingWithIdentityResolution(builder, true); + + /// + /// Configures the specification to apply AsNoTrackingWithIdentityResolution behavior if the condition is true. + /// It will also disable AsNoTracking and AsTracking flags. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTrackingWithIdentityResolution( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsTracking = false; + builder.Specification.AsNoTracking = false; + builder.Specification.AsNoTrackingWithIdentityResolution = true; + } + + return builder; + } + + /// + /// Configures the specification to apply AsNoTrackingWithIdentityResolution behavior. + /// It will also disable AsNoTracking and AsTracking flags. + /// + /// The type of the entity. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTrackingWithIdentityResolution( + this ISpecificationBuilder builder) where T : class + => AsNoTrackingWithIdentityResolution(builder, true); + + /// + /// Configures the specification to apply AsNoTrackingWithIdentityResolution behavior if the condition is true. + /// It will also disable AsNoTracking and AsTracking flags. + /// + /// The type of the entity. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsNoTrackingWithIdentityResolution( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsTracking = false; + builder.Specification.AsNoTracking = false; + builder.Specification.AsNoTrackingWithIdentityResolution = true; + } + + return builder; + } + + /// + /// Configures the specification to apply AsTracking behavior. + /// It will also disable AsNoTrackingWithIdentityResolution and AsNoTracking flags. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsTracking( + this ISpecificationBuilder builder) where T : class + => AsTracking(builder, true); + + /// + /// Configures the specification to apply AsTracking behavior if the condition is true. + /// It will also disable AsNoTrackingWithIdentityResolution and AsNoTracking flags. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsTracking( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsNoTracking = false; + builder.Specification.AsNoTrackingWithIdentityResolution = false; + builder.Specification.AsTracking = true; + } + + return builder; + } + + /// + /// Configures the specification to apply AsTracking behavior. + /// It will also disable AsNoTrackingWithIdentityResolution and AsNoTracking flags. + /// + /// The type of the entity. + /// The specification builder. + /// The updated specification builder. + public static ISpecificationBuilder AsTracking( + this ISpecificationBuilder builder) where T : class + => AsTracking(builder, true); + + /// + /// Configures the specification to apply AsTracking behavior if the condition is true. + /// It will also disable AsNoTrackingWithIdentityResolution and AsNoTracking flags. + /// + /// The type of the entity. + /// The specification builder. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder AsTracking( + this ISpecificationBuilder builder, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AsNoTracking = false; + builder.Specification.AsNoTrackingWithIdentityResolution = false; + builder.Specification.AsTracking = true; + } + + return builder; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_Include.cs b/src/Ardalis.Specification/Builders/Builder_Include.cs new file mode 100644 index 00000000..945d920f --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Include.cs @@ -0,0 +1,334 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Adds an Include clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The include string. + /// The updated specification builder. + public static ISpecificationBuilder Include( + this ISpecificationBuilder builder, + string includeString) where T : class + => Include(builder, includeString, true); + + /// + /// Adds an Include clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The include string. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder Include( + this ISpecificationBuilder builder, + string includeString, + bool condition) where T : class + { + if (condition) + { + builder.Specification.Add(includeString); + } + + return builder; + } + + /// + /// Adds an Include clause to the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The include string. + /// The updated specification builder. + public static ISpecificationBuilder Include( + this ISpecificationBuilder builder, + string includeString) where T : class + => Include(builder, includeString, true); + + /// + /// Adds an Include clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The include string. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder Include( + this ISpecificationBuilder builder, + string includeString, + bool condition) where T : class + { + if (condition) + { + builder.Specification.Add(includeString); + } + + return builder; + } + + /// + /// Adds an Include clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The type of the property. + /// The specification builder. + /// The include expression. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder Include( + this ISpecificationBuilder builder, + Expression> navigationSelector) where T : class + => Include(builder, navigationSelector, true); + + /// + /// Adds an Include clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The type of the property. + /// The specification builder. + /// The include expression. + /// The condition to evaluate. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder Include( + this ISpecificationBuilder builder, + Expression> navigationSelector, + bool condition) where T : class + { + if (condition) + { + var expr = new IncludeExpressionInfo(navigationSelector, typeof(T), typeof(TProperty)); + builder.Specification.Add(expr); + } + + Specification.IsChainDiscarded = !condition; + var includeBuilder = new IncludableSpecificationBuilder(builder.Specification); + return includeBuilder; + } + + /// + /// Adds an Include clause to the specification. + /// + /// The type of the entity. + /// The type of the property. + /// The specification builder. + /// The include expression. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder Include( + this ISpecificationBuilder builder, + Expression> navigationSelector) where T : class + => Include(builder, navigationSelector, true); + + /// + /// Adds an Include clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the property. + /// The specification builder. + /// The include expression. + /// The condition to evaluate. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder Include( + this ISpecificationBuilder builder, + Expression> navigationSelector, + bool condition) where T : class + { + if (condition) + { + var expr = new IncludeExpressionInfo(navigationSelector, typeof(T), typeof(TProperty)); + builder.Specification.Add(expr); + } + + Specification.IsChainDiscarded = !condition; + var includeBuilder = new IncludableSpecificationBuilder(builder.Specification); + return includeBuilder; + } + + /// + /// Adds a ThenInclude clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder builder, + Expression> navigationSelector) + where TEntity : class + => ThenInclude(builder, navigationSelector, true); + + /// + /// Adds a ThenInclude clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The condition to evaluate. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder builder, + Expression> navigationSelector, + bool condition) + where TEntity : class + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new IncludeExpressionInfo(navigationSelector, typeof(TEntity), typeof(TProperty), typeof(TPreviousProperty)); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + var includeBuilder = new IncludableSpecificationBuilder(builder.Specification); + return includeBuilder; + } + + /// + /// Adds a ThenInclude clause to the specification. + /// + /// The type of the entity. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder builder, + Expression> navigationSelector) + where TEntity : class + => ThenInclude(builder, navigationSelector, true); + + /// + /// Adds a ThenInclude clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The condition to evaluate. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder builder, + Expression> navigationSelector, + bool condition) + where TEntity : class + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new IncludeExpressionInfo(navigationSelector, typeof(TEntity), typeof(TProperty), typeof(TPreviousProperty)); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + var includeBuilder = new IncludableSpecificationBuilder(builder.Specification); + return includeBuilder; + } + + /// + /// Adds a ThenInclude clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder> builder, + Expression> navigationSelector) + where TEntity : class + => ThenInclude(builder, navigationSelector, true); + + /// + /// Adds a ThenInclude clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The condition to evaluate. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder> builder, + Expression> navigationSelector, + bool condition) + where TEntity : class + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new IncludeExpressionInfo(navigationSelector, typeof(TEntity), typeof(TProperty), typeof(IEnumerable)); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + var includeBuilder = new IncludableSpecificationBuilder(builder.Specification); + return includeBuilder; + } + + /// + /// Adds a ThenInclude clause to the specification. + /// + /// The type of the entity. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder> builder, + Expression> navigationSelector) + where TEntity : class + => ThenInclude(builder, navigationSelector, true); + + /// + /// Adds a ThenInclude clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the previous property. + /// The type of the property. + /// The previous includable specification builder. + /// The include expression. + /// The condition to evaluate. + /// The updated includable specification builder. + public static IIncludableSpecificationBuilder ThenInclude( + this IIncludableSpecificationBuilder> builder, + Expression> navigationSelector, + bool condition) + where TEntity : class + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new IncludeExpressionInfo(navigationSelector, typeof(TEntity), typeof(TProperty), typeof(IEnumerable)); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + var includeBuilder = new IncludableSpecificationBuilder(builder.Specification); + return includeBuilder; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_Order.cs b/src/Ardalis.Specification/Builders/Builder_Order.cs new file mode 100644 index 00000000..20714370 --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Order.cs @@ -0,0 +1,304 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Adds an OrderBy clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderBy( + this ISpecificationBuilder builder, + Expression> keySelector) + => OrderBy(builder, keySelector, true); + + /// + /// Adds an OrderBy clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderBy( + this ISpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.OrderBy); + builder.Specification.Add(expr); + } + + Specification.IsChainDiscarded = !condition; + return (SpecificationBuilder)builder; + } + + /// + /// Adds an OrderBy clause to the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderBy( + this ISpecificationBuilder builder, + Expression> keySelector) + => OrderBy(builder, keySelector, true); + + /// + /// Adds an OrderBy clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderBy( + this ISpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.OrderBy); + builder.Specification.Add(expr); + } + + Specification.IsChainDiscarded = !condition; + return (SpecificationBuilder)builder; + } + + /// + /// Adds an OrderBy descending clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderByDescending( + this ISpecificationBuilder builder, + Expression> keySelector) + => OrderByDescending(builder, keySelector, true); + + /// + /// Adds an OrderByDescending clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderByDescending( + this ISpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.OrderByDescending); + builder.Specification.Add(expr); + } + + Specification.IsChainDiscarded = !condition; + return (SpecificationBuilder)builder; + } + + /// + /// Adds an OrderByDescending clause to the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderByDescending( + this ISpecificationBuilder builder, + Expression> keySelector) + => OrderByDescending(builder, keySelector, true); + + /// + /// Adds an OrderByDescending clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder OrderByDescending( + this ISpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.OrderByDescending); + builder.Specification.Add(expr); + } + + Specification.IsChainDiscarded = !condition; + return (SpecificationBuilder)builder; + } + + /// + /// Adds a ThenBy clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The ordered specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenBy( + this IOrderedSpecificationBuilder builder, + Expression> keySelector) + => ThenBy(builder, keySelector, true); + + /// + /// Adds a ThenBy clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The ordered specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenBy( + this IOrderedSpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.ThenBy); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + return builder; + } + + /// + /// Adds a ThenBy clause to the specification. + /// + /// The type of the entity. + /// The ordered specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenBy( + this IOrderedSpecificationBuilder builder, + Expression> keySelector) + => ThenBy(builder, keySelector, true); + + /// + /// Adds a ThenBy clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The ordered specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenBy( + this IOrderedSpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.ThenBy); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + return builder; + } + + /// + /// Adds a ThenByDescending clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The ordered specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenByDescending( + this IOrderedSpecificationBuilder builder, + Expression> keySelector) + => ThenByDescending(builder, keySelector, true); + + /// + /// Adds a ThenByDescending clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The ordered specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenByDescending( + this IOrderedSpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.ThenByDescending); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + return builder; + } + + /// + /// Adds a ThenByDescending clause to the specification. + /// + /// The type of the entity. + /// The ordered specification builder. + /// The key selector expression. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenByDescending( + this IOrderedSpecificationBuilder builder, + Expression> keySelector) + => ThenByDescending(builder, keySelector, true); + + /// + /// Adds a ThenByDescending clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The ordered specification builder. + /// The key selector expression. + /// The condition to evaluate. + /// The updated ordered specification builder. + public static IOrderedSpecificationBuilder ThenByDescending( + this IOrderedSpecificationBuilder builder, + Expression> keySelector, + bool condition) + { + if (condition && !Specification.IsChainDiscarded) + { + var expr = new OrderExpressionInfo(keySelector, OrderTypeEnum.ThenByDescending); + builder.Specification.Add(expr); + } + else + { + Specification.IsChainDiscarded = true; + } + + return builder; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_Paging.cs b/src/Ardalis.Specification/Builders/Builder_Paging.cs new file mode 100644 index 00000000..8a3b431a --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Paging.cs @@ -0,0 +1,152 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Sets the number of items to take in the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The number of items to take. + /// The updated specification builder. + /// If take value is already set. + public static ISpecificationBuilder Take( + this ISpecificationBuilder builder, + int take) + => Take(builder, take, true); + + /// + /// Sets the number of items to take in the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The number of items to take. + /// The condition to evaluate. + /// The updated specification builder. + /// If take value is already set. + public static ISpecificationBuilder Take( + this ISpecificationBuilder builder, + int take, + bool condition) + { + if (condition) + { + if (builder.Specification.Take != -1) throw new DuplicateTakeException(); + builder.Specification.Take = take; + } + + return builder; + } + + /// + /// Sets the number of items to take in the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The number of items to take. + /// The updated specification builder. + /// If take value is already set. + public static ISpecificationBuilder Take( + this ISpecificationBuilder builder, + int take) + => Take(builder, take, true); + + /// + /// Sets the number of items to take in the specification if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The number of items to take. + /// The condition to evaluate. + /// The updated specification builder. + /// If take value is already set. + public static ISpecificationBuilder Take( + this ISpecificationBuilder builder, + int take, + bool condition) + { + if (condition) + { + if (builder.Specification.Take != -1) throw new DuplicateTakeException(); + builder.Specification.Take = take; + } + + return builder; + } + + /// + /// Sets the number of items to skip in the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The number of items to skip. + /// The updated specification builder. + /// If the skip value is already set. + public static ISpecificationBuilder Skip( + this ISpecificationBuilder builder, + int skip) + => Skip(builder, skip, true); + + /// + /// Sets the number of items to skip in the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The number of items to skip. + /// The condition to evaluate. + /// The updated specification builder. + /// If the skip value is already set. + public static ISpecificationBuilder Skip( + this ISpecificationBuilder builder, + int skip, + bool condition) + { + if (condition) + { + if (builder.Specification.Skip != -1) throw new DuplicateSkipException(); + builder.Specification.Skip = skip; + } + + return builder; + } + + /// + /// Sets the number of items to skip in the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The number of items to skip. + /// The updated specification builder. + /// If the skip value is already set. + public static ISpecificationBuilder Skip( + this ISpecificationBuilder builder, + int skip) + => Skip(builder, skip, true); + + /// + /// Sets the number of items to skip in the specification if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The number of items to skip. + /// The condition to evaluate. + /// The updated specification builder. + /// If the skip value is already set. + public static ISpecificationBuilder Skip( + this ISpecificationBuilder builder, + int skip, + bool condition) + { + if (condition) + { + if (builder.Specification.Skip != -1) throw new DuplicateSkipException(); + builder.Specification.Skip = skip; + } + + return builder; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_PostProcessing.cs b/src/Ardalis.Specification/Builders/Builder_PostProcessing.cs new file mode 100644 index 00000000..df4fcad8 --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_PostProcessing.cs @@ -0,0 +1,39 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + + /// + /// Specify a function to apply to the result of the query. It's an in-memory operation. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The filter function. + /// Filtered results + public static ISpecificationBuilder PostProcessingAction( + this ISpecificationBuilder builder, + Func, IEnumerable> filter) + { + builder.Specification.PostProcessingAction = filter; + + return builder; + } + + + /// + /// Specify a function to apply to the result of the query. It's an in-memory operation. + /// + /// The type of the entity. + /// The specification builder. + /// The filter function. + /// Filtered results + public static ISpecificationBuilder PostProcessingAction( + this ISpecificationBuilder builder, + Func, IEnumerable> filter) + { + builder.Specification.PostProcessingAction = filter; + + return builder; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_Search.cs b/src/Ardalis.Specification/Builders/Builder_Search.cs new file mode 100644 index 00000000..67e4bb1a --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Search.cs @@ -0,0 +1,92 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + // TODO: Improve the nullability for the key selector. Update everything to work with Func. [Fati Iseni, 27/02/2025] + + /// + /// Adds a Like clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The key selector expression. + /// The pattern to match. + /// The group number. Like clauses within the same group are evaluated using OR logic. + /// The updated specification builder. + public static ISpecificationBuilder Search( + this ISpecificationBuilder builder, + Expression> keySelector, + string pattern, + int group = 1) where T : class + => Search(builder, keySelector, pattern, true, group); + + /// + /// Adds a Like clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The key selector expression. + /// The pattern to match. + /// The condition to evaluate. + /// The group number. Like clauses within the same group are evaluated using OR logic. + /// The updated specification builder. + public static ISpecificationBuilder Search( + this ISpecificationBuilder builder, + Expression> keySelector, + string pattern, + bool condition, + int group = 1) where T : class + { + if (condition) + { + var expr = new SearchExpressionInfo(keySelector, pattern, group); + builder.Specification.Add(expr); + } + + return builder; + } + + /// + /// Adds a Like clause to the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The key selector expression. + /// The pattern to match. + /// The group number. Like clauses within the same group are evaluated using OR logic. + /// The updated specification builder. + public static ISpecificationBuilder Search( + this ISpecificationBuilder builder, + Expression> keySelector, + string pattern, + int group = 1) where T : class + => Search(builder, keySelector, pattern, true, group); + + /// + /// Adds a Like clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The key selector expression. + /// The pattern to match. + /// The condition to evaluate. + /// The group number. Like clauses within the same group are evaluated using OR logic. + /// The updated specification builder. + public static ISpecificationBuilder Search( + this ISpecificationBuilder builder, + Expression> keySelector, + string pattern, + bool condition, + int group = 1) where T : class + { + if (condition) + { + var expr = new SearchExpressionInfo(keySelector, pattern, group); + builder.Specification.Add(expr); + } + + return builder; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_Select.cs b/src/Ardalis.Specification/Builders/Builder_Select.cs new file mode 100644 index 00000000..8f291479 --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Select.cs @@ -0,0 +1,32 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Adds a Select clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The selector expression. + public static void Select( + this ISpecificationBuilder builder, + Expression> selector) + { + builder.Specification.Selector = selector; + } + + /// + /// Adds a SelectMany clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The selector expression. + public static void SelectMany( + this ISpecificationBuilder builder, + Expression>> selector) + { + builder.Specification.SelectorMany = selector; + } +} diff --git a/src/Ardalis.Specification/Builders/Builder_Where.cs b/src/Ardalis.Specification/Builders/Builder_Where.cs new file mode 100644 index 00000000..49800b86 --- /dev/null +++ b/src/Ardalis.Specification/Builders/Builder_Where.cs @@ -0,0 +1,74 @@ +namespace Ardalis.Specification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Adds a Where clause to the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The predicate expression. + /// The updated specification builder. + public static ISpecificationBuilder Where( + this ISpecificationBuilder builder, + Expression> predicate) + => Where(builder, predicate, true); + + /// + /// Adds a Where clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The predicate expression. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder Where( + this ISpecificationBuilder builder, + Expression> predicate, + bool condition) + { + if (condition) + { + var expr = new WhereExpressionInfo(predicate); + builder.Specification.Add(expr); + } + + return builder; + } + + /// + /// Adds a Where clause to the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The predicate expression. + /// The updated specification builder. + public static ISpecificationBuilder Where( + this ISpecificationBuilder builder, + Expression> predicate) + => Where(builder, predicate, true); + + /// + /// Adds a Where clause to the specification if the condition is true. + /// + /// The type of the entity. + /// The specification builder. + /// The predicate expression. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder Where( + this ISpecificationBuilder builder, + Expression> predicate, + bool condition) + { + if (condition) + { + var expr = new WhereExpressionInfo(predicate); + builder.Specification.Add(expr); + } + + return builder; + } +} diff --git a/src/Ardalis.Specification/Builders/IncludableSpecificationBuilder.cs b/src/Ardalis.Specification/Builders/IncludableSpecificationBuilder.cs new file mode 100644 index 00000000..4c84b981 --- /dev/null +++ b/src/Ardalis.Specification/Builders/IncludableSpecificationBuilder.cs @@ -0,0 +1,25 @@ +namespace Ardalis.Specification; + +public interface IIncludableSpecificationBuilder : ISpecificationBuilder where T : class +{ +} + +public interface IIncludableSpecificationBuilder : ISpecificationBuilder where T : class +{ +} + +internal class IncludableSpecificationBuilder + : SpecificationBuilder, IIncludableSpecificationBuilder where T : class +{ + public IncludableSpecificationBuilder(Specification specification) : base(specification) + { + } +} + +internal class IncludableSpecificationBuilder + : SpecificationBuilder, IIncludableSpecificationBuilder where T : class +{ + public IncludableSpecificationBuilder(Specification specification) : base(specification) + { + } +} diff --git a/src/Ardalis.Specification/Builders/SpecificationBuilder.cs b/src/Ardalis.Specification/Builders/SpecificationBuilder.cs new file mode 100644 index 00000000..aa2f00bf --- /dev/null +++ b/src/Ardalis.Specification/Builders/SpecificationBuilder.cs @@ -0,0 +1,49 @@ +namespace Ardalis.Specification; + +public interface ICacheSpecificationBuilder : ISpecificationBuilder +{ +} + +public interface ICacheSpecificationBuilder : ISpecificationBuilder +{ +} + +public interface IOrderedSpecificationBuilder : ISpecificationBuilder +{ +} + +public interface IOrderedSpecificationBuilder : ISpecificationBuilder +{ +} + +public interface ISpecificationBuilder +{ + Specification Specification { get; } +} + +public interface ISpecificationBuilder +{ + Specification Specification { get; } +} + +internal class SpecificationBuilder + : ICacheSpecificationBuilder, IOrderedSpecificationBuilder, ISpecificationBuilder +{ + public Specification Specification { get; } + + public SpecificationBuilder(Specification specification) + { + Specification = specification; + } +} + +internal class SpecificationBuilder + : ICacheSpecificationBuilder, IOrderedSpecificationBuilder, ISpecificationBuilder +{ + public Specification Specification { get; } + + public SpecificationBuilder(Specification specification) + { + Specification = specification; + } +} diff --git a/src/Ardalis.Specification/Specification.cs b/src/Ardalis.Specification/Specification.cs index 5ddfe86b..a9025f56 100644 --- a/src/Ardalis.Specification/Specification.cs +++ b/src/Ardalis.Specification/Specification.cs @@ -1,4 +1,6 @@ -namespace Ardalis.Specification; +using System.ComponentModel; + +namespace Ardalis.Specification; /// public class Specification : Specification, ISpecification @@ -24,6 +26,13 @@ public class Specification : Specification, ISpecification public class Specification : ISpecification { + // It is utilized only during the building stage for the sub-chains. Once the state is built, we don't care about it anymore. + // The initial value is not important since the value is always initialized by the root of the chain. Therefore, we don't need ThreadLocal (it's more expensive). + // With this we're saving 8 bytes per include builder, and we don't need an order builder at all (saving 32 bytes per order builder instance). + [ThreadStatic] + [EditorBrowsable(EditorBrowsableState.Never)] + public static bool IsChainDiscarded; + // The state is null initially, but we're spending 8 bytes per reference (on x64). // This will be reconsidered for version 10 where we may store the whole state as a single array of structs. private List>? _whereExpressions;