Skip to content

A .NET data-access framework built for simplicity, high performance and global scale.

Shard a large data set across servers. Give every tenant a database of their own. Split reads from writes. Map results at hand-written ADO.NET speed. Persist Microsoft Orleans grains in real relational tables. ArgentSea is the only .NET framework designed for scaling data out, and it is a pleasure to use even when you never shard.

MIT licensed.NET 10SQL ServerPostgreSQLOrleans 10
Why developers pick it

Everything a data layer needs. One thing nobody else offers.

Most data frameworks assume one database. ArgentSea assumes many, and makes the ordinary case better along the way.

Designed for scale-out

One application. Many databases. Zero special cases.

Whether you partition a huge data set into shards or isolate every customer in a tenant database, the topology is the same: a shard set of databases, each with a read and a write endpoint. Your code addresses the set, or a single shard by key, and ArgentSea opens the right connections, copies the parameters, runs the shards concurrently and merges the results.

  • Route by key. A ShardKey carries the shard id, so the right database is always known.
  • Fan out. ReadAll, ReadFirst and Write run across every shard, or a targeted subset, in parallel.
  • Scale reads. Point read connections at replicas, writes at the primary. Nothing in your code changes.
Walk through the core concepts →
Your appSqlShardSets["Tenants"]Tenant Ashard 0WRTenant Bshard 1WRTenant Cshard 2WRTenant Dshard 3WR
write primaryread replica
What it looks like

Attributes in, models out, SQL where it belongs.

A model declares how its properties map to parameters and columns. A Query names a stored procedure or a deployed SQL file. One call on a Read or Write connection, or on a whole shard set, does the rest: open, parameterize, execute, retry if needed, map, log.

  1. Configure connections in appsettings.json.
  2. Register with AddSqlServices or AddPgServices.
  3. Decorate a model. Name a query. Call a method.
Follow the quickstart →
csharp
public readonly record struct Customer : IKeyedModel<int>
{
    [MapSqlShardKey("@CustomerId")]
    [MapToSqlInt("@CustomerId")]
    public ShardKey<int> Key { get; init; }

    [MapToSqlNVarChar("@Name", 100)]
    public string Name { get; init; }

    [MapToSqlNVarChar("@Email", 255)]
    public string? Email { get; init; }
}

public static class Queries
{
    public static QueryProcedure GetCustomer
        => new("rdr.GetCustomer", ["CustomerId"]);
}

public class CustomerStore(SqlShardSets shardSets, ILogger<CustomerStore> logger)
{
    private readonly SqlShardSets.ShardSet _customers = shardSets["Customers"];

    public Task<Customer> GetAsync(ShardKey<int> key, CancellationToken cancellation)
    {
        var prms = new ParameterCollection()
            .AddSqlIntInputParameter("@CustomerId", key.RecordId);
        return _customers[key].Read
            .MapReaderAsync<Customer>(Queries.GetCustomer, prms, cancellation);
    }
}
csharp
public readonly record struct Customer : IKeyedModel<int>
{
    [MapPgShardKey("customer_id")]
    [MapToPgInteger("customer_id")]
    public ShardKey<int> Key { get; init; }

    [MapToPgVarchar("name", 100)]
    public string Name { get; init; }

    [MapToPgVarchar("email", 255)]
    public string? Email { get; init; }
}

public static class Queries
{
    // Loads SQL/GetCustomer.psql the first time it is used.
    private static readonly Lazy<QueryStatement> _getCustomer
        = QueryStatement.Create("GetCustomer", ["customer_id"]);
    public static QueryStatement GetCustomer => _getCustomer.Value;
}

public class CustomerStore(PgShardSets shardSets, ILogger<CustomerStore> logger)
{
    private readonly PgShardSets.ShardSet _customers = shardSets["Customers"];

    public Task<Customer> GetAsync(ShardKey<int> key, CancellationToken cancellation)
    {
        var prms = new ParameterCollection()
            .AddPgIntegerInputParameter("customer_id", key.RecordId);
        return _customers[key].Read
            .MapReaderAsync<Customer>(Queries.GetCustomer, prms, cancellation);
    }
}
Runs on

Your databases. Your cloud. Your .NET.

  • SQL Server 2017 and later, SQL Server Express
  • Azure SQL Database, including read scale-out and geo-replicas
  • Availability Group readable secondaries
ArgentSea.Sql on NuGet →
  • PostgreSQL with hot standbys
  • Amazon RDS and Aurora, Azure Database for PostgreSQL
  • Google Cloud SQL for PostgreSQL
ArgentSea.Pg on NuGet →
  • Grain persistence in normal relational tables
  • Clustering membership and gateway discovery
  • Reminders, on SQL Server or PostgreSQL
Read the Orleans guide →

Built on .NET 10 with Microsoft.Data.SqlClient and Npgsql. Version 1 of ArgentSea ran in production for years and handled billions of dollars in transactions; version 2 is the current release. Open source under the MIT license.

Released under the MIT License.