Azka.PostgreSQL
10.0.0-rc.1
dotnet add package Azka.PostgreSQL --version 10.0.0-rc.1
NuGet\Install-Package Azka.PostgreSQL -Version 10.0.0-rc.1
<PackageReference Include="Azka.PostgreSQL" Version="10.0.0-rc.1" />
<PackageVersion Include="Azka.PostgreSQL" Version="10.0.0-rc.1" />
<PackageReference Include="Azka.PostgreSQL" />
paket add Azka.PostgreSQL --version 10.0.0-rc.1
#r "nuget: Azka.PostgreSQL, 10.0.0-rc.1"
#:package Azka.PostgreSQL@10.0.0-rc.1
#addin nuget:?package=Azka.PostgreSQL&version=10.0.0-rc.1&prerelease
#tool nuget:?package=Azka.PostgreSQL&version=10.0.0-rc.1&prerelease
Azka.PostgreSQL
PostgreSQL database provider for the Azka Framework. This package registers an IDatabaseContext backed by Npgsql and provides repository implementations for Azka entities, specifications, generated numbers, generated codes, raw SQL execution, and transaction scopes.
Features
- PostgreSQL database context using
NpgsqlDataSource - Dependency injection setup through
services.AddPostgreSQL(...) - Repository pattern through
BaseRepository<T>andBaseViewRepository<T> - Specification queries with where, include, order, skip, and take support
- Generated numeric values through
[GeneratedNumber]and PostgreSQL sequences - Generated string codes through
[GeneratedCode(typeof(...))]andIGeneratedCodeGenerator<TEntity> - Transaction scopes through
IDatabaseContext.BeginTransactionScopeAsync() - Raw SQL helpers for non-query, scalar, table, and data reader operations
- Connection-string builder options through
PostgreSQLOptions
Installation
dotnet add package Azka.PostgreSQL
Requirements
- .NET 10.0 or higher
- Azka core framework
- Npgsql 10.0.1 or higher
Current package version: 10.0.0-alpha6.
Setup
Register the PostgreSQL provider in your application service collection.
using Azka.PostgreSQL;
using Azka.PostgreSQL.Common;
builder.Services.AddPostgreSQL(
new PostgreSQLOptions
{
Host = "localhost",
Port = 5432,
Database = "app_db",
Username = "postgres",
Password = "postgres"
},
typeof(ProductSkuGenerator).Assembly);
You can also pass a complete connection string:
builder.Services.AddPostgreSQL(
new PostgreSQLOptions
{
ConnectionString = builder.Configuration.GetConnectionString("PostgreSQL")
},
typeof(ProductSkuGenerator).Assembly);
The optional assembly parameters are scanned for implementations of IGeneratedCodeGenerator<TEntity>. If no assemblies are provided, the provider scans the currently loaded application assemblies.
PostgreSQLOptions
| Property | Default | Description |
|---|---|---|
ConnectionString |
null |
Complete Npgsql connection string. When set, all builder properties below are ignored. |
Host |
localhost |
PostgreSQL host. |
Port |
5432 |
PostgreSQL port. Must be greater than zero. |
Database |
postgres |
Database name. |
Username |
postgres |
Database username. |
Password |
null |
Optional database password. |
Pooling |
true |
Enables Npgsql connection pooling. |
Timeout |
15 |
Connection timeout in seconds. |
CommandTimeout |
30 |
Command timeout in seconds. |
When ConnectionString is not provided, Host, Port, Database, and Username are required.
Entity Mapping
Azka uses attributes from Azka.Common.Annotations to map entities to database tables and columns.
Mapped entities must have:
[Table]on the entity type.- One
[Key]property. [Column]on the key property.[Column]on every scalar property that should be persisted or loaded.
using Azka.Common.Annotations;
[Table("public", "products")]
public class Product
{
[Key]
[GeneratedNumber]
[Column("id")]
public int Id { get; set; }
[Column("sku")]
[GeneratedCode(typeof(ProductSkuGenerator))]
public string Sku { get; set; } = string.Empty;
[Column("name")]
public required string Name { get; set; }
[Column("price")]
public decimal Price { get; set; }
}
[Table("products")] is also supported when you do not need to set a schema explicitly.
When schema is omitted, Azka uses public.
Database Objects
Create the table and sequences used by generated values.
CREATE TABLE public.products
(
id integer PRIMARY KEY,
sku text NOT NULL,
name text NOT NULL,
price numeric NOT NULL
);
CREATE SEQUENCE products_id_seq START 1;
CREATE SEQUENCE products_sku_seq START 1;
[GeneratedNumber] uses the built-in sequence convention:
{table_name}_{column_name}_seq
For the Product.Id example above, the provider uses products_id_seq.
[GeneratedCode] uses the sequence name returned by your IGeneratedCodeGenerator<TEntity>.GetSequenceName(...) implementation.
Generated Numbers
Use [GeneratedNumber] on numeric properties that should be populated from PostgreSQL nextval(...).
[Key]
[GeneratedNumber]
[Column("id")]
public int Id { get; set; }
Supported property types:
intlongint?long?
For generated numeric primary keys, the provider inserts nextval(...) directly and reads the generated value back through RETURNING.
Generated Codes
Use [GeneratedCode(typeof(YourGenerator))] for generated string values.
using Azka.Common.Abstractions.GeneratedCode;
public sealed class ProductSkuGenerator : IGeneratedCodeGenerator<Product>
{
public string GetSequenceName(Product entity)
{
return "products_sku_seq";
}
public string Generate(Product entity, long sequence)
{
return $"SKU-{sequence:D5}";
}
public bool ShouldGenerate(
Product entity,
string? currentValue,
GeneratedCodeOperation operation)
{
return operation == GeneratedCodeOperation.Insert
&& string.IsNullOrWhiteSpace(currentValue);
}
}
The generator contract is:
| Method | Description |
|---|---|
GetSequenceName(entity) |
Returns the PostgreSQL sequence name to use. |
Generate(entity, sequence) |
Builds the final string value from the entity and sequence number. |
ShouldGenerate(entity, currentValue, operation) |
Controls whether a new value should be generated. The default implementation generates on insert only. |
[GeneratedCode] properties must be string. The generator type must implement IGeneratedCodeGenerator<TEntity> for the entity that owns the property.
Repository
Create a repository by inheriting from BaseRepository<T>. The constructor must accept IDatabaseContext.
BaseRepository<T> inherits from BaseViewRepository<T>, so a repository that extends BaseRepository<T> automatically gets both write operations and specification-based read operations. Use BaseViewRepository<T> directly only when you want a read-only repository surface.
using Azka.Common.Database;
using Azka.PostgreSQL.Repositories;
public sealed class ProductRepository : BaseRepository<Product>
{
public ProductRepository(IDatabaseContext databaseContext)
: base(databaseContext)
{
}
}
Register your repository in DI:
builder.Services.AddScoped<ProductRepository>();
Use it from services, controllers, or handlers:
public sealed class ProductService
{
private readonly ProductRepository _products;
public ProductService(ProductRepository products)
{
_products = products;
}
public async Task<Product> CreateAsync(CancellationToken cancellationToken = default)
{
var product = new Product
{
Name = "Widget",
Price = 29.99m
};
return await _products.AddAsync(product, cancellationToken);
}
}
After AddAsync, generated properties are set on the returned entity.
Basic CRUD usage:
var created = await repository.AddAsync(
new Product { Name = "Widget", Price = 29.99m },
cancellationToken);
var found = await repository.GetByIdAsync(created.Id, cancellationToken);
found.Price = 34.99m;
await repository.UpdateAsync(found, cancellationToken);
await repository.DeleteAsync(found.Id, cancellationToken);
Repository API
BaseRepository<T> is a full read/write repository. It extends BaseViewRepository<T> and implements the additional write operations and primary-key lookup below.
| Method | Description |
|---|---|
AddAsync(T model, CancellationToken cancellationToken = default) |
Inserts an entity and returns it with generated values populated. |
UpdateAsync(T model, CancellationToken cancellationToken = default) |
Updates an entity by its primary key. |
DeleteAsync(object key, CancellationToken cancellationToken = default) |
Deletes an entity by primary key. |
GetByIdAsync(object key, CancellationToken cancellationToken = default) |
Gets an entity by primary key. Throws when the entity is not found. |
Because BaseRepository<T> inherits from BaseViewRepository<T>, the following read methods are also available on every BaseRepository<T> implementation.
| Method | Description |
|---|---|
ListAsync(ISpecification<T> specification, CancellationToken cancellationToken = default) |
Returns all matching rows. |
FirstOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default) |
Returns the first matching row or null. |
FirstAsync(ISpecification<T> specification, CancellationToken cancellationToken = default) |
Returns the first matching row. Throws when no row is found. |
SingleOrDefaultAsync(ISpecification<T> specification, CancellationToken cancellationToken = default) |
Returns a single matching row or null. Throws when more than one row is found. |
SingleAsync(ISpecification<T> specification, CancellationToken cancellationToken = default) |
Returns a single matching row. Throws when no row or more than one row is found. |
Specifications
Use specifications from Azka.Common.Abstractions.Specifications for repository queries.
using Azka.Common.Abstractions.Specifications;
public sealed class ExpensiveProductsSpec : Specification<Product>
{
public ExpensiveProductsSpec(decimal minPrice)
{
Query.Where(product => product.Price >= minPrice);
Query.OrderByDesc(product => product.Price);
Query.Take(10);
}
}
var spec = new ExpensiveProductsSpec(50.00m);
IEnumerable<Product> products = await repository.ListAsync(spec);
Product? first = await repository.FirstOrDefaultAsync(spec);
Product single = await repository.SingleAsync(spec);
Specifications are translated to PostgreSQL SQL with parameters.
Relationships and Include
Use [WithOne] and [WithMany] from Azka.Common.Annotations to define relationships that can be included by specifications.
[WithOne("foreign_column")] is used when the current table has a foreign-key column pointing to the referenced entity primary key.
[Table("public", "orders")]
public class Order
{
[Key]
[GeneratedNumber]
[Column("id")]
public int Id { get; set; }
[Column("customer_id")]
public int CustomerId { get; set; }
[WithOne("customer_id")]
public Customer Customer { get; set; } = null!;
}
[WithMany("foreign_column")] is used when the referenced child table has a foreign-key column pointing back to the current entity primary key.
[Table("public", "products")]
public class Product
{
[Key]
[GeneratedNumber]
[Column("id")]
public int Id { get; set; }
[Column("name")]
public required string Name { get; set; }
[WithMany("product_id")]
public List<ProductReview> Reviews { get; set; } = new();
}
Include relationships in a specification:
public sealed class ProductWithReviewsSpec : Specification<Product>
{
public ProductWithReviewsSpec(int productId)
{
Query.Where(product => product.Id == productId);
Query.Include(product => product.Reviews);
}
}
Where(...) expressions that navigate through related properties can also add include expressions automatically when the navigation path is detected by Azka.
For one-to-many includes, FirstOrDefaultAsync and SingleOrDefaultAsync do not force an internal LIMIT 1 or LIMIT 2, because the mapper may need multiple joined rows to build one aggregate result.
Counting Rows
BaseRepository<T> does not expose CountAsync directly, but IDatabaseContext does.
using Azka.Common.Database;
public sealed class ProductReportService
{
private readonly IDatabaseContext _databaseContext;
public ProductReportService(IDatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}
public Task<long> CountExpensiveProductsAsync(
decimal minPrice,
CancellationToken cancellationToken = default)
{
var spec = new ExpensiveProductsSpec(minPrice);
return _databaseContext.CountAsync(spec, cancellationToken);
}
}
When the specification contains a one-to-many include, the provider counts distinct root primary keys to avoid inflated counts from joined child rows.
Transactions
Use transaction scopes from IDatabaseContext. All repository and database-context operations within the same scoped IDatabaseContext instance participate in the current transaction.
using Azka.Common.Database;
public sealed class ProductImportService
{
private readonly IDatabaseContext _databaseContext;
private readonly ProductRepository _products;
public ProductImportService(
IDatabaseContext databaseContext,
ProductRepository products)
{
_databaseContext = databaseContext;
_products = products;
}
public async Task ImportAsync(CancellationToken cancellationToken = default)
{
await using var transaction =
await _databaseContext.BeginTransactionScopeAsync(cancellationToken);
await _products.AddAsync(
new Product { Name = "Item 1", Price = 10m },
cancellationToken);
await _products.AddAsync(
new Product { Name = "Item 2", Price = 20m },
cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
}
If the transaction scope is disposed before CommitAsync, the provider rolls back the active transaction.
Nested transaction scopes are tracked by the database context. The actual PostgreSQL commit happens when the root transaction scope commits.
Raw SQL
IDatabaseContext exposes raw SQL helpers for cases where repository/specification queries are not enough.
var rows = await databaseContext.ExecuteNonQueryAsync(
"UPDATE products SET price = @price WHERE id = @id",
new Dictionary<string, object?>
{
["price"] = 34.99m,
["id"] = 1
},
cancellationToken);
Available raw SQL methods:
| Method | Description |
|---|---|
ExecuteNonQueryAsync(sql, parameters) |
Executes a command and returns affected row count. |
ExecuteNonQueryAsync(sql) |
Executes a command without parameters. |
ExecuteScalarAsync(sql, parameters) |
Executes a scalar query and returns the first column of the first row. |
ExecuteScalarAsync(sql) |
Executes a scalar query without parameters. |
ExecuteQueryAsync(sql, parameters) |
Executes a query and returns a DataTable. |
ExecuteQueryAsync(sql) |
Executes a query without parameters. |
ExecuteReaderAsync(sql, parameters) |
Executes a query and returns a managed IDataReader. |
ExecuteReaderAsync(sql) |
Executes a query without parameters. |
The context also exposes:
| Method | Description |
|---|---|
GetConnection() |
Gets or creates the current IDbConnection. |
GetCurrentTransaction() |
Returns the current transaction, when one is active. |
OpenConnection() / OpenConnectionAsync() |
Opens the current connection. |
CloseConnection() / CloseConnectionAsync() |
Closes and disposes the current connection when no transaction is active. |
Generated Value Behavior
Insert flow:
- Entity metadata is inspected for
[GeneratedNumber]and[GeneratedCode]. [GeneratedCode]values are generated before insert by resolving the configured generator.[GeneratedNumber]values use PostgreSQLnextval(...).- Generated numeric primary keys and other direct sequence values are read back with
RETURNING. - The entity instance returned by
AddAsynccontains the generated values.
Update flow:
[GeneratedCode]columns are evaluated withGeneratedCodeOperation.Update.- The generator decides whether to generate a replacement value through
ShouldGenerate(...). - All mapped non-primary-key columns are included in the update statement.
Sequence Troubleshooting
If a required sequence does not exist, the provider throws an InvalidOperationException with a message similar to:
Sequence 'products_id_seq' does not exist. Please create it using: CREATE SEQUENCE products_id_seq START 1;
Check that:
- The sequence exists in the schema available to the current connection.
[GeneratedNumber]follows{table_name}_{column_name}_seq.[GeneratedCode]returns the correct sequence name fromGetSequenceName(...).- The database user has permission to call
nextval(...)on the sequence.
Notes
BaseRepository<T>andBaseViewRepository<T>rely on Azka entity metadata. Entities should have a[Table],[Key], and mapped columns.- The provider registers
PostgreSQLOptionsas a singleton,NpgsqlDataSourcethrough Npgsql dependency injection, andIDatabaseContextas scoped. - Generated-code generators found during assembly scanning are registered as transient services.
GetByIdAsyncthrows when the entity is not found.SingleAsyncandSingleOrDefaultAsyncuse LINQ single semantics and throw when more than one row matches.
License
See LICENSE.txt for details.
Links
| 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
- Azka (>= 10.0.0-rc.1)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.1)
- Npgsql (>= 10.0.1)
- Npgsql.DependencyInjection (>= 10.0.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Azka.PostgreSQL:
| Package | Downloads |
|---|---|
|
Azka.BaseProject
Reusable base project primitives for Azka applications, including auditable entities, multi-tenant fields, core repositories, soft-delete behavior, public ID lookup, and SSO user contracts. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0-rc.1 | 43 | 6/30/2026 |
| 10.0.0-alpha6 | 59 | 6/20/2026 |
| 10.0.0-alpha5 | 88 | 3/16/2026 |
| 10.0.0-alpha4 | 74 | 2/17/2026 |
| 10.0.0-alpha3 | 83 | 2/1/2026 |
| 10.0.0-alpha2 | 76 | 1/31/2026 |
| 10.0.0-alpha1 | 82 | 1/19/2026 |
| 10.0.0-alpha.9 | 57 | 6/25/2026 |
| 10.0.0-alpha.8 | 57 | 6/25/2026 |
| 10.0.0-alpha.7 | 54 | 6/25/2026 |
Initial release of Azka.PostgreSQL Provider
- PostgreSQL database support via Npgsql
- Repository pattern implementation for PostgreSQL
- Unit of Work support with transaction management