Getting started
Add the NetlyDB.SDK package to your project, register NetlyDB during application startup,
and inject INetlyDBCollection<T> wherever you need typed document access.
Minimal setup
// Program.cs or startup
builder.Services
.AddNetlyDB()
.AddNetlyDBCollection<Order>();
var app = builder.Build();
var orders = app.Services.GetRequiredService<INetlyDBCollection<Order>>();
The default collection name is derived from the document type (for example User maps to a User collection).
Call AddNetlyDBCollection<T>() once per document type you want in dependency injection.
Using a collection
var id = orders.Add(new Order
{
Reference = "SO-10042",
CustomerName = "Contoso",
Country = "BE",
Status = "open",
Total = 1299.50m
});
var order = orders.Find(id);
var openInBelgium = await orders.ListAsync(x => x.Country == "BE" && x.Status == "open");
orders.Update(id, order with { Status = "shipped" });
orders.Delete(id);Configuration
Pass a delegate to AddNetlyDB to configure NetlyDBOptions. All options are optional; sensible defaults apply for embedded single-node use.
services.AddNetlyDB(options =>
{
options.EnableConsoleLogs = false;
options.EnableGrpcHosting = true;
options.GrpcPort = 9025;
options.ShardCount = 4;
options.QueryLogRetention = TimeSpan.FromDays(7);
})
.AddNetlyDBCollection<Order>();
| Option | Description | Default |
|---|---|---|
EnableConsoleLogs |
Writes structured log events to the console. | true |
EnableGrpcHosting |
Hosts the gRPC endpoint used by cluster replication and remote clients. | true |
GrpcPort |
Port for the gRPC service. | 9025 |
BrokerHost / BrokerPort |
Event broker used for logging and operational events. | localhost / 9023 |
ShardCount |
Number of shards for document distribution. | 4 |
EventLogRetention |
How long event log entries are kept. | 30 days |
QueryLogRetention |
How long query history entries are kept. | 3 days |
EnableTtlCleanup |
Background cleanup of documents with a TTL. | true |
SelfAddress / Port |
Required when running as a cluster node. | null |
NodeFocus |
Performance tuning mode for the node. | Speed |
Optional modules
NetlyDB is modular. Enable only what your deployment needs:
AddNetlyDBAPI(port)— self-hosted REST API (default port 9020).AddNetlyDBManagementUI(webPort, apiPort)— operational Management UI (default web port 9021).AddNetlyDBMCP(port, path, maxResultLimit, allowedCollections)— Model Context Protocol server for AI tooling (default port 9026).AddRemoteNetlyDB(remoteAddress, port)— gRPC client; replaces the local store with a remote node.JoinCluster(seedNodes)— distributed mode; contact seed nodes to join a cluster.UseStorageProvider<T>(rootPath)— swap file-system storage (for example Azure Blob).
services.AddNetlyDB(options => options.EnableConsoleLogs = false)
.AddNetlyDBCollection<User>()
.AddNetlyDBAPI(port: 9020)
.AddNetlyDBManagementUI(webappPort: 9021, apiPort: 9020)
.AddNetlyDBMCP(port: 9026, path: "/mcp", maxResultLimit: 100)
.JoinCluster("10.0.0.12", "10.0.0.13");
Named collections
Register multiple logical collections for the same document type:
services.AddNetlyDB()
.AddNetlyDBCollection<AuditEntry>("audit")
.AddNetlyDBCollection<AuditEntry>("security");
var provider = app.Services.GetRequiredService<INamedNetlyDBCollectionProvider<AuditEntry>>();
var audit = provider.Get("audit");
var security = provider.Get("security");Collections API
INetlyDBCollection<T> is the application-facing wrapper around a typed document collection.
| Member | Purpose |
|---|---|
Add / AddRange |
Insert one or many documents; optional per-document TTL. |
Find / FindAsync |
Load a document by id. |
All / AllAsync |
Return every document in the collection. |
Query() |
Build a fluent LINQ or string filter query. |
ListAsync / FirstOrDefaultAsync / AnyAsync / CountAsync |
Query with LINQ or plain-text filter. |
Update |
Replace a document by id. |
UpdateWhereAsync |
Patch fields on all documents matching a LINQ predicate. |
Delete / DeleteWhereAsync |
Remove by id or by LINQ predicate. |
CreateIndex |
Declare an index from a property expression. |
For projections, aggregates, and text-based update / delete statements, use the lower-level
IDocumentCollection<T> (also registered in DI) via QueryAsync, SelectAsync, and related methods.
LINQ queries
The fluent Query() API composes filters with Where, sorts with OrderBy / OrderByDescending,
and pages with Skip / Take. Multiple Where calls are combined with logical and.
var page = await orders.Query()
.Where(x => x.Country == "BE")
.Where(x => x.Total >= 100)
.OrderByDescending(x => x.Total)
.Skip(0)
.Take(25)
.ToListAsync();
Shortcut methods
These accept either a LINQ predicate or a plain-text filter string:
// LINQ predicate
var shipped = await orders.ListAsync(x => x.Status == "shipped");
// Plain-text filter (same syntax as Management UI)
var shippedText = await orders.ListAsync("Status == \"shipped\"");
var count = await orders.CountAsync("Country == \"BE\"");
var exists = await orders.AnyAsync(x => x.Total > 10_000);
Supported LINQ patterns
- Equality and inequality:
==,!= - Comparisons:
<,>,<=,>= - Logical combinations:
&&,||(via chainedWhereor a single expression) - String helpers:
StartsWith,EndsWith,Contains, and negation with! - Nested properties:
x.Address.Country == "BE"
await orders.ListAsync(x => x.CustomerName.StartsWith("Cont"));
await orders.ListAsync(x => x.Status != "cancelled");
await orders.ListAsync(x => !x.Reference.EndsWith("-TEMP"));
await orders.ListAsync(x => x.Country == "BE" && x.LineCount >= 3);
Bulk updates and deletes
var updated = await orders.UpdateWhereAsync(
x => x.Status == "open" && x.Country == "BE",
u => u.Set(x => x.Status, "processing"));
var removed = await orders.DeleteWhereAsync(x => x.Status == "cancelled");
Indexes
Create indexes with expression selectors to speed up filters on those fields (including nested and compound keys):
orders.CreateIndex(x => x.Country);
orders.CreateIndex(x => x.Status);
orders.CreateIndex(x => new { x.Country, x.Status });Plain-text query syntax
Pass a filter string to ListAsync, FirstOrDefaultAsync, CountAsync, AnyAsync,
or Query().Where(string). Syntax matches the Management UI query language.
Filter operators
| Operator | Example |
|---|---|
| == | Country == "BE" |
| != | Status != "cancelled" |
| >, >=, <, <= | Total >= 100 && LineCount < 5 |
| && | Country == "BE" && Status == "shipped" |
| like | CustomerName like "Acme%" |
| not like | Reference not like "TMP%" |
| in | Country in ("BE", "NL", "DE") |
| skip / limit | Status == "open" skip 20 limit 10 |
| order by | Country == "BE" order by Total desc limit 25 |
Examples
Single property
Match one field to a literal value.
Country == "BE"
await orders.ListAsync("Country == \"BE\"");Multiple conditions
Combine predicates with &&.
Country == "BE" && Status == "shipped" && Total >= 500
await orders.Query()
.Where("Country == \"BE\" && Status == \"shipped\" && Total >= 500")
.ToListAsync();Wildcard search
Use like with % as in SQL.
CustomerName like "Net%"
await orders.FirstOrDefaultAsync("CustomerName like \"Net%\"");Paging
Skip and limit can appear in any order after the filter.
Status == "open" skip 10 limit 25
await orders.ListAsync("Status == \"open\" skip 10 limit 25");Sorting
Order results before paging when using string queries on the collection.
Country == "BE" order by Total desc limit 10
// Prefer Query().OrderBy(...) for LINQ; order by is built into text queries on IDocumentCollection.SELECT queries
Use IDocumentCollection<T>.SelectAsync or QueryAsync when you need field projection, aggregates,
grouping, or raw rows without document metadata wrappers.
var collection = app.Services.GetRequiredService<IDocumentCollection<Order>>();
// Project fields
var rows = await collection.SelectAsync(
"select Reference, CustomerName, Total where Country == \"BE\" limit 50");
// Raw rows (no document metadata wrapper)
var raw = await collection.SelectAsync(
"select raw Reference, Total where Status == \"open\"");
// Aggregates
var stats = await collection.SelectAsync(
"select Country, sum(Total) as Revenue, count(*) as OrderCount group by Country");
Clauses can be combined: where, order by, group by, skip, and limit.
UPDATE and DELETE queries
Text mutations run through QueryAsync on IDocumentCollection<T>:
var collection = app.Services.GetRequiredService<IDocumentCollection<Order>>();
await collection.QueryAsync(
"update set Status = \"archived\" where Country == \"BE\" && CreatedAt < DateTime(\"2024-01-01T00:00:00Z\")");
await collection.QueryAsync("delete where Status == \"cancelled\"");
delete * truncates the entire collection. All other deletes require an explicit where clause.
Document metadata
Filter on system fields with the Meta. prefix:
await orders.ListAsync("Meta.CreatedAt >= DateTime(\"2025-01-01T00:00:00Z\")");
// Meta.Id uses the document identifier string
await orders.FirstOrDefaultAsync("Meta.Id == \"b549ea4b-0040-4d16-9dfe-0c6c06ca16fa\"");Solution packages
- NetlyDB.SDK — dependency injection extensions,
INetlyDBCollection<T>, cluster and remote client wiring. - NetlyDB.Core — document engine, query pipeline, indexing, persistence, replication.
- NetlyDB.API — optional REST API host.
- NetlyDB.MCP — optional MCP server for AI assistants.