|   |  | 1 |  | using System.Diagnostics.CodeAnalysis; | 
|   |  | 2 |  |  | 
|   |  | 3 |  | namespace Pozitron.QuerySpecification; | 
|   |  | 4 |  |  | 
|   |  | 5 |  | /// <summary> | 
|   |  | 6 |  | /// Represents a specification with a result type. | 
|   |  | 7 |  | /// </summary> | 
|   |  | 8 |  | /// <typeparam name="T">The type of the entity.</typeparam> | 
|   |  | 9 |  | /// <typeparam name="TResult">The type of the result.</typeparam> | 
|   |  | 10 |  | public class Specification<T, TResult> : Specification<T> | 
|   |  | 11 |  | { | 
|   |  | 12 |  |     /// <summary> | 
|   |  | 13 |  |     /// Initializes a new instance of the <see cref="Specification{T, TResult}"/> class. | 
|   |  | 14 |  |     /// </summary> | 
|   |  | 15 |  |     public Specification() : base() { } | 
|   |  | 16 |  |  | 
|   |  | 17 |  |     /// <summary> | 
|   |  | 18 |  |     /// Initializes a new instance of the <see cref="Specification{T, TResult}"/> class with the specified initial capac | 
|   |  | 19 |  |     /// </summary> | 
|   |  | 20 |  |     /// <param name="initialCapacity">The initial capacity of the specification.</param> | 
|   |  | 21 |  |     public Specification(int initialCapacity) : base(initialCapacity) { } | 
|   |  | 22 |  |  | 
|   |  | 23 |  |     /// <summary> | 
|   |  | 24 |  |     /// Evaluates the given entities according to the specification and returns the result. | 
|   |  | 25 |  |     /// </summary> | 
|   |  | 26 |  |     /// <param name="entities">The entities to evaluate.</param> | 
|   |  | 27 |  |     /// <param name="ignorePaging">Whether to ignore paging settings (Take/Skip) defined in the specification.</param> | 
|   |  | 28 |  |     /// <returns>The evaluated result.</returns> | 
|   |  | 29 |  |     public new virtual IEnumerable<TResult> Evaluate(IEnumerable<T> entities, bool ignorePaging = false) | 
|   |  | 30 |  |     { | 
|   |  | 31 |  |         var evaluator = Evaluator; | 
|   |  | 32 |  |         return evaluator.Evaluate(entities, this, ignorePaging); | 
|   |  | 33 |  |     } | 
|   |  | 34 |  |  | 
|   |  | 35 |  |     /// <summary> | 
|   |  | 36 |  |     /// Gets the specification builder. | 
|   |  | 37 |  |     /// </summary> | 
|   |  | 38 |  |     public new ISpecificationBuilder<T, TResult> Query => new SpecificationBuilder<T, TResult>(this); | 
|   |  | 39 |  |  | 
|   |  | 40 |  |     /// <summary> | 
|   |  | 41 |  |     /// Gets the Select expression. | 
|   |  | 42 |  |     /// </summary> | 
|   |  | 43 |  |     public Expression<Func<T, TResult>>? Selector => FirstOrDefault<Expression<Func<T, TResult>>>(ItemType.Select); | 
|   |  | 44 |  |  | 
|   |  | 45 |  |     /// <summary> | 
|   |  | 46 |  |     /// Gets the SelectMany expression. | 
|   |  | 47 |  |     /// </summary> | 
|   |  | 48 |  |     public Expression<Func<T, IEnumerable<TResult>>>? SelectorMany => FirstOrDefault<Expression<Func<T, IEnumerable<TRes | 
|   |  | 49 |  | } | 
|   |  | 50 |  |  | 
|   |  | 51 |  | /// <summary> | 
|   |  | 52 |  | /// Represents a specification. | 
|   |  | 53 |  | /// </summary> | 
|   |  | 54 |  | /// <typeparam name="T">The type of the entity.</typeparam> | 
|   |  | 55 |  | public partial class Specification<T> | 
|   |  | 56 |  | { | 
|   |  | 57 |  |     private SpecItem[]? _items; | 
|   |  | 58 |  |  | 
|   |  | 59 |  |     // It is utilized only during the building stage for the builder chains. Once the state is built, we don't care abou | 
|   |  | 60 |  |     // The initial value is not important since the value is always initialized by the root of the chain. Therefore, we  | 
|   |  | 61 |  |     // With this we're saving 8 bytes per include builder, and we don't need an order builder at all (saving 32 bytes pe | 
|   |  | 62 |  |     [ThreadStatic] | 
|   |  | 63 |  |     internal static bool IsChainDiscarded; | 
|   |  | 64 |  |  | 
|   |  | 65 |  |     /// <summary> | 
|   |  | 66 |  |     /// Initializes a new instance of the <see cref="Specification{T}"/> class. | 
|   |  | 67 |  |     /// </summary> | 
|   | 738 | 68 |  |     public Specification() { } | 
|   |  | 69 |  |  | 
|   |  | 70 |  |     /// <summary> | 
|   |  | 71 |  |     /// Initializes a new instance of the <see cref="Specification{T}"/> class with the specified initial capacity. | 
|   |  | 72 |  |     /// </summary> | 
|   |  | 73 |  |     /// <param name="initialCapacity">The initial capacity of the specification.</param> | 
|   | 2 | 74 |  |     public Specification(int initialCapacity) | 
|   |  | 75 |  |     { | 
|   | 2 | 76 |  |         _items = new SpecItem[initialCapacity]; | 
|   | 2 | 77 |  |     } | 
|   |  | 78 |  |  | 
|   |  | 79 |  |     /// <summary> | 
|   |  | 80 |  |     /// Evaluates the given entities according to the specification and returns the result. | 
|   |  | 81 |  |     /// </summary> | 
|   |  | 82 |  |     /// <param name="entities">The entities to evaluate.</param> | 
|   |  | 83 |  |     /// <param name="ignorePaging">Whether to ignore paging settings (Take/Skip) defined in the specification.</param> | 
|   |  | 84 |  |     /// <returns>The evaluated result.</returns> | 
|   |  | 85 |  |     public virtual IEnumerable<T> Evaluate(IEnumerable<T> entities, bool ignorePaging = false) | 
|   |  | 86 |  |     { | 
|   | 3 | 87 |  |         var evaluator = Evaluator; | 
|   | 3 | 88 |  |         return evaluator.Evaluate(entities, this, ignorePaging); | 
|   |  | 89 |  |     } | 
|   |  | 90 |  |  | 
|   |  | 91 |  |     /// <summary> | 
|   |  | 92 |  |     /// Determines whether the specified entity satisfies the specification. | 
|   |  | 93 |  |     /// </summary> | 
|   |  | 94 |  |     /// <param name="entity">The entity to evaluate.</param> | 
|   |  | 95 |  |     /// <returns>true if the entity satisfies the specification; otherwise, false.</returns> | 
|   |  | 96 |  |     public virtual bool IsSatisfiedBy(T entity) | 
|   |  | 97 |  |     { | 
|   | 4 | 98 |  |         var validator = Validator; | 
|   | 4 | 99 |  |         return validator.IsValid(entity, this); | 
|   |  | 100 |  |     } | 
|   |  | 101 |  |  | 
|   |  | 102 |  |     /// <summary> | 
|   |  | 103 |  |     /// Gets the evaluator. | 
|   |  | 104 |  |     /// </summary> | 
|   | 7 | 105 |  |     protected virtual SpecificationMemoryEvaluator Evaluator => SpecificationMemoryEvaluator.Default; | 
|   |  | 106 |  |  | 
|   |  | 107 |  |     /// <summary> | 
|   |  | 108 |  |     /// Gets the validator. | 
|   |  | 109 |  |     /// </summary> | 
|   | 4 | 110 |  |     protected virtual SpecificationValidator Validator => SpecificationValidator.Default; | 
|   |  | 111 |  |  | 
|   |  | 112 |  |     /// <summary> | 
|   |  | 113 |  |     /// Gets the specification builder. | 
|   |  | 114 |  |     /// </summary> | 
|   | 192 | 115 |  |     public ISpecificationBuilder<T> Query => new SpecificationBuilder<T>(this); | 
|   |  | 116 |  |  | 
|   |  | 117 |  |     /// <summary> | 
|   |  | 118 |  |     /// Gets a value indicating whether the specification is empty. | 
|   |  | 119 |  |     /// </summary> | 
|   |  | 120 |  |     [MemberNotNullWhen(false, nameof(_items))] | 
|   | 1726 | 121 |  |     public bool IsEmpty => _items is null; | 
|   |  | 122 |  |  | 
|   |  | 123 |  |     /// <summary> | 
|   |  | 124 |  |     /// Gets the compiled where expressions. | 
|   |  | 125 |  |     /// </summary> | 
|   | 2 | 126 |  |     public IEnumerable<WhereExpressionCompiled<T>> WhereExpressionsCompiled => _items is null | 
|   | 2 | 127 |  |         ? Enumerable.Empty<WhereExpressionCompiled<T>>() | 
|   | 3 | 128 |  |         : new SpecSelectIterator<Func<T, bool>, WhereExpressionCompiled<T>>(GetCompiledItems(), ItemType.Where, (x, bag) | 
|   |  | 129 |  |  | 
|   |  | 130 |  |     /// <summary> | 
|   |  | 131 |  |     /// Gets the compiled order expressions. | 
|   |  | 132 |  |     /// </summary> | 
|   | 2 | 133 |  |     public IEnumerable<OrderExpressionCompiled<T>> OrderExpressionsCompiled => _items is null | 
|   | 2 | 134 |  |         ? Enumerable.Empty<OrderExpressionCompiled<T>>() | 
|   | 4 | 135 |  |         : new SpecSelectIterator<Func<T, object?>, OrderExpressionCompiled<T>>(GetCompiledItems(), ItemType.Order, (x, b | 
|   |  | 136 |  |  | 
|   |  | 137 |  |     /// <summary> | 
|   |  | 138 |  |     /// Gets the compiled like expressions. | 
|   |  | 139 |  |     /// </summary> | 
|   | 2 | 140 |  |     public IEnumerable<LikeExpressionCompiled<T>> LikeExpressionsCompiled => _items is null | 
|   | 2 | 141 |  |         ? Enumerable.Empty<LikeExpressionCompiled<T>>() | 
|   | 3 | 142 |  |         : new SpecSelectIterator<SpecLikeCompiled<T>, LikeExpressionCompiled<T>>(GetCompiledItems(), ItemType.Like, (x,  | 
|   |  | 143 |  |  | 
|   |  | 144 |  |     /// <summary> | 
|   |  | 145 |  |     /// Gets the where expressions. | 
|   |  | 146 |  |     /// </summary> | 
|   | 18 | 147 |  |     public IEnumerable<WhereExpression<T>> WhereExpressions => _items is null | 
|   | 18 | 148 |  |         ? Enumerable.Empty<WhereExpression<T>>() | 
|   | 31 | 149 |  |         : new SpecSelectIterator<Expression<Func<T, bool>>, WhereExpression<T>>(_items, ItemType.Where, (x, bag) => new  | 
|   |  | 150 |  |  | 
|   |  | 151 |  |     /// <summary> | 
|   |  | 152 |  |     /// Gets the include expressions. | 
|   |  | 153 |  |     /// </summary> | 
|   | 57 | 154 |  |     public IEnumerable<IncludeExpression<T>> IncludeExpressions => _items is null | 
|   | 57 | 155 |  |         ? Enumerable.Empty<IncludeExpression<T>>() | 
|   | 405 | 156 |  |         : new SpecSelectIterator<LambdaExpression, IncludeExpression<T>>(_items, ItemType.Include, (x, bag) => new Inclu | 
|   |  | 157 |  |  | 
|   |  | 158 |  |     /// <summary> | 
|   |  | 159 |  |     /// Gets the order expressions. | 
|   |  | 160 |  |     /// </summary> | 
|   | 100 | 161 |  |     public IEnumerable<OrderExpression<T>> OrderExpressions => _items is null | 
|   | 100 | 162 |  |         ? Enumerable.Empty<OrderExpression<T>>() | 
|   | 290 | 163 |  |         : new SpecSelectIterator<Expression<Func<T, object?>>, OrderExpression<T>>(_items, ItemType.Order, (x, bag) => n | 
|   |  | 164 |  |  | 
|   |  | 165 |  |     /// <summary> | 
|   |  | 166 |  |     /// Gets the like expressions. | 
|   |  | 167 |  |     /// </summary> | 
|   | 28 | 168 |  |     public IEnumerable<LikeExpression<T>> LikeExpressions => _items is null | 
|   | 28 | 169 |  |         ? Enumerable.Empty<LikeExpression<T>>() | 
|   | 63 | 170 |  |         : new SpecSelectIterator<SpecLike<T>, LikeExpression<T>>(_items, ItemType.Like, (x, bag) => new LikeExpression<T | 
|   |  | 171 |  |  | 
|   |  | 172 |  |     /// <summary> | 
|   |  | 173 |  |     /// Gets the include strings. | 
|   |  | 174 |  |     /// </summary> | 
|   | 16 | 175 |  |     public IEnumerable<string> IncludeStrings => _items is null | 
|   | 16 | 176 |  |         ? Enumerable.Empty<string>() | 
|   | 27 | 177 |  |         : new SpecSelectIterator<string, string>(_items, ItemType.IncludeString, (x, bag) => x); | 
|   |  | 178 |  |  | 
|   |  | 179 |  |     /// <summary> | 
|   |  | 180 |  |     /// Gets the Query tags. | 
|   |  | 181 |  |     /// </summary> | 
|   | 26 | 182 |  |     public IEnumerable<string> QueryTags => _items is null | 
|   | 26 | 183 |  |         ? Enumerable.Empty<string>() | 
|   | 60 | 184 |  |         : new SpecSelectIterator<string, string>(_items, ItemType.QueryTag, (x, bag) => x); | 
|   |  | 185 |  |  | 
|   |  | 186 |  |     /// <summary> | 
|   |  | 187 |  |     /// Return whether or not a cache key is set. | 
|   |  | 188 |  |     /// </summary> | 
|   | 6 | 189 |  |     public bool HasCacheKey => Contains(ItemType.CacheKey); | 
|   |  | 190 |  |  | 
|   |  | 191 |  |     /// <summary> | 
|   |  | 192 |  |     /// Gets the cache key. | 
|   |  | 193 |  |     /// </summary> | 
|   | 8 | 194 |  |     public string? CacheKey => FirstOrDefault<string>(ItemType.CacheKey); | 
|   |  | 195 |  |  | 
|   |  | 196 |  |     /// <summary> | 
|   |  | 197 |  |     /// Gets the number of items to take. | 
|   |  | 198 |  |     /// </summary> | 
|   | 15 | 199 |  |     public int Take => FirstOrDefault<SpecPaging>(ItemType.Paging)?.Take ?? -1; | 
|   |  | 200 |  |  | 
|   |  | 201 |  |     /// <summary> | 
|   |  | 202 |  |     /// Gets the number of items to skip. | 
|   |  | 203 |  |     /// </summary> | 
|   | 15 | 204 |  |     public int Skip => FirstOrDefault<SpecPaging>(ItemType.Paging)?.Skip ?? -1; | 
|   |  | 205 |  |  | 
|   |  | 206 |  |     /// <summary> | 
|   |  | 207 |  |     /// Gets a value indicating whether IgnoreQueryFilters is applied. | 
|   |  | 208 |  |     /// </summary> | 
|   | 60 | 209 |  |     public bool IgnoreQueryFilters => GetFlag(SpecFlags.IgnoreQueryFilters); | 
|   |  | 210 |  |  | 
|   |  | 211 |  |     /// <summary> | 
|   |  | 212 |  |     /// Gets a value indicating whether IgnoreAutoIncludes is applied. | 
|   |  | 213 |  |     /// </summary> | 
|   | 60 | 214 |  |     public bool IgnoreAutoIncludes => GetFlag(SpecFlags.IgnoreAutoIncludes); | 
|   |  | 215 |  |  | 
|   |  | 216 |  |     /// <summary> | 
|   |  | 217 |  |     /// Gets a value indicating whether AsSplitQuery is applied. | 
|   |  | 218 |  |     /// </summary> | 
|   | 60 | 219 |  |     public bool AsSplitQuery => GetFlag(SpecFlags.AsSplitQuery); | 
|   |  | 220 |  |  | 
|   |  | 221 |  |     /// <summary> | 
|   |  | 222 |  |     /// Gets a value indicating whether AsNoTracking is applied. | 
|   |  | 223 |  |     /// </summary> | 
|   | 71 | 224 |  |     public bool AsNoTracking => GetFlag(SpecFlags.AsNoTracking); | 
|   |  | 225 |  |  | 
|   |  | 226 |  |     /// <summary> | 
|   |  | 227 |  |     /// Gets a value indicating whether AsNoTrackingWithIdentityResolution is applied. | 
|   |  | 228 |  |     /// </summary> | 
|   | 65 | 229 |  |     public bool AsNoTrackingWithIdentityResolution => GetFlag(SpecFlags.AsNoTrackingWithIdentityResolution); | 
|   |  | 230 |  |  | 
|   |  | 231 |  |     /// <summary> | 
|   |  | 232 |  |     /// Gets a value indicating whether AsTracking is applied. | 
|   |  | 233 |  |     /// </summary> | 
|   | 65 | 234 |  |     public bool AsTracking => GetFlag(SpecFlags.AsTracking); | 
|   |  | 235 |  |  | 
|   |  | 236 |  |     /// <summary> | 
|   |  | 237 |  |     /// Adds an item to the specification. | 
|   |  | 238 |  |     /// </summary> | 
|   |  | 239 |  |     /// <param name="type">The type of the item. It must be a positive number.</param> | 
|   |  | 240 |  |     /// <param name="value">The object to be stored in the item.</param> | 
|   |  | 241 |  |     /// <exception cref="ArgumentNullException">If value is null</exception> | 
|   |  | 242 |  |     /// <exception cref="ArgumentOutOfRangeException">If type is zero or negative.</exception> | 
|   |  | 243 |  |     public void Add(int type, object value) | 
|   |  | 244 |  |     { | 
|   | 21 | 245 |  |         ArgumentNullException.ThrowIfNull(value); | 
|   | 20 | 246 |  |         ArgumentOutOfRangeException.ThrowIfNegativeOrZero(type); | 
|   |  | 247 |  |  | 
|   | 18 | 248 |  |         AddInternal(type, value); | 
|   | 18 | 249 |  |     } | 
|   |  | 250 |  |  | 
|   |  | 251 |  |     /// <summary> | 
|   |  | 252 |  |     /// Adds or updates an item in the specification. | 
|   |  | 253 |  |     /// </summary> | 
|   |  | 254 |  |     /// <param name="type">The type of the item.</param> | 
|   |  | 255 |  |     /// <param name="value">The object to be stored in the item.</param> | 
|   |  | 256 |  |     /// <exception cref="ArgumentNullException">Thrown if value is null</exception> | 
|   |  | 257 |  |     /// <exception cref="ArgumentOutOfRangeException">Thrown if type is zero or negative.</exception> | 
|   |  | 258 |  |     public void AddOrUpdate(int type, object value) | 
|   |  | 259 |  |     { | 
|   | 11 | 260 |  |         ArgumentNullException.ThrowIfNull(value); | 
|   | 10 | 261 |  |         ArgumentOutOfRangeException.ThrowIfNegativeOrZero(type); | 
|   |  | 262 |  |  | 
|   | 8 | 263 |  |         AddOrUpdateInternal(type, value); | 
|   | 8 | 264 |  |     } | 
|   |  | 265 |  |  | 
|   |  | 266 |  |     /// <summary> | 
|   |  | 267 |  |     /// Gets the first item of the specified type or the default value if no item is found. | 
|   |  | 268 |  |     /// </summary> | 
|   |  | 269 |  |     /// <typeparam name="TObject">The type of the object stored in the item.</typeparam> | 
|   |  | 270 |  |     /// <param name="type">The type of the item.</param> | 
|   |  | 271 |  |     /// <returns>The first item of the specified type or the default value if no item is found.</returns> | 
|   |  | 272 |  |     public TObject? FirstOrDefault<TObject>(int type) | 
|   |  | 273 |  |     { | 
|   | 280 | 274 |  |         if (IsEmpty) return default; | 
|   |  | 275 |  |  | 
|   | 2271 | 276 |  |         foreach (var item in _items) | 
|   |  | 277 |  |         { | 
|   | 981 | 278 |  |             if (item.Type == type && item.Reference is TObject reference) | 
|   |  | 279 |  |             { | 
|   | 127 | 280 |  |                 return reference; | 
|   |  | 281 |  |             } | 
|   |  | 282 |  |         } | 
|   | 91 | 283 |  |         return default; | 
|   |  | 284 |  |     } | 
|   |  | 285 |  |  | 
|   |  | 286 |  |     /// <summary> | 
|   |  | 287 |  |     /// Gets the first item of the specified type. | 
|   |  | 288 |  |     /// </summary> | 
|   |  | 289 |  |     /// <typeparam name="TObject">The type of the object stored in the item.</typeparam> | 
|   |  | 290 |  |     /// <param name="type">The type of the item.</param> | 
|   |  | 291 |  |     /// <returns>The first item of the specified type.</returns> | 
|   |  | 292 |  |     /// <exception cref="InvalidOperationException">Thrown if no matching item is found.</exception> | 
|   |  | 293 |  |     public TObject First<TObject>(int type) | 
|   |  | 294 |  |     { | 
|   | 4 | 295 |  |         if (IsEmpty) throw new InvalidOperationException("Specification contains no items"); | 
|   |  | 296 |  |  | 
|   | 11 | 297 |  |         foreach (var item in _items) | 
|   |  | 298 |  |         { | 
|   | 4 | 299 |  |             if (item.Type == type && item.Reference is TObject reference) | 
|   |  | 300 |  |             { | 
|   | 1 | 301 |  |                 return reference; | 
|   |  | 302 |  |             } | 
|   |  | 303 |  |         } | 
|   | 1 | 304 |  |         throw new InvalidOperationException("Specification contains no matching item"); | 
|   |  | 305 |  |     } | 
|   |  | 306 |  |  | 
|   |  | 307 |  |     /// <summary> | 
|   |  | 308 |  |     /// Gets all items of the specified type. | 
|   |  | 309 |  |     /// </summary> | 
|   |  | 310 |  |     /// <typeparam name="TObject">The type of the object stored in the item.</typeparam> | 
|   |  | 311 |  |     /// <param name="type">The type of the items.</param> | 
|   |  | 312 |  |     /// <returns>An enumerable of items of the specified type.</returns> | 
|   | 3 | 313 |  |     public IEnumerable<TObject> OfType<TObject>(int type) => _items is null | 
|   | 3 | 314 |  |         ? Enumerable.Empty<TObject>() | 
|   | 3 | 315 |  |         : new SpecIterator<TObject>(_items, type); | 
|   |  | 316 |  |  | 
|   | 348 | 317 |  |     internal ReadOnlySpan<SpecItem> Items => _items ?? ReadOnlySpan<SpecItem>.Empty; | 
|   |  | 318 |  |  | 
|   |  | 319 |  |     internal bool Contains(int type) | 
|   |  | 320 |  |     { | 
|   | 8 | 321 |  |         if (IsEmpty) return false; | 
|   |  | 322 |  |  | 
|   | 18 | 323 |  |         foreach (var item in _items) | 
|   |  | 324 |  |         { | 
|   | 6 | 325 |  |             if (item.Type == type) | 
|   |  | 326 |  |             { | 
|   | 2 | 327 |  |                 return true; | 
|   |  | 328 |  |             } | 
|   |  | 329 |  |         } | 
|   | 2 | 330 |  |         return false; | 
|   |  | 331 |  |     } | 
|   |  | 332 |  |  | 
|   |  | 333 |  |     [MemberNotNull(nameof(_items))] | 
|   |  | 334 |  |     internal void AddInternal(int type, object value, int bag = 0) | 
|   |  | 335 |  |     { | 
|   | 669 | 336 |  |         var newItem = new SpecItem | 
|   | 669 | 337 |  |         { | 
|   | 669 | 338 |  |             Type = type, | 
|   | 669 | 339 |  |             Reference = value, | 
|   | 669 | 340 |  |             Bag = bag | 
|   | 669 | 341 |  |         }; | 
|   |  | 342 |  |  | 
|   | 669 | 343 |  |         if (IsEmpty) | 
|   |  | 344 |  |         { | 
|   |  | 345 |  |             // Specs with two items are very common, we'll optimize for that. | 
|   | 262 | 346 |  |             _items = new SpecItem[2]; | 
|   | 262 | 347 |  |             _items[0] = newItem; | 
|   |  | 348 |  |         } | 
|   |  | 349 |  |         else | 
|   |  | 350 |  |         { | 
|   | 407 | 351 |  |             var items = _items; | 
|   |  | 352 |  |  | 
|   |  | 353 |  |             // We have a special case for Paging, we're storing it in the same item with Flags. | 
|   | 407 | 354 |  |             if (type == ItemType.Paging) | 
|   |  | 355 |  |             { | 
|   | 456 | 356 |  |                 for (var i = 0; i < items.Length; i++) | 
|   |  | 357 |  |                 { | 
|   | 195 | 358 |  |                     if (items[i].Type == ItemType.Paging) | 
|   |  | 359 |  |                     { | 
|   | 1 | 360 |  |                         _items[i].Reference = newItem.Reference; | 
|   | 1 | 361 |  |                         return; | 
|   |  | 362 |  |                     } | 
|   |  | 363 |  |                 } | 
|   |  | 364 |  |             } | 
|   |  | 365 |  |  | 
|   | 4012 | 366 |  |             for (var i = 0; i < items.Length; i++) | 
|   |  | 367 |  |             { | 
|   | 1898 | 368 |  |                 if (items[i].Type == 0) | 
|   |  | 369 |  |                 { | 
|   | 298 | 370 |  |                     items[i] = newItem; | 
|   | 298 | 371 |  |                     return; | 
|   |  | 372 |  |                 } | 
|   |  | 373 |  |             } | 
|   |  | 374 |  |  | 
|   | 108 | 375 |  |             var originalLength = items.Length; | 
|   | 108 | 376 |  |             var newArray = new SpecItem[originalLength + 4]; | 
|   | 108 | 377 |  |             Array.Copy(items, newArray, originalLength); | 
|   | 108 | 378 |  |             newArray[originalLength] = newItem; | 
|   | 108 | 379 |  |             _items = newArray; | 
|   |  | 380 |  |         } | 
|   | 108 | 381 |  |     } | 
|   |  | 382 |  |     internal void AddOrUpdateInternal(int type, object value, int bag = 0) | 
|   |  | 383 |  |     { | 
|   | 93 | 384 |  |         if (IsEmpty) | 
|   |  | 385 |  |         { | 
|   | 16 | 386 |  |             AddInternal(type, value, bag); | 
|   | 16 | 387 |  |             return; | 
|   |  | 388 |  |         } | 
|   | 77 | 389 |  |         var items = _items; | 
|   | 682 | 390 |  |         for (var i = 0; i < items.Length; i++) | 
|   |  | 391 |  |         { | 
|   | 267 | 392 |  |             if (items[i].Type == type) | 
|   |  | 393 |  |             { | 
|   | 3 | 394 |  |                 _items[i].Reference = value; | 
|   | 3 | 395 |  |                 _items[i].Bag = bag; | 
|   | 3 | 396 |  |                 return; | 
|   |  | 397 |  |             } | 
|   |  | 398 |  |         } | 
|   | 74 | 399 |  |         AddInternal(type, value, bag); | 
|   | 74 | 400 |  |     } | 
|   |  | 401 |  |     internal SpecItem[] GetCompiledItems() | 
|   |  | 402 |  |     { | 
|   | 93 | 403 |  |         if (IsEmpty) return Array.Empty<SpecItem>(); | 
|   |  | 404 |  |  | 
|   | 81 | 405 |  |         var compilableItemsCount = CountCompilableItems(_items); | 
|   | 82 | 406 |  |         if (compilableItemsCount == 0) return Array.Empty<SpecItem>(); | 
|   |  | 407 |  |  | 
|   | 80 | 408 |  |         var compiledItems = GetCompiledItems(_items); | 
|   |  | 409 |  |  | 
|   |  | 410 |  |         // If the count of compilable items is equal to the count of compiled items, we don't need to recompile. | 
|   | 118 | 411 |  |         if (compiledItems.Length == compilableItemsCount) return compiledItems; | 
|   |  | 412 |  |  | 
|   | 42 | 413 |  |         compiledItems = GenerateCompiledItems(_items, compilableItemsCount); | 
|   | 42 | 414 |  |         AddOrUpdateInternal(ItemType.Compiled, compiledItems); | 
|   | 42 | 415 |  |         return compiledItems; | 
|   |  | 416 |  |  | 
|   |  | 417 |  |         static SpecItem[] GetCompiledItems(SpecItem[] items) | 
|   |  | 418 |  |         { | 
|   | 686 | 419 |  |             foreach (var item in items) | 
|   |  | 420 |  |             { | 
|   | 282 | 421 |  |                 if (item.Type == ItemType.Compiled && item.Reference is SpecItem[] compiledItems) | 
|   |  | 422 |  |                 { | 
|   | 38 | 423 |  |                     return compiledItems; | 
|   |  | 424 |  |                 } | 
|   |  | 425 |  |             } | 
|   | 42 | 426 |  |             return Array.Empty<SpecItem>(); | 
|   |  | 427 |  |         } | 
|   |  | 428 |  |  | 
|   |  | 429 |  |         static int CountCompilableItems(SpecItem[] items) | 
|   |  | 430 |  |         { | 
|   | 81 | 431 |  |             var count = 0; | 
|   | 862 | 432 |  |             foreach (var item in items) | 
|   |  | 433 |  |             { | 
|   | 350 | 434 |  |                 if (item.Type == ItemType.Where || item.Type == ItemType.Like || item.Type == ItemType.Order) | 
|   | 155 | 435 |  |                     count++; | 
|   |  | 436 |  |             } | 
|   | 81 | 437 |  |             return count; | 
|   |  | 438 |  |         } | 
|   |  | 439 |  |  | 
|   |  | 440 |  |         static SpecItem[] GenerateCompiledItems(SpecItem[] items, int count) | 
|   |  | 441 |  |         { | 
|   | 42 | 442 |  |             var compiledItems = new SpecItem[count]; | 
|   |  | 443 |  |  | 
|   |  | 444 |  |             // We want to place the items contiguously by type. Sorting is more expensive than looping per type. | 
|   | 42 | 445 |  |             var index = 0; | 
|   | 316 | 446 |  |             foreach (var item in items) | 
|   |  | 447 |  |             { | 
|   | 116 | 448 |  |                 if (item.Type == ItemType.Where && item.Reference is Expression<Func<T, bool>> expr) | 
|   |  | 449 |  |                 { | 
|   | 22 | 450 |  |                     compiledItems[index++] = new SpecItem | 
|   | 22 | 451 |  |                     { | 
|   | 22 | 452 |  |                         Type = item.Type, | 
|   | 22 | 453 |  |                         Reference = expr.Compile(), | 
|   | 22 | 454 |  |                         Bag = item.Bag | 
|   | 22 | 455 |  |                     }; | 
|   |  | 456 |  |                 } | 
|   |  | 457 |  |             } | 
|   | 52 | 458 |  |             if (index == count) return compiledItems; | 
|   |  | 459 |  |  | 
|   | 256 | 460 |  |             foreach (var item in items) | 
|   |  | 461 |  |             { | 
|   | 96 | 462 |  |                 if (item.Type == ItemType.Order && item.Reference is Expression<Func<T, object?>> expr) | 
|   |  | 463 |  |                 { | 
|   | 22 | 464 |  |                     compiledItems[index++] = new SpecItem | 
|   | 22 | 465 |  |                     { | 
|   | 22 | 466 |  |                         Type = item.Type, | 
|   | 22 | 467 |  |                         Reference = expr.Compile(), | 
|   | 22 | 468 |  |                         Bag = item.Bag | 
|   | 22 | 469 |  |                     }; | 
|   |  | 470 |  |                 } | 
|   |  | 471 |  |             } | 
|   | 42 | 472 |  |             if (index == count) return compiledItems; | 
|   |  | 473 |  |  | 
|   | 22 | 474 |  |             var likeStartIndex = index; | 
|   | 180 | 475 |  |             foreach (var item in items) | 
|   |  | 476 |  |             { | 
|   | 68 | 477 |  |                 if (item.Type == ItemType.Like && item.Reference is SpecLike<T> like) | 
|   |  | 478 |  |                 { | 
|   | 33 | 479 |  |                     compiledItems[index++] = new SpecItem | 
|   | 33 | 480 |  |                     { | 
|   | 33 | 481 |  |                         Type = item.Type, | 
|   | 33 | 482 |  |                         Reference = new SpecLikeCompiled<T>(like.KeySelector.Compile(), like.Pattern), | 
|   | 33 | 483 |  |                         Bag = item.Bag | 
|   | 33 | 484 |  |                     }; | 
|   |  | 485 |  |                 } | 
|   |  | 486 |  |             } | 
|   |  | 487 |  |  | 
|   |  | 488 |  |             // Sort Like items by the group, so we do it only once and not repeatedly in the Like evaluator). | 
|   | 34 | 489 |  |             compiledItems.AsSpan()[likeStartIndex..count].Sort((x, y) => x.Bag.CompareTo(y.Bag)); | 
|   |  | 490 |  |  | 
|   | 22 | 491 |  |             return compiledItems; | 
|   |  | 492 |  |         } | 
|   |  | 493 |  |     } | 
|   |  | 494 |  |  | 
|   |  | 495 |  |     internal TObject GetOrCreate<TObject>(int type) where TObject : new() | 
|   |  | 496 |  |     { | 
|   | 79 | 497 |  |         return FirstOrDefault<TObject>(type) ?? Create(); | 
|   |  | 498 |  |         TObject Create() | 
|   |  | 499 |  |         { | 
|   | 42 | 500 |  |             var reference = new TObject(); | 
|   | 42 | 501 |  |             AddInternal(type, reference); | 
|   | 42 | 502 |  |             return reference; | 
|   |  | 503 |  |         } | 
|   |  | 504 |  |     } | 
|   |  | 505 |  |     internal bool GetFlag(SpecFlags flag) | 
|   |  | 506 |  |     { | 
|   | 406 | 507 |  |         if (IsEmpty) return false; | 
|   |  | 508 |  |  | 
|   | 3075 | 509 |  |         foreach (var item in _items) | 
|   |  | 510 |  |         { | 
|   | 1266 | 511 |  |             if (item.Type == ItemType.Flags) | 
|   |  | 512 |  |             { | 
|   | 169 | 513 |  |                 return ((SpecFlags)item.Bag & flag) == flag; | 
|   |  | 514 |  |             } | 
|   |  | 515 |  |         } | 
|   | 187 | 516 |  |         return false; | 
|   |  | 517 |  |     } | 
|   |  | 518 |  |     internal void AddOrUpdateFlag(SpecFlags flag, bool value) | 
|   |  | 519 |  |     { | 
|   | 158 | 520 |  |         if (IsEmpty) | 
|   |  | 521 |  |         { | 
|   | 63 | 522 |  |             if (value) | 
|   |  | 523 |  |             { | 
|   | 30 | 524 |  |                 AddInternal(ItemType.Flags, null!, (int)flag); | 
|   |  | 525 |  |             } | 
|   | 63 | 526 |  |             return; | 
|   |  | 527 |  |         } | 
|   |  | 528 |  |  | 
|   | 95 | 529 |  |         var items = _items; | 
|   | 724 | 530 |  |         for (var i = 0; i < items.Length; i++) | 
|   |  | 531 |  |         { | 
|   | 358 | 532 |  |             if (items[i].Type == ItemType.Flags) | 
|   |  | 533 |  |             { | 
|   | 91 | 534 |  |                 var newValue = value | 
|   | 91 | 535 |  |                     ? (SpecFlags)items[i].Bag | flag | 
|   | 91 | 536 |  |                     : (SpecFlags)items[i].Bag & ~flag; | 
|   |  | 537 |  |  | 
|   | 91 | 538 |  |                 _items[i].Bag = (int)newValue; | 
|   | 91 | 539 |  |                 return; | 
|   |  | 540 |  |             } | 
|   |  | 541 |  |         } | 
|   |  | 542 |  |  | 
|   | 4 | 543 |  |         if (value) | 
|   |  | 544 |  |         { | 
|   | 3 | 545 |  |             AddInternal(ItemType.Flags, null!, (int)flag); | 
|   |  | 546 |  |         } | 
|   | 4 | 547 |  |     } | 
|   |  | 548 |  |  | 
|   |  | 549 |  |     internal Specification<T> Clone() | 
|   |  | 550 |  |     { | 
|   | 3 | 551 |  |         if (IsEmpty) return new Specification<T>(); | 
|   |  | 552 |  |  | 
|   | 1 | 553 |  |         var items = _items; | 
|   | 1 | 554 |  |         var newItems = new SpecItem[items.Length]; | 
|   | 1 | 555 |  |         Array.Copy(items, newItems, items.Length); | 
|   | 1 | 556 |  |         return new Specification<T> { _items = newItems }; | 
|   |  | 557 |  |     } | 
|   |  | 558 |  |  | 
|   |  | 559 |  |     internal Specification<T, TResult> Clone<TResult>() | 
|   |  | 560 |  |     { | 
|   | 6 | 561 |  |         if (IsEmpty) return new Specification<T, TResult>(); | 
|   |  | 562 |  |  | 
|   | 4 | 563 |  |         var items = _items; | 
|   | 4 | 564 |  |         var newItems = new SpecItem[items.Length]; | 
|   | 4 | 565 |  |         Array.Copy(items, newItems, items.Length); | 
|   | 4 | 566 |  |         return new Specification<T, TResult> { _items = newItems }; | 
|   |  | 567 |  |     } | 
|   |  | 568 |  | } |