< Summary

Information
Class: Pozitron.QuerySpecification.Specification<T>
Assembly: Pozitron.QuerySpecification
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Specification.cs
Tag: 67_15587897385
Line coverage
100%
Covered lines: 194
Uncovered lines: 0
Coverable lines: 194
Total lines: 568
Line coverage: 100%
Branch coverage
100%
Covered branches: 152
Total branches: 152
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
.ctor(...)100%11100%
Evaluate(...)100%11100%
IsSatisfiedBy(...)100%11100%
Add(...)100%11100%
AddOrUpdate(...)100%11100%
FirstOrDefault(...)100%88100%
First(...)100%88100%
OfType(...)100%22100%
Contains(...)100%66100%
AddInternal(...)100%1212100%
AddOrUpdateInternal(...)100%66100%
GetCompiledItems()100%66100%
GetCompiledItems(Pozitron.QuerySpecification.SpecItem[])100%66100%
CountCompilableItems(Pozitron.QuerySpecification.SpecItem[])100%88100%
GenerateCompiledItems(Pozitron.QuerySpecification.SpecItem[],System.Int32)100%2424100%
GetOrCreate(...)100%22100%
GetFlag(...)100%66100%
AddOrUpdateFlag(...)100%1212100%
Clone()100%22100%
Clone()100%22100%

File(s)

/home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Specification.cs

