Bodu.Financial.ExchangeRates.Caching.Sqlite
1.0.0
dotnet add package Bodu.Financial.ExchangeRates.Caching.Sqlite --version 1.0.0
NuGet\Install-Package Bodu.Financial.ExchangeRates.Caching.Sqlite -Version 1.0.0
<PackageReference Include="Bodu.Financial.ExchangeRates.Caching.Sqlite" Version="1.0.0" />
<PackageVersion Include="Bodu.Financial.ExchangeRates.Caching.Sqlite" Version="1.0.0" />
<PackageReference Include="Bodu.Financial.ExchangeRates.Caching.Sqlite" />
paket add Bodu.Financial.ExchangeRates.Caching.Sqlite --version 1.0.0
#r "nuget: Bodu.Financial.ExchangeRates.Caching.Sqlite, 1.0.0"
#:package Bodu.Financial.ExchangeRates.Caching.Sqlite@1.0.0
#addin nuget:?package=Bodu.Financial.ExchangeRates.Caching.Sqlite&version=1.0.0
#tool nuget:?package=Bodu.Financial.ExchangeRates.Caching.Sqlite&version=1.0.0
Bodu.Financial.ExchangeRates.Caching.Sqlite
API stability — Stable. The public API surface is committed; breaking changes are reserved for a major-version bump per SemVer.
A SQLite-backed persistent cache for Bodu.Financial exchange-rate providers.
For the full composition walkthrough (quickstart, stacking, aggregation, observability, troubleshooting) see the Caching and aggregating exchange rates guide; for a cross-host cache, see the
Bodu.Financial.ExchangeRates.Caching.Distributedbackend.
SqliteRateCache implements the IRateCache contract over a SQLite database, persisting one
provider's dated rates and fetch-coverage windows so they need not be re-fetched while fresh. It is behaviourally
identical to the in-memory and TOML caches in Bodu.Financial.ExchangeRates.Caching — the same freshness, merge,
coverage, and validation semantics — and is validated against the same shared RateCacheContractTests.
Storage
- A
ratestable keyed by(provider, from_code, to_code, obs_date), one row per dated observation (UPSERT on store). - A
coveragetable of(provider, from_code, to_code, start_date, end_date, fetched_at)allowing multiple fetch windows per pair. - Decimal rates are stored as invariant strings and all dates and instants as invariant ISO text (
yyyy-MM-ddfor dates, round-trip"O"for instants) so precision and scale round-trip losslessly. - The
ratestable carries an additiveobserved_at TEXT NULLcolumn holding the upstream fetch instant (ExchangeRate.FetchedAtUtc), distinct from thecached_atcache-write instant. A pre-existing database created before the column was added is migrated idempotently on open (ALTER TABLE ... ADD COLUMN, a no-op when already present); rows from before the migration, or whose source supplied no fetch instant, store and read backnull.
Behaviour
- Expiry is by caching duration: stale and semantically invalid rows are filtered on read and pruned on write; stale coverage windows are pruned when coverage is recorded, so the database self-cleans.
- The independent half-writes preserve the other half —
Storenever drops coverage, andRecordCoveragenever drops rows — whileStoreFetchedRange(the path theCachingRateProviderdecorator uses) rewrites both theratesandcoveragetables for the pair in one transaction, so a reader never observes coverage without its rows. An empty-but-fetched range still records its coverage window so it is not perpetually re-fetched. The write reports anRateCacheWriteStatus(Stored/Failed/Skipped). - Single-process best-effort: same-pair writes are serialized under a per-pair lock and run in a transaction. A storage
failure (
SqliteException/IOException) degrades to an empty read or skipped write rather than throwing.
Because the persisted observed_at is restored onto a served rate's ExchangeRate.FetchedAtUtc, a cache-served rate
reports its original upstream fetch instant (data age), distinct from the cache-write age surfaced through
RateLookupResult.Provenance (CachedAtUtc / Age). See the served-rate provenance notes in the
Bodu.Financial.ExchangeRates.Caching README.
Usage
var options = new SqliteRateCacheOptions { Provider = "RBA", DatabaseFilePath = "/var/cache/rba.db" };
using var cache = new SqliteRateCache(options);
IDatedRateProvider cached = new CachingRateProvider(rba, cache, new CachingRateOptions());
One cache instance serves every currency pair for its provider — the store is keyed by
(provider, from_code, to_code, obs_date), so a single SqliteRateCache holds AUD/USD, GBP/USD, and any
other pair the provider returns. There is never a cache per pair.
Several providers in one database (without DI)
Because provider is the leading key column, several single-provider caches can share one database file with no
collisions — each provider's series stays partitioned. Construct one cache per provider over the same
DatabaseFilePath and wrap each in its own CachingRateProvider:
using Bodu.Financial.ExchangeRates.Caching;
var options = new CachingRateOptions { DefaultExpiry = TimeSpan.FromHours(24) };
// One shared .db file, one cache per provider; each cache covers all of that provider's pairs.
using var rbaCache = new SqliteRateCache("RBA", "/var/cache/fx.db");
using var ofxCache = new SqliteRateCache("OFX", "/var/cache/fx.db");
IDatedRateProvider rba = new CachingRateProvider(rbaSource, rbaCache, options);
IDatedRateProvider ofx = new CachingRateProvider(ofxSource, ofxCache, options);
Each cache holds its own keep-alive connection and per-pair locks, so dispose every cache you create. To group several sources behind one entry point with fallback / averaging / per-pair routing, or to stack a fast in-memory tier in front of the SQLite tier, see the exchange-rate caching guide (Stacking providers and Grouping providers with the aggregator).
Through dependency injection
The package ships its own AddSqliteRateCache registration in the Bodu.Financial.ExchangeRates namespace; call it
once per provider, pointing each at the same file to share one database:
using Bodu.Financial;
using Bodu.Financial.ExchangeRates;
services.AddFinancialService()
.AddSqliteRateCache("RBA", configure: o => o.DatabaseFilePath = "/var/cache/fx.db")
.AddSqliteRateCache("OFX", configure: o => o.DatabaseFilePath = "/var/cache/fx.db");
A SqliteRateCache holds one keep-alive connection open for its lifetime so a shared in-memory database
(Mode=Memory;Cache=Shared) survives between operations; dispose the cache to release it.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Bodu.Core (>= 1.0.0)
- Bodu.Financial (>= 1.0.0)
- Bodu.Financial.DependencyInjection (>= 1.0.0)
- Bodu.Financial.ExchangeRates.Caching (>= 1.0.0)
- Microsoft.Data.Sqlite (>= 10.0.12)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.12)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.12)
- Microsoft.Extensions.Options (>= 10.0.12)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.12)
-
net8.0
- Bodu.Core (>= 1.0.0)
- Bodu.Financial (>= 1.0.0)
- Bodu.Financial.DependencyInjection (>= 1.0.0)
- Bodu.Financial.ExchangeRates.Caching (>= 1.0.0)
- Microsoft.Data.Sqlite (>= 8.0.31)
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.