Table of Contents

RateCacheBase<TOptions> Class

Definition

Namespace
Bodu.Financial.ExchangeRates.Caching
Assembly
Bodu.Financial.ExchangeRates.Caching.dll
Package
Bodu.Financial.ExchangeRates.Caching 1.0.0
Source
RateCacheBase{T}.cs

Provides the storage-agnostic mechanism for an IRateCache: read-time freshness filtering, write-time merge-and-prune, and the recording and pruning of coverage windows for a single provider. Derived types implement only the persistence of a Bodu.Financial.ExchangeRates.Caching.CachePairState; this base prescribes no physical storage structure.

public abstract class RateCacheBase<TOptions> : IRateCache where TOptions : RateCacheOptions

Type Parameters

TOptions

The options type carrying the bound provider and any storage settings.

Inheritance
RateCacheBase<TOptions>
Implements
Derived
Inherited Members
Extension Methods

Remarks

ReadState(CurrencyPair) returns the raw stored rows and coverage windows without filtering; this base applies the freshness policy in GetRates(CurrencyPair, TimeSpan, DateTimeOffset) and GetCoverage(CurrencyPair, TimeSpan, DateTimeOffset), prunes stale rows in Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset), and prunes stale coverage windows in RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset), so the backing store self-cleans on every write.

Rate rows and coverage windows are independent halves of the per-pair state: a write through one path preserves the other half so that recording coverage never drops rows and storing rows never drops coverage. The read-modify-write sequences in Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset), RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset), and StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset) run under a per-pair lock so concurrent writes to the same pair cannot interleave and lose either half. The lock is process-local; file caches remain best-effort across processes, where the atomic temp-and-move write keeps each file internally consistent.

The freshness, validity, merge, and coverage rules are not implemented here: they are delegated to the shared RateCacheRules so this base and the SQLite and distributed backends apply one authoritative policy. This base contributes only the per-pair locking and the read-modify-write sequencing over a Bodu.Financial.ExchangeRates.Caching.CachePairState.

Constructors

RateCacheBase(TOptions)

Initializes a new instance of the RateCacheBase<TOptions> class.

protected RateCacheBase(TOptions options)

Parameters

options TOptions

The options carrying the bound provider and any storage settings.

Exceptions

ArgumentNullException

Thrown when options is null.

ArgumentException

Thrown when options fails validation.

Properties

Options

Gets the validated options the cache was constructed with.

protected TOptions Options { get; }

Property Value

TOptions

The cache options.

Provider

Gets the name of the provider this cache stores rates for.

public string Provider { get; }

Property Value

string

The provider identifier the cache is bound to.

Methods

GetCoverage(CurrencyPair, TimeSpan, DateTimeOffset)

Returns the union of the still-fresh coverage windows recorded for the supplied pair, evaluated against asOf.

public DateRangeCoverage GetCoverage(CurrencyPair pair, TimeSpan duration, DateTimeOffset asOf)

Parameters

pair CurrencyPair

The currency pair.

duration TimeSpan

The duration a recorded coverage window remains fresh after it was fetched.

asOf DateTimeOffset

The instant against which coverage freshness is evaluated.

Returns

DateRangeCoverage

A DateRangeCoverage describing the days known to have been fetched and still fresh; empty when no fresh coverage exists.

Remarks

Coverage answers which days were actually fetched, not merely which days have a cached rate. A range lookup should be served from the cache only when this coverage Contains(DateOnly, DateOnly) the whole requested window, so an interior day that was never fetched forces a refetch rather than being served from a sparse set of rows.

GetRates(CurrencyPair, TimeSpan, DateTimeOffset)

Returns the fresh cached rates for the supplied pair, evaluated against asOf.

public IReadOnlyList<CachedRate> GetRates(CurrencyPair pair, TimeSpan duration, DateTimeOffset asOf)

Parameters

pair CurrencyPair

The currency pair.