#LineLine coverage
 1using System.Diagnostics.CodeAnalysis;
 2
 3namespace 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>
 10public 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>
 55public 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>
 72868    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>
 274    public Specification(int initialCapacity)
 75    {
 276        _items = new SpecItem[initialCapacity];
 277    }
 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    {
 387        var evaluator = Evaluator;
 388        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    {
 498        var validator = Validator;
 499        return validator.IsValid(entity, this);
 100    }
 101
 102    /// <summary>
 103    /// Gets the evaluator.
 104    /// </summary>
 7105    protected virtual SpecificationMemoryEvaluator Evaluator => SpecificationMemoryEvaluator.Default;
 106
 107    /// <summary>
 108    /// Gets the validator.
 109    /// </summary>
 4110    protected virtual SpecificationValidator Validator => SpecificationValidator.Default;
 111
 112    /// <summary>
 113    /// Gets the specification builder.
 114    /// </summary>
 188115    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))]
 1712121    public bool IsEmpty => _items is null;
 122
 123    /// <summary>
 124    /// Gets the compiled where expressions.
 125    /// </summary>
 2126    public IEnumerable<WhereExpressionCompiled<T>> WhereExpressionsCompiled => _items is null
 2127        ? Enumerable.Empty<WhereExpressionCompiled<T>>()
 3128        : new SpecSelectIterator<Func<T, bool>, WhereExpressionCompiled<T>>(GetCompiledItems(), ItemType.Where, (x, bag)
 129
 130    /// <summary>
 131    /// Gets the compiled order expressions.
 132    /// </summary>
 2133    public IEnumerable<OrderExpressionCompiled<T>> OrderExpressionsCompiled => _items is null
 2134        ? Enumerable.Empty<OrderExpressionCompiled<T>>()
 4135        : new SpecSelectIterator<Func<T, object?>, OrderExpressionCompiled<T>>(GetCompiledItems(), ItemType.Order, (x, b
 136
 137    /// <summary>
 138    /// Gets the compiled like expressions.
 139    /// </summary>
 2140    public IEnumerable<LikeExpressionCompiled<T>> LikeExpressionsCompiled => _items is null
 2141        ? Enumerable.Empty<LikeExpressionCompiled<T>>()
 3142        : new SpecSelectIterator<SpecLikeCompiled<T>, LikeExpressionCompiled<T>>(GetCompiledItems(), ItemType.Like, (x, 
 143
 144    /// <summary>
 145    /// Gets the where expressions.
 146    /// </summary>
 18147    public IEnumerable<WhereExpression<T>> WhereExpressions => _items is null
 18148        ? Enumerable.Empty<WhereExpression<T>>()
 31149        : new SpecSelectIterator<Expression<Func<T, bool>>, WhereExpression<T>>(_items, ItemType.Where, (x, bag) => new 
 150
 151    /// <summary>
 152    /// Gets the include expressions.
 153    /// </summary>
 57154    public IEnumerable<IncludeExpression<T>> IncludeExpressions => _items is null
 57155        ? Enumerable.Empty<IncludeExpression<T>>()
 405156        : new SpecSelectIterator<LambdaExpression, IncludeExpression<T>>(_items, ItemType.Include, (x, bag) => new Inclu
 157
 158    /// <summary>
 159    /// Gets the order expressions.
 160    /// </summary>
 100161    public IEnumerable<OrderExpression<T>> OrderExpressions => _items is null
 100162        ? Enumerable.Empty<OrderExpression<T>>()
 290163        : new SpecSelectIterator<Expression<Func<T, object?>>, OrderExpression<T>>(_items, ItemType.Order, (x, bag) => n
 164
 165    /// <summary>
 166    /// Gets the like expressions.
 167    /// </summary>
 28168    public IEnumerable<LikeExpression<T>> LikeExpressions => _items is null
 28169        ? Enumerable.Empty<LikeExpression<T>>()
 63170        : new SpecSelectIterator<SpecLike<T>, LikeExpression<T>>(_items, ItemType.Like, (x, bag) => new LikeExpression<T
 171
 172    /// <summary>
 173    /// Gets the include strings.
 174    /// </summary>
 16175    public IEnumerable<string> IncludeStrings => _items is null
 16176        ? Enumerable.Empty<string>()
 27177        : new SpecSelectIterator<string, string>(_items, ItemType.IncludeString, (x, bag) => x);
 178
 179    /// <summary>
 180    /// Gets the Query tags.
 181    /// </summary>
 26182    public IEnumerable<string> QueryTags => _items is null
 26183        ? Enumerable.Empty<string>()
 60184        : 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>
 6189    public bool HasCacheKey => Contains(ItemType.CacheKey);
 190
 191    /// <summary>
 192    /// Gets the cache key.
 193    /// </summary>
 8194    public string? CacheKey => FirstOrDefault<string>(ItemType.CacheKey);
 195
 196    /// <summary>
 197    /// Gets the number of items to take.
 198    /// </summary>
 15199    public int Take => FirstOrDefault<SpecPaging>(ItemType.Paging)?.Take ?? -1;
 200
 201    /// <summary>
 202    /// Gets the number of items to skip.
 203    /// </summary>
 15204    public int Skip => FirstOrDefault<SpecPaging>(ItemType.Paging)?.Skip ?? -1;
 205
 206    /// <summary>
 207    /// Gets a value indicating whether IgnoreQueryFilters is applied.
 208    /// </summary>
 60209    public bool IgnoreQueryFilters => GetFlag(SpecFlags.IgnoreQueryFilters);
 210
 211    /// <summary>
 212    /// Gets a value indicating whether IgnoreAutoIncludes is applied.
 213    /// </summary>
 60214    public bool IgnoreAutoIncludes => GetFlag(SpecFlags.IgnoreAutoIncludes);
 215
 216    /// <summary>
 217    /// Gets a value indicating whether AsSplitQuery is applied.
 218    /// </summary>
 60219    public bool AsSplitQuery => GetFlag(SpecFlags.AsSplitQuery);
 220
 221    /// <summary>
 222    /// Gets a value indicating whether AsNoTracking is applied.
 223    /// </summary>
 71224    public bool AsNoTracking => GetFlag(SpecFlags.AsNoTracking);
 225
 226    /// <summary>
 227    /// Gets a value indicating whether AsNoTrackingWithIdentityResolution is applied.
 228    /// </summary>
 65229    public bool AsNoTrackingWithIdentityResolution => GetFlag(SpecFlags.AsNoTrackingWithIdentityResolution);
 230
 231    /// <summary>
 232    /// Gets a value indicating whether AsTracking is applied.
 233    /// </summary>
 65234    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    {
 21245        ArgumentNullException.ThrowIfNull(value);
 20246        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(type);
 247
 18248        AddInternal(type, value);
 18249    }
 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    {
 11260        ArgumentNullException.ThrowIfNull(value);
 10261        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(type);
 262
 8263        AddOrUpdateInternal(type, value);
 8264    }
 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    {
 280274        if (IsEmpty) return default;
 275
 2271276        foreach (var item in _items)
 277        {
 981278            if (item.Type == type && item.Reference is TObject reference)
 279            {
 127280                return reference;
 281            }
 282        }
 91283        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    {
 4295        if (IsEmpty) throw new InvalidOperationException("Specification contains no items");
 296
 11297        foreach (var item in _items)
 298        {
 4299            if (item.Type == type && item.Reference is TObject reference)
 300            {
 1301                return reference;
 302            }
 303        }
 1304        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>
 3313    public IEnumerable<TObject> OfType<TObject>(int type) => _items is null
 3314        ? Enumerable.Empty<TObject>()
 3315        : new SpecIterator<TObject>(_items, type);
 316
 345317    internal ReadOnlySpan<SpecItem> Items => _items ?? ReadOnlySpan<SpecItem>.Empty;
 318
 319    internal bool Contains(int type)
 320    {
 8321        if (IsEmpty) return false;
 322
 18323        foreach (var item in _items)
 324        {
 6325            if (item.Type == type)
 326            {
 2327                return true;
 328            }
 329        }
 2330        return false;
 331    }
 332
 333    [MemberNotNull(nameof(_items))]
 334    internal void AddInternal(int type, object value, int bag = 0)
 335    {
 661336        var newItem = new SpecItem
 661337        {
 661338            Type = type,
 661339            Reference = value,
 661340            Bag = bag
 661341        };
 342
 661343        if (IsEmpty)
 344        {
 345            // Specs with two items are very common, we'll optimize for that.
 258346            _items = new SpecItem[2];
 258347            _items[0] = newItem;
 348        }
 349        else
 350        {
 403351            var items = _items;
 352
 353            // We have a special case for Paging, we're storing it in the same item with Flags.
 403354            if (type == ItemType.Paging)
 355            {
 456356                for (var i = 0; i < items.Length; i++)
 357                {
 195358                    if (items[i].Type == ItemType.Paging)
 359                    {
 1360                        _items[i].Reference = newItem.Reference;
 1361                        return;
 362                    }
 363                }
 364            }
 365
 3994366            for (var i = 0; i < items.Length; i++)
 367            {
 1890368                if (items[i].Type == 0)
 369                {
 295370                    items[i] = newItem;
 295371                    return;
 372                }
 373            }
 374
 107375            var originalLength = items.Length;
 107376            var newArray = new SpecItem[originalLength + 4];
 107377            Array.Copy(items, newArray, originalLength);
 107378            newArray[originalLength] = newItem;
 107379            _items = newArray;
 380        }
 107381    }
 382    internal void AddOrUpdateInternal(int type, object value, int bag = 0)
 383    {
 90384        if (IsEmpty)
 385        {
 16386            AddInternal(type, value, bag);
 16387            return;
 388        }
 74389        var items = _items;
 664390        for (var i = 0; i < items.Length; i++)
 391        {
 261392            if (items[i].Type == type)
 393            {
 3394                _items[i].Reference = value;
 3395                _items[i].Bag = bag;
 3396                return;
 397            }
 398        }
 71399        AddInternal(type, value, bag);
 71400    }
 401    internal SpecItem[] GetCompiledItems()
 402    {
 90403        if (IsEmpty) return Array.Empty<SpecItem>();
 404
 78405        var compilableItemsCount = CountCompilableItems(_items);
 79406        if (compilableItemsCount == 0) return Array.Empty<SpecItem>();
 407
 77408        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.
 115411        if (compiledItems.Length == compilableItemsCount) return compiledItems;
 412
 39413        compiledItems = GenerateCompiledItems(_items, compilableItemsCount);
 39414        AddOrUpdateInternal(ItemType.Compiled, compiledItems);
 39415        return compiledItems;
 416
 417        static SpecItem[] GetCompiledItems(SpecItem[] items)
 418        {
 668419            foreach (var item in items)
 420            {
 276421                if (item.Type == ItemType.Compiled && item.Reference is SpecItem[] compiledItems)
 422                {
 38423                    return compiledItems;
 424                }
 425            }
 39426            return Array.Empty<SpecItem>();
 427        }
 428
 429        static int CountCompilableItems(SpecItem[] items)
 430        {
 78431            var count = 0;
 844432            foreach (var item in items)
 433            {
 344434                if (item.Type == ItemType.Where || item.Type == ItemType.Like || item.Type == ItemType.Order)
 151435                    count++;
 436            }
 78437            return count;
 438        }
 439
 440        static SpecItem[] GenerateCompiledItems(SpecItem[] items, int count)
 441        {
 39442            var compiledItems = new SpecItem[count];
 443
 444            // We want to place the items contiguously by type. Sorting is more expensive than looping per type.
 39445            var index = 0;
 298446            foreach (var item in items)
 447            {
 110448                if (item.Type == ItemType.Where && item.Reference is Expression<Func<T, bool>> expr)
 449                {
 20450                    compiledItems[index++] = new SpecItem
 20451                    {
 20452                        Type = item.Type,
 20453                        Reference = expr.Compile(),
 20454                        Bag = item.Bag
 20455                    };
 456                }
 457            }
 48458            if (index == count) return compiledItems;
 459
 244460            foreach (var item in items)
 461            {
 92462                if (item.Type == ItemType.Order && item.Reference is Expression<Func<T, object?>> expr)
 463                {
 22464                    compiledItems[index++] = new SpecItem
 22465                    {
 22466                        Type = item.Type,
 22467                        Reference = expr.Compile(),
 22468                        Bag = item.Bag
 22469                    };
 470                }
 471            }
 40472            if (index == count) return compiledItems;
 473
 20474            var likeStartIndex = index;
 168475            foreach (var item in items)
 476            {
 64477                if (item.Type == ItemType.Like && item.Reference is SpecLike<T> like)
 478                {
 31479                    compiledItems[index++] = new SpecItem
 31480                    {
 31481                        Type = item.Type,
 31482                        Reference = new SpecLikeCompiled<T>(like.KeySelector.Compile(), like.Pattern),
 31483                        Bag = item.Bag
 31484                    };
 485                }
 486            }
 487
 488            // Sort Like items by the group, so we do it only once and not repeatedly in the Like evaluator).
 32489            compiledItems.AsSpan()[likeStartIndex..count].Sort((x, y) => x.Bag.CompareTo(y.Bag));
 490
 20491            return compiledItems;
 492        }
 493    }
 494
 495    internal TObject GetOrCreate<TObject>(int type) where TObject : new()
 496    {
 79497        return FirstOrDefault<TObject>(type) ?? Create();
 498        TObject Create()
 499        {
 42500            var reference = new TObject();
 42501            AddInternal(type, reference);
 42502            return reference;
 503        }
 504    }
 505    internal bool GetFlag(SpecFlags flag)
 506    {
 406507        if (IsEmpty) return false;
 508
 3075509        foreach (var item in _items)
 510        {
 1266511            if (item.Type == ItemType.Flags)
 512            {
 169513                return ((SpecFlags)item.Bag & flag) == flag;
 514            }
 515        }
 187516        return false;
 517    }
 518    internal void AddOrUpdateFlag(SpecFlags flag, bool value)
 519    {
 158520        if (IsEmpty)
 521        {
 63522            if (value)
 523            {
 30524                AddInternal(ItemType.Flags, null!, (int)flag);
 525            }
 63526            return;
 527        }
 528
 95529        var items = _items;
 724530        for (var i = 0; i < items.Length; i++)
 531        {
 358532            if (items[i].Type == ItemType.Flags)
 533            {
 91534                var newValue = value
 91535                    ? (SpecFlags)items[i].Bag | flag
 91536                    : (SpecFlags)items[i].Bag & ~flag;
 537
 91538                _items[i].Bag = (int)newValue;
 91539                return;
 540            }
 541        }
 542
 4543        if (value)
 544        {
 3545            AddInternal(ItemType.Flags, null!, (int)flag);
 546        }
 4547    }
 548
 549    internal Specification<T> Clone()
 550    {
 3551        if (IsEmpty) return new Specification<T>();
 552
 1553        var items = _items;
 1554        var newItems = new SpecItem[items.Length];
 1555        Array.Copy(items, newItems, items.Length);
 1556        return new Specification<T> { _items = newItems };
 557    }
 558
 559    internal Specification<T, TResult> Clone<TResult>()
 560    {
 6561        if (IsEmpty) return new Specification<T, TResult>();
 562
 4563        var items = _items;
 4564        var newItems = new SpecItem[items.Length];
 4565        Array.Copy(items, newItems, items.Length);
 4566        return new Specification<T, TResult> { _items = newItems };
 567    }
 568}