Building blocks for .NET services
The plumbing every .NET service ends up writing anyway — service discovery, authentication, identifiers, localization, data access, error contracts — as small, independent libraries.
Worked examples
A closer look at how a few of them are used.
Data/Authors.DataOps.xml
<DataConfiguration Timeout="PT30S" Compatibility="PostgreSql" AutoTransaction="ReadCommitted" />
<OperationGroup Name="Authors">
<SqlOperation Name="ById">
<TextCommand ExpectedResult="Row">SELECT id, full_name FROM authors WHERE id = @Id</TextCommand>
</SqlOperation>
<SqlOperation Name="Create">
<TextCommand ExpectedResult="RowCount">INSERT INTO authors (full_name) VALUES (@FullName)</TextCommand>
</SqlOperation>
</OperationGroup>Data/AuthorRepository.cs
var author = await dataOps.Connect().QueryFirst("Authors", "ById").ExecuteAsync<Author>(new { Id = id });
var affected = await dataOps.Connect().NonQuery("Authors", "Create").ExecuteAsync(newAuthor);The call site names an operation; it never spells out SQL. Retuning a query, or adding a dialect for another engine, is an edit to the XML — and because the timeout and transaction level are declared alongside, they are reviewed with the statement rather than buried in code.
Data Operations docsNothing to adopt
There is no framework here to buy into. Each library ships on its own schedule and stands on its own — take one, ignore the rest.
Everything opt-in
No package wires the others together. Each registers what it owns and nothing else, so your composition root stays yours.
Zero-dependency cores
The core of each library takes no dependency on ASP.NET Core or on a container. A console app or worker service can use it as-is.
Consistent where it counts
The same settings-section shape, the same error contract, the same conventions — so the second library you reach for behaves like the first.