duration TimeSpan

The duration a cached rate remains fresh after it was cached.

asOf DateTimeOffset

The instant against which freshness is evaluated.

Returns

IReadOnlyList<CachedRate>

The fresh cached rates ordered by date, or an empty list when none are fresh or available.

RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset)

Records that the inclusive range start..end was fetched for the supplied pair, stamping the window at asOf and pruning windows that are no longer fresh under duration.

public void RecordCoverage(CurrencyPair pair, DateOnly start, DateOnly end, TimeSpan duration, DateTimeOffset asOf)

Parameters

pair CurrencyPair

The currency pair.

start DateOnly

The inclusive first date of the fetched range.

end DateOnly

The inclusive last date of the fetched range.

duration TimeSpan

The duration a recorded coverage window remains fresh after it was fetched.

asOf DateTimeOffset

The instant the fetched window is stamped with and against which stale windows are pruned.

Remarks

This is a low-level building block that records the coverage half only, with no rows. It exists for the cache's own composition and is not the path a provider fetch should use: recording coverage here and writing rows through a separate Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset) risks persisting coverage a backend later serves as a false hit. A range fetch must instead use StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset), which writes both halves atomically.

Exceptions

ArgumentOutOfRangeException

Thrown when start is later than end.

Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset)

Stores rates for the supplied pair, merging with any existing entry so the most recently cached rate wins per date, and pruning rows that are no longer fresh under duration.

public void Store(CurrencyPair pair, IReadOnlyList<CachedRate> rates, TimeSpan duration, DateTimeOffset asOf)

Parameters

pair CurrencyPair

The currency pair.

rates IReadOnlyList<CachedRate>

The rates to store.

duration TimeSpan

The duration a cached rate remains fresh after it was cached.

asOf DateTimeOffset

The instant against which stale rows are pruned.

Remarks

This stores rate rows only and records no coverage window, so it is the path a single-date miss caches its resolved row through. Because no coverage is recorded, a later range lookup that spans those dates still refetches rather than serving from these rows - by design, since only a range fetch (through StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset)) establishes the contiguous coverage a range serve requires.

StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset)

Atomically merges rows into the pair's cached rows and records the inclusive range start..end as covered, persisting both halves together or neither.

public RateCacheWriteStatus StoreFetchedRange(CurrencyPair pair, IReadOnlyList<CachedRate> rows, DateOnly start, DateOnly end, TimeSpan duration, DateTimeOffset asOf)

Parameters

pair CurrencyPair

The currency pair.

rows IReadOnlyList<CachedRate>

The rows fetched for the range, which may be empty when the fetch returned no observation.

start DateOnly

The inclusive first date of the fetched range.

end DateOnly

The inclusive last date of the fetched range.

duration TimeSpan

The duration a cached row and a recorded coverage window remain fresh after they were cached or fetched.

asOf DateTimeOffset

The instant newly cached rows and the fetched window are stamped with and against which stale rows and windows are pruned.

Returns

RateCacheWriteStatus

Stored when both halves were persisted; Failed when a storage error was swallowed and nothing was persisted; Skipped for a cache that intentionally stores nothing.

Remarks

The coverage window is recorded even when rows is empty: a successful fetch that returned no observation (a weekend, a holiday, a true gap) must still mark the window covered so a later lookup of the same window is served rather than refetched.

The merge follows the same most-recent-per-date rule as Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset) and the same window pruning as RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset). The write is atomic per pair - under the per-pair lock for the in-memory and file caches, in one transaction for SQLite, and as one read-modify-write of the per-pair blob for a distributed cache - so a reader never observes coverage without its rows. As with the other write paths, a swallowed storage error degrades to Failed rather than throwing; argument validation still throws.

Exceptions

ArgumentNullException

Thrown when rows is null.

ArgumentOutOfRangeException

Thrown when start is later than end.

Applies to

ProductVersions
.NET8, 10