Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions src/QuerySpecification/Builders/Builder_Cache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
namespace Pozitron.QuerySpecification;

public static partial class SpecificationBuilderExtensions
{
/// <summary>
/// Sets the cache key for the specification.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <param name="cacheKey">The cache key to be used.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T, TResult> WithCacheKey<T, TResult>(
this ISpecificationBuilder<T, TResult> builder,
string cacheKey) where T : class
{
WithCacheKey(builder, cacheKey, true);
return (SpecificationBuilder<T, TResult>)builder;
}

/// <summary>
/// Sets the cache key for the specification.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <param name="cacheKey">The cache key to be used.</param>
/// <param name="condition">The condition to evaluate.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T, TResult> WithCacheKey<T, TResult>(
this ISpecificationBuilder<T, TResult> builder,
string cacheKey,
bool condition) where T : class
{
if (condition)
{
builder.Specification.AddOrUpdateInternal(ItemType.CacheKey, cacheKey);
}

return builder;
}

/// <summary>
/// Sets the cache key for the specification.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <param name="cacheKey">The cache key to be used.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T> WithCacheKey<T>(
this ISpecificationBuilder<T> builder,
string cacheKey) where T : class
=> WithCacheKey(builder, cacheKey, true);

/// <summary>
/// Sets the cache key for the specification.
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <param name="builder">The specification builder.</param>
/// <param name="cacheKey">The cache key to be used.</param>
/// <param name="condition">The condition to evaluate.</param>
/// <returns>The updated specification builder.</returns>
public static ISpecificationBuilder<T> WithCacheKey<T>(
this ISpecificationBuilder<T> builder,
string cacheKey,
bool condition) where T : class
{
if (condition)
{
builder.Specification.AddOrUpdateInternal(ItemType.CacheKey, cacheKey);
}

return builder;
}
}
1 change: 1 addition & 0 deletions src/QuerySpecification/Internals/ItemType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
24 changes: 24 additions & 0 deletions src/QuerySpecification/Specification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ public virtual bool IsSatisfiedBy(T entity)
? Enumerable.Empty<string>()
: new SpecSelectIterator<string, string>(_items, ItemType.QueryTag, (x, bag) => x);

/// <summary>
/// Return whether or not a cache key is set.
/// </summary>
public bool HasCacheKey => Contains(ItemType.CacheKey);

/// <summary>
/// Gets the cache key.
/// </summary>
public string? CacheKey => FirstOrDefault<string>(ItemType.CacheKey);

/// <summary>
/// Gets the number of items to take.
/// </summary>
Expand Down Expand Up @@ -306,6 +316,20 @@ public IEnumerable<TObject> OfType<TObject>(int type) => _items is null

internal ReadOnlySpan<SpecItem> Items => _items ?? ReadOnlySpan<SpecItem>.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)
{
Expand Down
61 changes: 61 additions & 0 deletions tests/QuerySpecification.Tests/Builders/Builder_WithCacheKey.cs
Original file line number Diff line number Diff line change
@@ -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<Customer>();
var spec2 = new Specification<Customer, string>();

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<Customer>();
spec1.Query
.Where(x=> x.Id > 0)
.WithCacheKey(key, false);

var spec2 = new Specification<Customer, string>();
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<Customer>();
spec1.Query
.WithCacheKey(key);

var spec2 = new Specification<Customer, string>();
spec2.Query
.WithCacheKey(key);

spec1.CacheKey.Should().Be(key);
spec1.HasCacheKey.Should().BeTrue();

spec1.CacheKey.Should().Be(key);
spec2.HasCacheKey.Should().BeTrue();
}
}