Skip to content

Commit e657ac8

Browse files
committed
docs: Builder extension method example
1 parent f88f169 commit e657ac8

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

docs/extensions/create-specification-builder.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,70 @@ parent: Extensions
55
nav_order: 2
66
---
77

8-
How to create your own specification builder
8+
9+
# How to create your own specification builder
10+
How to create your own specification builder
11+
12+
## Example: Configure caching behaviour through specification builder extension method
13+
14+
In order to achieve this:
15+
16+
````csharp
17+
public class CustomerByNameWithStores : Specification<Customer>
18+
{
19+
public CustomerByNameWithStores(string name)
20+
{
21+
Query.Where(x => x.Name == name)
22+
.EnableCache(nameof(CustomerByNameWithStoresSpec), name)
23+
// Can only be called after .EnableCache()
24+
.WithTimeToLive(TimeSpan.FromHours(1))
25+
.Include(x => x.Stores);
26+
}
27+
}
28+
````
29+
30+
We can create a simple extension method on the specification builder:
31+
32+
````csharp
33+
public static class SpecificationBuilderExtensions
34+
{
35+
public static ISpecificationBuilder<T> WithTimeToLive<T>(this ISpecificationBuilder<T> @this, TimeSpan ttl)
36+
where T : class
37+
{
38+
@this.Specification.SetCacheTTL(ttl);
39+
return @this;
40+
}
41+
}
42+
````
43+
44+
This extension method can only be called when chained after `SpecificationBuilderExtensions.EnableCache`. This is because `EnableCache` returns `ICacheSpecificationBuilder<T>` which inherits from `ISpecificationBuilder<T>`.
45+
46+
```csharp
47+
// TODO: Repository example
48+
```
49+
50+
Finally, we need to take of some plumbing to implement both `` and ``. The class below uses `ConditionalWeakTable` to do the trick. An other solution is to create a base class that inherits from `Specification<T>`.
51+
52+
````csharp
53+
public static class SpecificationExtentions
54+
{
55+
private static readonly ConditionalWeakTable<object, CacheOptions> SpecificationCacheOptions = new();
56+
57+
public static void SetCacheTTL<T>(this ISpecification<T> spec, TimeSpan ttl)
58+
{
59+
SpecificationCacheOptions.AddOrUpdate(spec, new CacheOptions() { TTL = ttl });
60+
}
61+
62+
public static TimeSpan GetCacheTTL<T>(this ISpecification<T> spec)
63+
{
64+
var opts = SpecificationCacheOptions.GetOrCreateValue(spec);
65+
return opts?.TTL ?? TimeSpan.MaxValue;
66+
}
67+
68+
// ConditionalWeakTable need reference types; TimeSpan is a struct
69+
private class CacheOptions
70+
{
71+
public TimeSpan TTL { get; set; }
72+
}
73+
}
74+
````

docs/features/caching.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ public class CustomerByNameWithStoresSpec : Specification<Customer>, ISingleResu
2323
}
2424
```
2525

26-
The `.EnableCache` method takes in two parameters: the name of the specification and the parameters of the specification.
26+
The `.EnableCache` method takes in two parameters: the name of the specification and the parameters of the specification. It does not include any parameters to control how the cache should behave (e.g. absolute expiration date, expiration tokens, ...). However, one could create an extension method to the specification builder in order to add this information ([example](../extensions/create-specification-builder.md)).
2727

2828
Implementing caching will also require infrastructure such as a CachedRepository, an example of which is given in [the sample](https://github.com/ardalis/Specification/blob/2605202df4d8e40fe388732db6d8f7a3754fcc2b/sample/Ardalis.SampleApp.Infrastructure/Data/CachedCustomerRepository.cs#L13) on GitHub. The `EnableCache` method is used to inform the cache implementation that caching should be used, and to configure the `CacheKey` based on the arguments supplied.

0 commit comments

Comments
 (0)