From 73f2a45152d4cad842872762f82e8686e65c4fe4 Mon Sep 17 00:00:00 2001 From: Fati Iseni Date: Wed, 4 Jun 2025 10:45:29 +0200 Subject: [PATCH] Add WithCacheKey --- .../Builders/Builder_Cache.cs | 75 +++++++++++++++++++ src/QuerySpecification/Internals/ItemType.cs | 1 + src/QuerySpecification/Specification.cs | 24 ++++++ .../Builders/Builder_WithCacheKey.cs | 61 +++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 src/QuerySpecification/Builders/Builder_Cache.cs create mode 100644 tests/QuerySpecification.Tests/Builders/Builder_WithCacheKey.cs diff --git a/src/QuerySpecification/Builders/Builder_Cache.cs b/src/QuerySpecification/Builders/Builder_Cache.cs new file mode 100644 index 00000000..94297dc9 --- /dev/null +++ b/src/QuerySpecification/Builders/Builder_Cache.cs @@ -0,0 +1,75 @@ +namespace Pozitron.QuerySpecification; + +public static partial class SpecificationBuilderExtensions +{ + /// + /// Sets the cache key for the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The cache key to be used. + /// The updated specification builder. + public static ISpecificationBuilder WithCacheKey( + this ISpecificationBuilder builder, + string cacheKey) where T : class + { + WithCacheKey(builder, cacheKey, true); + return (SpecificationBuilder)builder; + } + + /// + /// Sets the cache key for the specification. + /// + /// The type of the entity. + /// The type of the result. + /// The specification builder. + /// The cache key to be used. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder WithCacheKey( + this ISpecificationBuilder builder, + string cacheKey, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AddOrUpdateInternal(ItemType.CacheKey, cacheKey); + } + + return builder; + } + + /// + /// Sets the cache key for the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The cache key to be used. + /// The updated specification builder. + public static ISpecificationBuilder WithCacheKey( + this ISpecificationBuilder builder, + string cacheKey) where T : class + => WithCacheKey(builder, cacheKey, true); + + /// + /// Sets the cache key for the specification. + /// + /// The type of the entity. + /// The specification builder. + /// The cache key to be used. + /// The condition to evaluate. + /// The updated specification builder. + public static ISpecificationBuilder WithCacheKey( + this ISpecificationBuilder builder, + string cacheKey, + bool condition) where T : class + { + if (condition) + { + builder.Specification.AddOrUpdateInternal(ItemType.CacheKey, cacheKey); + } + + return builder; + } +} diff --git a/src/QuerySpecification/Internals/ItemType.cs b/src/QuerySpecification/Internals/ItemType.cs index 07345ac5..69c9fe16 100644 --- a/src/QuerySpecification/Internals/ItemType.cs +++ b/src/QuerySpecification/Internals/ItemType.cs @@ -15,4 +15,5 @@ internal static class ItemType public const int Flags = -8; // Stored in the bag public const int QueryTag = -9; + public const int CacheKey = -10; } diff --git a/src/QuerySpecification/Specification.cs b/src/QuerySpecification/Specification.cs index 25db04a9..2a1e786a 100644 --- a/src/QuerySpecification/Specification.cs +++ b/src/QuerySpecification/Specification.cs @@ -183,6 +183,16 @@ public virtual bool IsSatisfiedBy(T entity) ? Enumerable.Empty() : new SpecSelectIterator(_items, ItemType.QueryTag, (x, bag) => x); + /// + /// Return whether or not a cache key is set. + /// + public bool HasCacheKey => Contains(ItemType.CacheKey); + + /// + /// Gets the cache key. + /// + public string? CacheKey => FirstOrDefault(ItemType.CacheKey); + /// /// Gets the number of items to take. /// @@ -306,6 +316,20 @@ public IEnumerable OfType(int type) => _items is null internal ReadOnlySpan Items => _items ?? ReadOnlySpan.Empty; + internal bool Contains(int type) + { + if (IsEmpty) return false; + + foreach (var item in _items) + { + if (item.Type == type) + { + return true; + } + } + return false; + } + [MemberNotNull(nameof(_items))] internal void AddInternal(int type, object value, int bag = 0) { diff --git a/tests/QuerySpecification.Tests/Builders/Builder_WithCacheKey.cs b/tests/QuerySpecification.Tests/Builders/Builder_WithCacheKey.cs new file mode 100644 index 00000000..cef83fcf --- /dev/null +++ b/tests/QuerySpecification.Tests/Builders/Builder_WithCacheKey.cs @@ -0,0 +1,61 @@ +namespace Tests.Builders; + +public class Builder_WithCacheKey +{ + public record Customer(int Id, string Name); + + [Fact] + public void DoesNothing_GivenNoWithCacheKey() + { + var spec1 = new Specification(); + var spec2 = new Specification(); + + spec1.CacheKey.Should().BeNull(); + spec1.HasCacheKey.Should().BeFalse(); + + spec2.CacheKey.Should().BeNull(); + spec2.HasCacheKey.Should().BeFalse(); + } + + [Fact] + public void DoesNothing_GivenWithCacheKeyWithFalseCondition() + { + var key = "someKey"; + + var spec1 = new Specification(); + spec1.Query + .Where(x=> x.Id > 0) + .WithCacheKey(key, false); + + var spec2 = new Specification(); + spec2.Query + .Where(x=> x.Id > 0) + .WithCacheKey(key, false); + + spec1.CacheKey.Should().BeNull(); + spec1.HasCacheKey.Should().BeFalse(); + + spec2.CacheKey.Should().BeNull(); + spec2.HasCacheKey.Should().BeFalse(); + } + + [Fact] + public void SetsCacheKey_GivenWithCacheKey() + { + var key = "someKey"; + + var spec1 = new Specification(); + spec1.Query + .WithCacheKey(key); + + var spec2 = new Specification(); + spec2.Query + .WithCacheKey(key); + + spec1.CacheKey.Should().Be(key); + spec1.HasCacheKey.Should().BeTrue(); + + spec1.CacheKey.Should().Be(key); + spec2.HasCacheKey.Should().BeTrue(); + } +}