< Summary

Information
Class: Pozitron.QuerySpecification.Specification<T>
Assembly: Pozitron.QuerySpecification
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Specification.cs
Tag: 52_11740816328
Line coverage
100%
Covered lines: 173
Uncovered lines: 0
Coverable lines: 173
Total lines: 512
Line coverage: 100%
Branch coverage
100%
Covered branches: 138
Total branches: 138
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
.ctor(...)100%11100%
Evaluate(...)100%11100%
IsSatisfiedBy(...)100%11100%
get_Evaluator()100%11100%
get_Validator()100%11100%
get_Query()100%11100%
get_IsEmpty()100%11100%
get_WhereExpressionsCompiled()100%44100%
get_OrderExpressionsCompiled()100%44100%
get_LikeExpressionsCompiled()100%44100%
get_WhereExpressions()100%44100%
get_IncludeExpressions()100%44100%
get_OrderExpressions()100%44100%
get_LikeExpressions()100%44100%
get_IncludeStrings()100%44100%
get_Take()100%22100%
get_Skip()100%22100%
get_IgnoreQueryFilters()100%11100%
get_AsSplitQuery()100%11100%
get_AsNoTracking()100%11100%
get_AsNoTrackingWithIdentityResolution()100%11100%
get_AsTracking()100%11100%
Add(...)100%11100%
AddOrUpdate(...)100%11100%
FirstOrDefault(...)100%88100%
First(...)100%88100%
OfType(...)100%22100%
get_Items()100%22100%
AddInternal(...)100%1212100%
AddOrUpdateInternal(...)100%66100%
GetCompiledItems()100%66100%
GetCompiledItems()100%66100%
CountCompilableItems()100%88100%
GenerateCompiledItems()100%2424100%
GetOrCreate(...)100%22100%
Create()100%11100%
GetFlag(...)100%66100%
AddOrUpdateFlag(...)100%1212100%

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>
 62468    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 SpecificationInMemoryEvaluator Evaluator => SpecificationInMemoryEvaluator.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>
 163115    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))]
 1472121    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>
 12147    public IEnumerable<WhereExpression<T>> WhereExpressions => _items is null
 12148        ? Enumerable.Empty<WhereExpression<T>>()
 21149        : new SpecSelectIterator<Expression<Func<T, bool>>, WhereExpression<T>>(_items, ItemType.Where, (x, bag) => new 
 150
 151    /// <summary>
 152    /// Gets the include expressions.
 153    /// </summary>
 40154    public IEnumerable<IncludeExpression<T>> IncludeExpressions => _items is null
 40155        ? Enumerable.Empty<IncludeExpression<T>>()
 296156        : new SpecSelectIterator<LambdaExpression, IncludeExpression<T>>(_items, ItemType.Include, (x, bag) => new Inclu
 157
 158    /// <summary>
 159    /// Gets the order expressions.
 160    /// </summary>
 94161    public IEnumerable<OrderExpression<T>> OrderExpressions => _items is null
 94162        ? Enumerable.Empty<OrderExpression<T>>()
 280163        : new SpecSelectIterator<Expression<Func<T, object?>>, OrderExpression<T>>(_items, ItemType.Order, (x, bag) => n
 164
 165    /// <summary>
 166    /// Gets the like expressions.
 167    /// </summary>
 22168    public IEnumerable<LikeExpression<T>> LikeExpressions => _items is null
 22169        ? Enumerable.Empty<LikeExpression<T>>()
 53170        : new SpecSelectIterator<SpecLike<T>, LikeExpression<T>>(_items, ItemType.Like, (x, bag) => new LikeExpression<T
 171
 172    /// <summary>
 173    /// Gets the include strings.
 174    /// </summary>
 12175    public IEnumerable<string> IncludeStrings => _items is null
 12176        ? Enumerable.Empty<string>()
 21177        : new SpecSelectIterator<string, string>(_items, ItemType.IncludeString, (x, bag) => x);
 178
 179    /// <summary>
 180    /// Gets the number of items to take.
 181    /// </summary>
 13182    public int Take => FirstOrDefault<SpecPaging>(ItemType.Paging)?.Take ?? -1;
 183
 184    /// <summary>
 185    /// Gets the number of items to skip.
 186    /// </summary>
 13187    public int Skip => FirstOrDefault<SpecPaging>(ItemType.Paging)?.Skip ?? -1;
 188
 189    /// <summary>
 190    /// Gets a value indicating whether IgnoreQueryFilters is applied.
 191    /// </summary>
 58192    public bool IgnoreQueryFilters => GetFlag(SpecFlags.IgnoreQueryFilters);
 193
 194    /// <summary>
 195    /// Gets a value indicating whether AsSplitQuery is applied.
 196    /// </summary>
 58197    public bool AsSplitQuery => GetFlag(SpecFlags.AsSplitQuery);
 198
 199    /// <summary>
 200    /// Gets a value indicating whether AsNoTracking is applied.
 201    /// </summary>
 69202    public bool AsNoTracking => GetFlag(SpecFlags.AsNoTracking);
 203
 204    /// <summary>
 205    /// Gets a value indicating whether AsNoTrackingWithIdentityResolution is applied.
 206    /// </summary>
 63207    public bool AsNoTrackingWithIdentityResolution => GetFlag(SpecFlags.AsNoTrackingWithIdentityResolution);
 208
 209    /// <summary>
 210    /// Gets a value indicating whether AsTracking is applied.
 211    /// </summary>
 63212    public bool AsTracking => GetFlag(SpecFlags.AsTracking);
 213
 214    /// <summary>
 215    /// Adds an item to the specification.
 216    /// </summary>
 217    /// <param name="type">The type of the item. It must be a positive number.</param>
 218    /// <param name="value">The object to be stored in the item.</param>
 219    /// <exception cref="ArgumentNullException">If value is null</exception>
 220    /// <exception cref="ArgumentOutOfRangeException">If type is zero or negative.</exception>
 221    public void Add(int type, object value)
 222    {
 21223        ArgumentNullException.ThrowIfNull(value);
 20224        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(type);
 225
 18226        AddInternal(type, value);
 18227    }
 228
 229    /// <summary>
 230    /// Adds or updates an item in the specification.
 231    /// </summary>
 232    /// <param name="type">The type of the item.</param>
 233    /// <param name="value">The object to be stored in the item.</param>
 234    /// <exception cref="ArgumentNullException">Thrown if value is null</exception>
 235    /// <exception cref="ArgumentOutOfRangeException">Thrown if type is zero or negative.</exception>
 236    public void AddOrUpdate(int type, object value)
 237    {
 11238        ArgumentNullException.ThrowIfNull(value);
 10239        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(type);
 240
 8241        AddOrUpdateInternal(type, value);
 8242    }
 243
 244    /// <summary>
 245    /// Gets the first item of the specified type or the default value if no item is found.
 246    /// </summary>
 247    /// <typeparam name="TObject">The type of the object stored in the item.</typeparam>
 248    /// <param name="type">The type of the item.</param>
 249    /// <returns>The first item of the specified type or the default value if no item is found.</returns>
 250    public TObject? FirstOrDefault<TObject>(int type)
 251    {
 252252        if (IsEmpty) return default;
 253
 1934254        foreach (var item in _items)
 255        {
 829256            if (item.Type == type && item.Reference is TObject reference)
 257            {
 112258                return reference;
 259            }
 260        }
 82261        return default;
 262    }
 263
 264    /// <summary>
 265    /// Gets the first item of the specified type.
 266    /// </summary>
 267    /// <typeparam name="TObject">The type of the object stored in the item.</typeparam>
 268    /// <param name="type">The type of the item.</param>
 269    /// <returns>The first item of the specified type.</returns>
 270    /// <exception cref="InvalidOperationException">Thrown if no matching item is found.</exception>
 271    public TObject First<TObject>(int type)
 272    {
 4273        if (IsEmpty) throw new InvalidOperationException("Specification contains no items");
 274
 11275        foreach (var item in _items)
 276        {
 4277            if (item.Type == type && item.Reference is TObject reference)
 278            {
 1279                return reference;
 280            }
 281        }
 1282        throw new InvalidOperationException("Specification contains no matching item");
 283    }
 284
 285    /// <summary>
 286    /// Gets all items of the specified type.
 287    /// </summary>
 288    /// <typeparam name="TObject">The type of the object stored in the item.</typeparam>
 289    /// <param name="type">The type of the items.</param>
 290    /// <returns>An enumerable of items of the specified type.</returns>
 3291    public IEnumerable<TObject> OfType<TObject>(int type) => _items is null
 3292        ? Enumerable.Empty<TObject>()
 3293        : new SpecIterator<TObject>(_items, type);
 294
 284295    internal ReadOnlySpan<SpecItem> Items => _items ?? ReadOnlySpan<SpecItem>.Empty;
 296
 297    [MemberNotNull(nameof(_items))]
 298    internal void AddInternal(int type, object value, int bag = 0)
 299    {
 575300        var newItem = new SpecItem
 575301        {
 575302            Type = type,
 575303            Reference = value,
 575304            Bag = bag
 575305        };
 306
 575307        if (IsEmpty)
 308        {
 309            // Specs with two items are very common, we'll optimize for that.
 227310            _items = new SpecItem[2];
 227311            _items[0] = newItem;
 312        }
 313        else
 314        {
 348315            var items = _items;
 316
 317            // We have a special case for Paging, we're storing it in the same item with Flags.
 348318            if (type == ItemType.Paging)
 319            {
 386320                for (var i = 0; i < items.Length; i++)
 321                {
 165322                    if (items[i].Type == ItemType.Paging)
 323                    {
 1324                        _items[i].Reference = newItem.Reference;
 1325                        return;
 326                    }
 327                }
 328            }
 329
 3442330            for (var i = 0; i < items.Length; i++)
 331            {
 1630332                if (items[i].Type == 0)
 333                {
 256334                    items[i] = newItem;
 256335                    return;
 336                }
 337            }
 338
 91339            var originalLength = items.Length;
 91340            var newArray = new SpecItem[originalLength + 4];
 91341            Array.Copy(items, newArray, originalLength);
 91342            newArray[originalLength] = newItem;
 91343            _items = newArray;
 344        }
 91345    }
 346    internal void AddOrUpdateInternal(int type, object value, int bag = 0)
 347    {
 80348        if (IsEmpty)
 349        {
 12350            AddInternal(type, value, bag);
 12351            return;
 352        }
 68353        var items = _items;
 572354        for (var i = 0; i < items.Length; i++)
 355        {
 221356            if (items[i].Type == type)
 357            {
 3358                _items[i].Reference = value;
 3359                _items[i].Bag = bag;
 3360                return;
 361            }
 362        }
 65363        AddInternal(type, value, bag);
 65364    }
 365    internal SpecItem[] GetCompiledItems()
 366    {
 90367        if (IsEmpty) return Array.Empty<SpecItem>();
 368
 78369        var compilableItemsCount = CountCompilableItems(_items);
 79370        if (compilableItemsCount == 0) return Array.Empty<SpecItem>();
 371
 77372        var compiledItems = GetCompiledItems(_items);
 373
 374        // If the count of compilable items is equal to the count of compiled items, we don't need to recompile.
 115375        if (compiledItems.Length == compilableItemsCount) return compiledItems;
 376
 39377        compiledItems = GenerateCompiledItems(_items, compilableItemsCount);
 39378        AddOrUpdateInternal(ItemType.Compiled, compiledItems);
 39379        return compiledItems;
 380
 381        static SpecItem[] GetCompiledItems(SpecItem[] items)
 382        {
 668383            foreach (var item in items)
 384            {
 276385                if (item.Type == ItemType.Compiled && item.Reference is SpecItem[] compiledItems)
 386                {
 38387                    return compiledItems;
 388                }
 389            }
 39390            return Array.Empty<SpecItem>();
 391        }
 392
 393        static int CountCompilableItems(SpecItem[] items)
 394        {
 78395            var count = 0;
 844396            foreach (var item in items)
 397            {
 344398                if (item.Type == ItemType.Where || item.Type == ItemType.Like || item.Type == ItemType.Order)
 151399                    count++;
 400            }
 78401            return count;
 402        }
 403
 404        static SpecItem[] GenerateCompiledItems(SpecItem[] items, int count)
 405        {
 39406            var compiledItems = new SpecItem[count];
 407
 408            // We want to place the items contiguously by type. Sorting is more expensive than looping per type.
 39409            var index = 0;
 298410            foreach (var item in items)
 411            {
 110412                if (item.Type == ItemType.Where && item.Reference is Expression<Func<T, bool>> expr)
 413                {
 20414                    compiledItems[index++] = new SpecItem
 20415                    {
 20416                        Type = item.Type,
 20417                        Reference = expr.Compile(),
 20418                        Bag = item.Bag
 20419                    };
 420                }
 421            }
 48422            if (index == count) return compiledItems;
 423
 244424            foreach (var item in items)
 425            {
 92426                if (item.Type == ItemType.Order && item.Reference is Expression<Func<T, object?>> expr)
 427                {
 22428                    compiledItems[index++] = new SpecItem
 22429                    {
 22430                        Type = item.Type,
 22431                        Reference = expr.Compile(),
 22432                        Bag = item.Bag
 22433                    };
 434                }
 435            }
 40436            if (index == count) return compiledItems;
 437
 20438            var likeStartIndex = index;
 168439            foreach (var item in items)
 440            {
 64441                if (item.Type == ItemType.Like && item.Reference is SpecLike<T> like)
 442                {
 31443                    compiledItems[index++] = new SpecItem
 31444                    {
 31445                        Type = item.Type,
 31446                        Reference = new SpecLikeCompiled<T>(like.KeySelector.Compile(), like.Pattern),
 31447                        Bag = item.Bag
 31448                    };
 449                }
 450            }
 451
 452            // Sort Like items by the group, so we do it only once and not repeatedly in the Like evaluator).
 32453            compiledItems.AsSpan()[likeStartIndex..count].Sort((x, y) => x.Bag.CompareTo(y.Bag));
 454
 20455            return compiledItems;
 456        }
 457    }
 458
 459    internal TObject GetOrCreate<TObject>(int type) where TObject : new()
 460    {
 69461        return FirstOrDefault<TObject>(type) ?? Create();
 462        TObject Create()
 463        {
 37464            var reference = new TObject();
 37465            AddInternal(type, reference);
 37466            return reference;
 467        }
 468    }
 469    internal bool GetFlag(SpecFlags flag)
 470    {
 332471        if (IsEmpty) return false;
 472
 2404473        foreach (var item in _items)
 474        {
 979475            if (item.Type == ItemType.Flags)
 476            {
 134477                return ((SpecFlags)item.Bag & flag) == flag;
 478            }
 479        }
 156480        return false;
 481    }
 482    internal void AddOrUpdateFlag(SpecFlags flag, bool value)
 483    {
 123484        if (IsEmpty)
 485        {
 59486            if (value)
 487            {
 26488                AddInternal(ItemType.Flags, null!, (int)flag);
 489            }
 59490            return;
 491        }
 492
 64493        var items = _items;
 354494        for (var i = 0; i < items.Length; i++)
 495        {
 173496            if (items[i].Type == ItemType.Flags)
 497            {
 60498                var newValue = value
 60499                    ? (SpecFlags)items[i].Bag | flag
 60500                    : (SpecFlags)items[i].Bag & ~flag;
 501
 60502                _items[i].Bag = (int)newValue;
 60503                return;
 504            }
 505        }
 506
 4507        if (value)
 508        {
 3509            AddInternal(ItemType.Flags, null!, (int)flag);
 510        }
 4511    }
 512}