CosmoSQLClient is a unified .NET 10 database driver for MSSQL, PostgreSQL, MySQL, and SQLite built on System.IO.Pipelines (high-performance async I/O). It implements the full wire protocols from scratch β TDS 7.4 for SQL Server, PostgreSQL wire protocol v3, MySQL wire protocol v10, and the native SQLite3 file format β without delegating to ADO.NET for the network layer. All four backends share a single ISqlDatabase interface, giving you a familiar, SqlClient-compatible API that works identically regardless of the underlying engine.
- π Advanced Features
- Standard ADO.NET API
- Feature Matrix
- Installation
- Quick Start
- Connection Pooling
- Stored Procedures (MSSQL)
- Named Instances (MSSQL)
- Integrated Security & NTLMv2
- Transactions
- SqlDataTable & Output Formats
- β‘ Benchmark Results
- π¦ Package Size vs Microsoft.Data.SqlClient
- Architecture
- Connection String Reference
- SqlValue Type Mapping
- Related Projects
CosmoSQLClient provides a suite of advanced features designed for high-throughput, low-latency applications. These are accessible via the .Advanced property on any connection or pool.
await using var conn = await MsSqlConnection.OpenAsync(connStr);
// Use Advanced features
await foreach (var row in conn.Advanced.QueryStreamAsync("...")) { ... }No other .NET SQL library has this.
QueryJsonStreamAsync()delivers each complete JSON object the instant its closing}arrives β without ever buffering the full result array.
SQL Server's FOR JSON PATH fragments output at ~2033-character row boundaries that do not align with JSON object boundaries. QueryJsonStreamAsync() uses JsonChunkAssembler β backed by Utf8JsonReader + JsonReaderState β to detect exact {...} boundaries across arbitrary chunk splits, yielding each complete JSON object as a JsonElement the moment it is fully received.
Untyped streaming β iterate JsonElement directly:
await foreach (var elem in conn.Advanced.QueryJsonStreamAsync(
"SELECT Id, Name, Price FROM Products FOR JSON PATH"))
{
var id = elem.GetProperty("Id").GetInt32();
var name = elem.GetProperty("Name").GetString();
Console.WriteLine($"{id}: {name}");
}Rows are yielded as they arrive from the socket. This is perfect for reactive processing or pushing data directly to a web socket (NDJSON).
await foreach (var row in conn.Advanced.QueryStreamAsync(
"SELECT * FROM LargeTable"))
{
// Process one row at a time β the full result set is never buffered
var id = row["Id"].AsInt32();
}Unlike ADO.NET which uses classes for almost everything, CosmoSQLClient uses readonly record struct for SqlValue, SqlParameter, and SqlRow. This significantly reduces heap allocations and Garbage Collection overhead in high-concurrency scenarios.
- TLS Session Resumption: For SQL Server, the pool reuses TLS session tickets. This reduces "cold connect" time from ~17ms to ~4ms for new physical connections.
- Pool Pre-warming: Call
pool.WarmUpAsync()during application startup so that the very first database request is instant.
The SqlRowDecoder uses compiled LINQ Expressions to map database rows to POCO classes. It is faster than standard reflection-based mappers, approaching the speed of handwritten code.
For easy migration from Microsoft.Data.SqlClient, Npgsql, or MySqlConnector, all CosmoSQLClient providers implement the standard System.Data.Common base classes. This makes it a drop-in replacement for libraries like Dapper and Entity Framework Core.
// Standard ADO.NET usage
DbConnection conn = new MsSqlConnection(connStr);
await conn.OpenAsync();
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT Name FROM Products WHERE Id = @id";
cmd.Parameters.Add(new MsSqlParameter("id", 123));
// Works with Dapper!
var products = await conn.QueryAsync<Product>("SELECT * FROM Products");| Feature | MSSQL | PostgreSQL | MySQL | SQLite |
|---|---|---|---|---|
| Wire protocol | TDS 7.4 | v3 | v10 | sqlite3 |
| Connection pooling | β | β | β | β |
| Transactions | β | β | β | β |
| Parameterized queries | β | β | β | β |
| TLS/SSL | β | β | β | β |
| TrustServerCertificate | β | β | β | β |
| SCRAM-SHA-256 auth | β | β | β | β |
| NTLM / Windows auth | β | β | β | β |
Named instance resolution (SERVER\INSTANCE) |
β | β | β | β |
| Stored procedures + OUTPUT params | β | β | β | β |
| Multiple result sets | β | β | β | β |
| Bulk insert | β | β | β | β |
| IAsyncEnumerable row streaming | β | β | β | β |
JSON streaming (QueryJsonStreamAsync) π |
β | β | β | β |
SqlDataTable / ToJson() / ToList<T>() |
β | β | β | β |
| Native SQL backup / restore | β (MSSQL) | β (Logical) | β (Logical) | β (SQLite) |
| SqlClient-compatible API | β | β | β | β |
Install only the package(s) you need:
dotnet add package CosmoSQLClient.MsSql
dotnet add package CosmoSQLClient.Postgres
dotnet add package CosmoSQLClient.MySql
dotnet add package CosmoSQLClient.SqliteAll packages target .NET 10.0 and share the CosmoSQLClient.Core dependency automatically.
Factory method (recommended):
await using var conn = await MsSqlConnection.OpenAsync(
"Server=localhost,1433;Database=mydb;User Id=sa;Password=YourPassword!;" +
"Encrypt=True;TrustServerCertificate=True;");
var users = await conn.QueryAsync("SELECT * FROM Users WHERE Active = @active",
new SqlParameter("active", true));
foreach (var row in users)
Console.WriteLine(row["Email"].AsString());SqlClient-compatible constructor (familiar to ADO.NET users):
var conn = new MsSqlConnection(
"Server=localhost,1433;Database=mydb;User Id=sa;Password=YourPassword!;");
await conn.OpenAsync();
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT * FROM Users WHERE Id = @id";
cmd.Parameters.AddWithValue("id", 42);
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
Console.WriteLine(reader["Email"]);
}await using var conn = await PgConnection.OpenAsync(
"Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=secret;");
var table = await conn.QueryTableAsync(
"SELECT id, name FROM products WHERE price > @minPrice",
new SqlParameter("minPrice", 9.99m));
Console.WriteLine(table.ToMarkdownTable());await using var conn = await MySqlConnection.OpenAsync(
"Server=localhost;Port=3306;Database=mydb;User=root;Password=secret;");
await conn.ExecuteAsync(
"INSERT INTO orders (customer_id, total) VALUES (@cid, @total)",
new SqlParameter("cid", 7),
new SqlParameter("total", 149.95m));await using var conn = await SqliteConnection.OpenAsync("Data Source=app.db;");
await conn.ExecuteAsync(@"
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT NOT NULL
)");
var notes = await conn.QueryAsync("SELECT * FROM notes ORDER BY id DESC");
var list = notes.ToList<Note>();CosmoSQLClient supports both transparent pooling via connection strings and manual pool management.
New in 1.9.16: For MSSQL, you can enable connection pooling directly in your connection string. This is the easiest way to add pooling to existing ADO.NET-style code.
// Just add "Pooling=true" to your connection string
var connStr = "Server=localhost;Database=mydb;User Id=sa;Password=pass;Pooling=true;Max Pool Size=100;";
// Every time you open/close this connection, it uses a global pool transparently
await using var conn = await MsSqlConnection.OpenAsync(connStr);
var rows = await conn.QueryAsync("SELECT 1");
// Upon disposal/close, the physical connection returns to the poolFor more granular control (e.g., custom warming up, different pools for the same string), use the *ConnectionPool classes directly:
var pool = new MsSqlConnectionPool(
MsSqlConfiguration.Parse(connStr),
maxConnections: 20);
// Pre-create idle connections at startup
await pool.WarmUpAsync();
// Use the pool directly as ISqlDatabase β acquire and release happen automatically
var rows = await pool.QueryAsync("SELECT * FROM Users WHERE Active = @a",
new SqlParameter("a", true));The pool uses a channel-based queue and never creates more than maxConnections simultaneous physical connections. Idle connections are health-checked before being returned to callers.
// Simple call
var result = await conn.ExecuteProcedureAsync("usp_GetUser",
new SqlParameter("UserId", 42),
new SqlParameter("IncludeDeleted", false));
// With OUTPUT parameter
var outParam = new SqlParameter("TotalCount", SqlDbType.Int)
{
Direction = ParameterDirection.Output
};
await conn.ExecuteProcedureAsync("usp_CountUsers", outParam);
Console.WriteLine($"Total users: {outParam.Value}");
// Procedure returning a result set
var table = await conn.QueryProcedureTableAsync("usp_ListActiveOrders",
new SqlParameter("CustomerId", 123));Named SQL Server instances are resolved automatically via the SQL Server Browser service (SSRP, UDP 1434). No manual port lookup needed:
// SERVER\INSTANCE β CosmoSQLClient resolves the dynamic port via UDP 1434
await using var conn = await MsSqlConnection.OpenAsync(
"Server=MYSERVER\\SQLEXPRESS;Database=mydb;User Id=sa;Password=pass;" +
"Encrypt=True;TrustServerCertificate=True;");CosmoSQLClient includes a fully managed NTLMv2 implementation, allowing for Windows/Domain authentication on all supported platforms (Windows, Linux, and macOS) without requiring native dependencies or SSPI.
To enable Integrated Security, add Integrated Security=True to your connection string. You can also specify a Domain.
// Multiplatform Domain Authentication
var connStr = "Server=db.internal;Database=mydb;Integrated Security=True;" +
"Domain=MYDOMAIN;User Id=myuser;Password=mypass;";
await using var conn = await MsSqlConnection.OpenAsync(connStr);If User Id is omitted, the library defaults to Environment.UserName. Note that because this is a managed implementation, a Password is still required to compute the NTLMv2 response hashes.
CosmoSQLClient provides a unified interface for engine-native backup and restore operations. This allows you to trigger server-side backups directly via SQL commands without shell access.
Check if the connection supports backups and execute:
using CosmoSQLClient.Core;
if (conn is ISqlBackupable backupable)
{
await backupable.BackupDatabaseAsync("MyDatabase", new BackupOptions
{
DestinationPath = "/var/opt/mssql/backups/MyBackup.bak",
Compression = true,
Format = true
});
}- MSSQL: Native
BACKUP DATABASEandRESTORE DATABASEcommands. - SQLite: Native
VACUUM INTOfor safe, online backups. - Postgres/MySQL: Interface hooks provided; currently requires external CLI tools.
await using var tx = await conn.BeginTransactionAsync();
try
{
await conn.ExecuteAsync(
"INSERT INTO accounts (owner, balance) VALUES (@owner, @bal)",
new object[] { new SqlParameter("owner", "Alice"), new SqlParameter("bal", 1000m) },
tx);
await conn.ExecuteAsync(
"UPDATE accounts SET balance = balance - @amount WHERE id = @id",
new object[] { new SqlParameter("amount", 200m), new SqlParameter("id", 7) },
tx);
await tx.CommitAsync();
}
catch
{
await tx.RollbackAsync();
throw;
}QueryTableAsync materializes a result set into a SqlDataTable that can be exported in several formats:
var table = await conn.QueryTableAsync("SELECT * FROM Products");
// Markdown table β useful for logging, reports, or README generation
Console.WriteLine(table.ToMarkdownTable());
// JSON array
string json = table.ToJson(indented: false);
// Typed list β column-to-property matching is case-insensitive
List<Product> products = table.ToList<Product>();
// Access individual cells
int count = table.Rows.Count;
string name = table.Rows[0]["Name"].AsString();
decimal price = table.Rows[0]["Price"].AsDecimal();All benchmarks run with BenchmarkDotNet on .NET 10.0, Apple M1 (ARM64), with databases running in local Docker containers. Units are in microseconds (Β΅s) or milliseconds (ms).
Since version 1.9.33, CosmoSQLClient implements a strictly-constrained generic pipeline (ProcessTokensAsync) using struct-based token handlers. This architecture ensures:
- Zero Boxing:
SqlRowstructs are never cast toobjectduring protocol decoding. - Devirtualization: The .NET JIT compiler inlines the entire row-parsing path, eliminating interface dispatch overhead.
- Minimal Heap Pressure: Bounded memory footprint even when streaming gigabytes of data.
| Benchmark | CosmoSQL | ADO.NET | Winner |
|---|---|---|---|
| Cold connect + query | 16.3 ms | 13.2 ms | ADO.NET |
| Warm query (full table) | 15.6 ms | 12.8 ms | ADO.NET |
| Warm single-row query | 591 Β΅s | 561 Β΅s | Parity |
Warm ToList<Product> |
16.5 ms | 17.2 ms | π CosmoSQL +4% |
Warm ToJson |
14.4 ms | 14.4 ms | π Parity |
FOR JSON streamed |
13.8 ms | β N/A | π Exclusive |
| Benchmark | CosmoSQL | MySqlConnector | Winner |
|---|---|---|---|
| Cold connect + query | 9.31 ms | 9.32 ms | π CosmoSQL |
| Pool acquire + query | 8.41 ms | 3.10 ms | MySqlConnector |
| Warm single-row query | 221 Β΅s | 231 Β΅s | π CosmoSQL +4% |
| JSON streamed | 14.4 ms | β N/A | π Exclusive |
| Benchmark | CosmoSQL | Npgsql | Winner |
|---|---|---|---|
| Cold connect + query | 9.94 ms | 6.81 ms | Npgsql |
| Pool acquire + query | 5.73 ms | 2.58 ms | Npgsql |
| Warm single-row query | 200 Β΅s | 189 Β΅s | Parity |
| JSON streamed | 13.8 ms | β N/A | π Exclusive |
Takeaway: For modern thin-client workloads, CosmoSQL matches or beats native drivers in raw single-row latency. Our reflection-free
ToList<T>mapping and native JSON streaming deliver massive gains over standard ADO.NET patterns.
Measured against
Microsoft.Data.SqlClient6.1.4 β the official Microsoft MSSQL driver.
| Package | Size | Notes |
|---|---|---|
CosmoSQLClient.MsSql |
67 KB | Single target: net10.0 |
CosmoSQLClient.Core |
29 KB | Required dependency |
| CosmoSQL total | ~96 KB | |
Microsoft.Data.SqlClient 6.1.4 |
8.3 MB | 67 files Β· 14 target frameworks Β· 13 language resource DLLs |
CosmoSQL is ~87Γ smaller as a download.
| CosmoSQL | Size |
|---|---|
| CosmoSQLClient.MsSql.dll | 134 KB |
| CosmoSQLClient.Core.dll | 45 KB |
| Cosmo.Transport.dll (Pipelines wrapper) | 28 KB |
| Total | ~207 KB |
| Microsoft.Data.SqlClient | Size |
|---|---|
| Microsoft.Data.SqlClient.dll (unix/net9) | 2.0 MB β just the one DLL |
| + Azure.Identity, Azure.Core, MSAL, SNI native, JWT tokens⦠| ~15 MB |
| Total | ~17 MB+ |
Microsoft.Data.SqlClient ships:
- 14 target frameworks β net462 through net9, netstandard2.0
- 13 language resource DLLs β ru, ja, fr, de, ko, and more
- 13 mandatory NuGet dependencies including
Azure.Identity,Microsoft.Identity.Client,Microsoft.Data.SqlClient.SNI,Microsoft.IdentityModel.JsonWebTokens, and OpenIdConnect β even if you never use AAD authentication
CosmoSQL ships one target (net10.0), no localisation resources, and a single high-performance transport library (Cosmo.Transport) as its only transitive dependency.
CosmoSQLClient is built on System.IO.Pipelines, the same high-performance I/O library that powers Kestrel. This allows for zero-copy parsing of database protocols. Every connection manages a PipeReader and PipeWriter, ensuring that data is processed directly from the socket buffer without intermediate allocations.
Application code
β
βΌ
ISqlDatabase (MsSqlConnection / PgConnection / MySqlConnection / SqliteConnection)
β
βΌ
Cosmo.Transport (System.IO.Pipelines wrapper)
ββ TlsStream (optional)
ββ Protocol framer (TDS / PG / MySQL length-prefix decoder)
ββ Message decoder (token-stream / backend-message / packet parser)
ββ Command handler (request/response correlation via Channels)
β
βΌ
TCP socket (System.Net.Sockets β zero blocking I/O)
All four backends implement ISqlDatabase, so you can write backend-agnostic application code and swap the underlying engine via dependency injection:
public interface ISqlDatabase
{
Task<SqlDataTable> QueryTableAsync(string sql, params SqlParameter[] parameters);
Task<IEnumerable<SqlRow>> QueryAsync(string sql, params SqlParameter[] parameters);
IAsyncEnumerable<SqlRow> QueryStreamAsync(string sql, params SqlParameter[] parameters);
IAsyncEnumerable<JsonElement> QueryJsonStreamAsync(string sql, params SqlParameter[] parameters);
Task<int> ExecuteAsync(string sql, params SqlParameter[] parameters);
Task<ISqlTransaction> BeginTransactionAsync();
}QueryJsonStreamAsync feeds raw byte chunks from the wire directly into JsonChunkAssembler. It uses Utf8JsonReader with a persisted JsonReaderState to scan incrementally β tracking brace depth without allocating intermediate strings. Each time depth returns to zero after a }, the assembled span is handed to JsonSerializer and yielded to the caller. Peak memory for a 1 GB FOR JSON PATH result is proportional to the largest single JSON object, not the full result.
| Key | Default | Description |
|---|---|---|
Server |
β | Host and port. Aliases: Data Source, Address, Addr. Supports (local) and . shorthands. |
Database |
β | Database name. Aliases: Initial Catalog. |
User Id |
β | Username. Aliases: UID, User. |
Password |
β | Password. Aliases: PWD. |
Encrypt |
True |
True to enable TLS (default is True). |
TrustServerCertificate |
False |
Skip certificate validation (dev/test only). |
Connect Timeout |
30 |
Connection timeout in seconds. |
Application Name |
CosmoSQLClient |
Appears in sys.dm_exec_sessions. |
Integrated Security |
False |
Set to True for multiplatform Windows/Domain auth (Uses a fully managed NTLMv2 implementation). |
Domain |
β | Windows Domain name for NTLM authentication. |
Pooling |
True |
New in 1.9.16: Enable transparent connection pooling. |
Max Pool Size |
100 |
Maximum number of pooled connections. |
Min Pool Size |
0 |
Minimum number of idle connections. |
Examples:
Server=localhost,1433;Database=mydb;User Id=sa;Password=pass;Encrypt=True;TrustServerCertificate=True;Pooling=true;Max Pool Size=50;
Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=secret;
Host=db.internal;Port=5432;Database=mydb;Username=app;Password=secret;SslMode=Require;
Server=localhost;Port=3306;Database=mydb;User=root;Password=secret;
Server=db.internal;Port=3306;Database=mydb;User=app;Password=secret;CharSet=utf8mb4;
Data Source=app.db;
Data Source=/absolute/path/to/app.db;Mode=ReadOnly;
Data Source=:memory:;
SqlValue is the universal cell type returned by QueryAsync and QueryTableAsync. It wraps the raw wire value and provides typed accessors:
SqlValue Accessor |
.NET Type | MSSQL | PostgreSQL | MySQL | SQLite |
|---|---|---|---|---|---|
AsInt32() |
int |
INT |
integer |
INT |
INTEGER |
AsInt64() |
long |
BIGINT |
bigint |
BIGINT |
INTEGER |
AsString() |
string |
NVARCHAR, TEXT |
text, varchar |
VARCHAR, TEXT |
TEXT |
AsDecimal() |
decimal |
DECIMAL, MONEY |
numeric |
DECIMAL |
REAL |
AsDouble() |
double |
FLOAT |
float8 |
DOUBLE |
REAL |
AsFloat() |
float |
REAL |
float4 |
FLOAT |
REAL |
AsBoolean() |
bool |
BIT |
boolean |
TINYINT(1) |
INTEGER |
AsDateTime() |
DateTime |
DATETIME2, DATETIME |
timestamp |
DATETIME |
TEXT |
AsDateTimeOffset() |
DateTimeOffset |
DATETIMEOFFSET |
timestamptz |
β | β |
AsGuid() |
Guid |
UNIQUEIDENTIFIER |
uuid |
CHAR(36) |
TEXT |
AsBytes() |
byte[] |
VARBINARY |
bytea |
BLOB |
BLOB |
IsNull |
bool |
NULL |
NULL |
NULL |
NULL |
var row = rows.First();
if (!row["DeletedAt"].IsNull)
{
var deletedAt = row["DeletedAt"].AsDateTimeOffset();
}- CosmoSQLClient-Swift β Swift NIO port of CosmoSQLClient with the same API design and wire-protocol implementations for server-side Swift (Vapor, Hummingbird).