CaeriusNet 11.1.0
dotnet add package CaeriusNet --version 11.1.0
NuGet\Install-Package CaeriusNet -Version 11.1.0
<PackageReference Include="CaeriusNet" Version="11.1.0" />
<PackageVersion Include="CaeriusNet" Version="11.1.0" />
<PackageReference Include="CaeriusNet" />
paket add CaeriusNet --version 11.1.0
#r "nuget: CaeriusNet, 11.1.0"
#:package CaeriusNet@11.1.0
#addin nuget:?package=CaeriusNet&version=11.1.0
#tool nuget:?package=CaeriusNet&version=11.1.0
CaeriusNet
<p align="center"> <a href="https://www.nuget.org/packages/CaeriusNet"><img src="https://img.shields.io/nuget/v/CaeriusNet?style=flat&logo=nuget" alt="NuGet version"></a> <a href="https://www.nuget.org/packages/CaeriusNet"><img src="https://img.shields.io/nuget/dt/CaeriusNet?style=flat" alt="NuGet downloads"></a> <img src="https://img.shields.io/badge/.NET%2010-512BD4.svg?style=flat&logo=dotnet&logoColor=white" alt=".NET 10"> <img src="https://img.shields.io/badge/C%23%2014-%23239120.svg?style=flat&logo=csharp&logoColor=white" alt="C# 14"> <img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat" alt="MIT License"> </p>
High-performance micro-ORM for C# 14 / .NET 10 that executes SQL Server Stored Procedures, maps DTOs at compile-time, passes Table-Valued Parameters, and caches results — all in a single package, zero reflection at runtime.
Installation
dotnet add package CaeriusNet
AutoContracts is included in CaeriusNet. Set the MSBuild properties when you want build-time SQL Server contract discovery; there is no additional install step.
Prerequisites
- .NET 10 or later
- SQL Server 2019 or later
Quick Start
1. Configure (Program.cs)
// Standard
CaeriusNetBuilder.Create(services)
.WithSqlServer("Server=.;Database=MyDb;Trusted_Connection=True;")
.Build();
// .NET Aspire
CaeriusNetBuilder.Create(builder)
.WithAspireSqlServer("CaeriusNet")
.WithAspireRedis()
.Build();
2. Define a DTO
Source-generated (recommended):
[GenerateDto]
public sealed partial record ProductDto(int Id, string Name, decimal Price);
// Generates: ISpMapper<ProductDto> with MapFromDataReader at compile-time
Manual:
public sealed record ProductDto(int Id, string Name, decimal Price) : ISpMapper<ProductDto>
{
public static ProductDto MapFromDataReader(SqlDataReader reader)
=> new(reader.GetInt32(0), reader.GetString(1), reader.GetDecimal(2));
}
3. Execute a Stored Procedure
var sp = new StoredProcedureParametersBuilder("dbo", "sp_GetProducts", ResultSetCapacity: 1)
.AddParameter("CategoryId", categoryId, SqlDbType.Int)
.Build();
ReadOnlyCollection<ProductDto> products =
await dbContext.QueryAsReadOnlyCollectionAsync<ProductDto>(sp, ct);
Table-Valued Parameters (TVP)
Source-generated:
[GenerateTvp(Schema = "dbo", TvpName = "tvp_int")]
public sealed partial record IntTvp(int Value);
Manual:
public sealed record OrderLineDto(int ProductId, int Qty) : ITvpMapper<OrderLineDto>
{
public static string TvpTypeName => "dbo.tvp_OrderLine";
public IEnumerable<SqlDataRecord> MapAsSqlDataRecords(IEnumerable<OrderLineDto> items)
{
var meta = new[] { new SqlMetaData("ProductId", SqlDbType.Int), new SqlMetaData("Qty", SqlDbType.Int) };
foreach (var item in items)
{
var record = new SqlDataRecord(meta);
record.SetInt32(0, item.ProductId);
record.SetInt32(1, item.Qty);
yield return record;
}
}
}
Usage:
var sp = new StoredProcedureParametersBuilder("dbo", "sp_BulkInsert", ResultSetCapacity: 1)
.AddTvpParameter("OrderLines", orderLines)
.Build();
int rows = await dbContext.ExecuteNonQueryAsync(sp, ct);
AutoContracts
AutoContracts validates stored procedure contracts from SQL Server metadata during normal builds. Add CaeriusNet to the project that owns your data-access code, then use MSBuild properties to pull or verify the manifest.
<PropertyGroup>
<CaeriusContractsMode>Pull</CaeriusContractsMode>
<CaeriusContractsConnectionName>DefaultConnection</CaeriusContractsConnectionName>
</PropertyGroup>
dotnet build
Pull creates or refreshes caerius.contracts.json. Commit that file with your code. In CI, switch to Verify so the build fails if SQL Server metadata drifts from the committed manifest.
Caching
var sp = new StoredProcedureParametersBuilder("dbo", "sp_GetProducts", ResultSetCapacity: 2)
.AddParameter("CategoryId", categoryId, SqlDbType.Int)
.AddFrozenCache("products:all") // immutable, process-lifetime
// .AddInMemoryCache("products:all", TimeSpan.FromMinutes(5))
// .AddRedisCache("products:all", TimeSpan.FromMinutes(5))
.Build();
Cache invalidation
Inject the ICaeriusNetCache façade to invalidate entries from any service:
public sealed class ProductsService(ICaeriusNetCache cache)
{
public async ValueTask InvalidateProductAsync(int id, CancellationToken ct)
{
await cache.RemoveAsync($"products:{id}", ct); // all tiers
await cache.RemoveAsync("products:all", CacheType.Frozen, ct);
// await cache.ClearAsync(CacheType.InMemory, ct); // dangerous; use sparingly
}
}
ClearAsync(CacheType.Redis) intentionally throws NotSupportedException — clearing a shared
distributed cache from a single service is almost never what you want.
To bound the in-memory tier, configure it explicitly at startup:
CaeriusNetBuilder.Create(services)
.WithSqlServer(connectionString)
.WithInMemoryCacheOptions(new MemoryCacheOptions { SizeLimit = 50_000 })
.Build();
When SizeLimit is set, every cached entry is sized as 1 so the limit acts as a maximum entry
count.
Transactions
Stored-procedure transactions reuse a single SqlConnection for the whole scope and attach the
underlying SqlTransaction to every command. Caching is bypassed inside a transaction.
await using var tx = await dbContext.BeginTransactionAsync(IsolationLevel.ReadCommitted, ct);
var debit = new StoredProcedureParametersBuilder("dbo", "sp_DebitAccount", ResultSetCapacity: 2)
.AddParameter("AccountId", fromId, SqlDbType.Int)
.AddParameter("Amount", amount, SqlDbType.Decimal)
.Build();
var credit = new StoredProcedureParametersBuilder("dbo", "sp_CreditAccount", ResultSetCapacity: 2)
.AddParameter("AccountId", toId, SqlDbType.Int)
.AddParameter("Amount", amount, SqlDbType.Decimal)
.Build();
await tx.ExecuteNonQueryAsync(debit, ct);
await tx.ExecuteNonQueryAsync(credit, ct);
await tx.CommitAsync(ct); // omit -> auto-rollback on dispose
Design constraints (enforced at runtime):
| Rule | Behavior |
|---|---|
| State machine | Active → Committed / RolledBack / Poisoned |
| Single in-flight command | Concurrent commands throw InvalidOperationException |
| Cache bypass | No reads from cache, no writes to cache inside a transaction |
| Poison state | A failing command poisons the scope — only RollbackAsync/DisposeAsync remain valid |
| Auto-rollback | If CommitAsync is never called, the transaction rolls back on dispose |
| No nesting | BeginTransactionAsync on an active transaction throws NotSupportedException |
Write Operations
var sp = new StoredProcedureParametersBuilder("dbo", "sp_CreateProduct", ResultSetCapacity: 2)
.AddParameter("Name", name, SqlDbType.NVarChar)
.AddParameter("Price", price, SqlDbType.Decimal)
.Build();
int rows = await dbContext.ExecuteNonQueryAsync(sp, ct);
// Or retrieve a scalar return value
int newId = await dbContext.ExecuteScalarAsync<int>(sp, ct);
Multi-Result Sets
var sp = new StoredProcedureParametersBuilder("dbo", "sp_GetDashboard", ResultSetCapacity: 0).Build();
(IEnumerable<ProductDto> products, IEnumerable<CategoryDto> categories) =
await dbContext.QueryMultipleIEnumerableAsync<ProductDto, CategoryDto>(sp, ct);
Supported up to 5 result sets: QueryMultipleIEnumerableAsync<T1,T2> through
QueryMultipleIEnumerableAsync<T1,T2,T3,T4,T5>. The multi-result APIs return ValueTask
to match the rest of the async execution API.
Available Query Methods
| Method | Returns |
|---|---|
FirstQueryAsync<T> |
T? (first row or null) |
QueryAsReadOnlyCollectionAsync<T> |
ReadOnlyCollection<T> |
QueryAsIEnumerableAsync<T> |
IEnumerable<T> |
QueryAsImmutableArrayAsync<T> |
ImmutableArray<T> |
ExecuteNonQueryAsync |
int (rows affected) |
ExecuteAsync |
void |
ExecuteScalarAsync<T> |
T |
Supported DTO Types
The source generator maps C# types to SQL Server types and generates the correct SqlDataReader calls.
| C# Type | SQL Server Type | Reader Method |
|---|---|---|
bool |
bit |
GetBoolean |
byte |
tinyint |
GetByte |
short |
smallint |
GetInt16 |
int |
int |
GetInt32 |
long |
bigint |
GetInt64 |
decimal |
decimal |
GetDecimal |
float |
real |
GetFloat |
Half |
real |
GetFloat (cast) |
double |
float |
GetDouble |
string |
nvarchar |
GetString |
char |
nchar |
GetString (cast) |
DateTime |
datetime2 |
GetDateTime |
DateOnly |
date |
DateOnly.FromDateTime |
TimeOnly |
time |
TimeOnly.FromTimeSpan |
DateTimeOffset |
datetimeoffset |
GetDateTimeOffset |
TimeSpan |
time |
GetTimeSpan |
Guid |
uniqueidentifier |
GetGuid |
byte[] |
varbinary |
GetFieldValue<byte[]> |
| Enums | (underlying type) | (underlying reader) |
Types without a native mapping fall back to sql_variant with compile-time warning CAERIUS005.
Observability
CaeriusNet uses [LoggerMessage] source-generated structured logging with zero-allocation event methods.
| Event Range | Category |
|---|---|
| 1xxx | In-Memory cache operations |
| 2xxx | Frozen cache operations |
| 3xxx | Redis cache operations |
| 4xxx | Database / stored procedure execution |
| 5xxx | Command execution lifecycle |
All events include structured properties (cache key, schema, procedure name, elapsed time, row count) for integration
with OpenTelemetry, Seq, Application Insights, or any ILogger sink.
Tracing & Metrics (OpenTelemetry / Aspire)
CaeriusNet also publishes OpenTelemetry-compatible traces and metrics through the BCL primitives — no
OpenTelemetry SDK package is added to the library. Consumers (typically an Aspire ServiceDefaults project) opt-in
by registering the source/meter named CaeriusNet:
using CaeriusNet.Telemetry;
builder.Services.AddOpenTelemetry()
.WithTracing(t => t.AddSource(CaeriusDiagnostics.SourceName))
.WithMetrics(m => m.AddMeter(CaeriusDiagnostics.SourceName));
Spans are ActivityKind.Client, named SP {schema}.{procedure}, and tagged with the OpenTelemetry DB semantic
conventions (db.system = mssql, db.operation, db.statement) plus library-specific attributes:
caerius.sp.schema,caerius.sp.name,caerius.sp.command,caerius.sp.parameters(names only by default;CaptureParameterValuesdefaults tofalse— set it totrueonCaeriusTelemetryOptionsto also capture@name=valuepairs, but keep it disabled in production to avoid leaking PII or secrets into telemetry back-ends)caerius.tvp.used(true/false) andcaerius.tvp.type_namewhen a Table-Valued Parameter is attachedcaerius.resultset.multiandcaerius.resultset.expected_count(1 by default, 2/3/4/5 for the multi-RS overloads)caerius.cache.tier/caerius.cache.hit(set on the active span when a cache lookup occurs)caerius.tx = truewhen the call runs inside anICaeriusNetTransactioncaerius.rows_returned/caerius.rows_affectedon success
Metrics exposed by the CaeriusNet meter:
caerius.sp.duration(Histogram, ms)caerius.sp.executions(Counter)caerius.sp.errors(Counter)caerius.cache.lookups(Counter, tagged withcaerius.cache.tierandcaerius.cache.hit)
When a cache hit short-circuits the SQL call, no DB span is created — only caerius.cache.lookups{hit=true} is
emitted, so the Aspire dashboard accurately reflects that the database was not contacted.
Documentation
Full documentation, samples, and API reference: https://caerius.net
Source code & releases: https://github.com/CaeriusNET/CaeriusNet
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Aspire.Microsoft.Data.SqlClient (>= 13.3.2)
- Aspire.StackExchange.Redis.DistributedCaching (>= 13.3.2)
- Microsoft.Data.SqlClient (>= 7.0.1)
- Microsoft.Extensions.Caching.Memory (>= 10.0.8)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 10.0.8)
- Microsoft.Extensions.Configuration.Json (>= 10.0.8)
- Microsoft.Extensions.DependencyInjection (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 11.1.0 | 0 | 5/15/2026 |
| 11.0.3 | 107 | 4/28/2026 |
| 11.0.2 | 101 | 4/26/2026 |
| 11.0.1 | 99 | 4/21/2026 |
| 11.0.0 | 93 | 4/20/2026 |
| 10.3.0 | 95 | 4/19/2026 |
| 10.2.1 | 94 | 4/19/2026 |
| 10.1.3 | 122 | 2/20/2026 |
| 10.1.2 | 273 | 1/22/2026 |
| 10.1.0 | 260 | 12/19/2025 |
| 10.0.1 | 209 | 12/5/2025 |
| 10.0.0.8-alpha | 177 | 11/2/2025 |
| 10.0.0.7-alpha | 177 | 10/29/2025 |
| 10.0.0.6-alpha | 171 | 10/29/2025 |
| 10.0.0.5-alpha | 182 | 10/29/2025 |
| 10.0.0.4-alpha | 178 | 10/28/2025 |
| 10.0.0.3-alpha | 168 | 10/28/2025 |
| 10.0.0.2-alpha | 171 | 10/27/2025 |
| 10.0.0.1-alpha | 119 | 10/25/2025 |
| 10.0.0 | 320 | 11/12/2025 |
See https://github.com/CaeriusNET/CaeriusNet/releases for release notes.