Azka 10.0.0-alpha3
dotnet add package Azka --version 10.0.0-alpha3
NuGet\Install-Package Azka -Version 10.0.0-alpha3
<PackageReference Include="Azka" Version="10.0.0-alpha3" />
<PackageVersion Include="Azka" Version="10.0.0-alpha3" />
<PackageReference Include="Azka" />
paket add Azka --version 10.0.0-alpha3
#r "nuget: Azka, 10.0.0-alpha3"
#:package Azka@10.0.0-alpha3
#addin nuget:?package=Azka&version=10.0.0-alpha3&prerelease
#tool nuget:?package=Azka&version=10.0.0-alpha3&prerelease
Azka Framework
A lightweight, high-performance data access framework for .NET that provides Repository, Unit of Work, and Specification patterns with fluent query building capabilities.
Features
- Repository Pattern - Clean abstraction for CRUD operations
- Unit of Work Pattern - Transaction management with nested transaction support
- Specification Pattern - Composable queries with fluent API
- High-Performance Mapping - Fast object mapper using DynamicMethod compilation
- Relationship Support - One-to-many and many-to-one relationships with automatic JOIN handling
- Metadata-Driven - Automatic metadata generation through reflection
- Attribute-Based Configuration - Simple attributes for entity-to-table mapping
Installation
dotnet add package Azka
Quick Start
1. Define Your Entity
using Azka.Common.Annotations;
[Table("Users")]
public class User
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("username")]
public required string Username { get; set; }
[Column("email")]
public required string Email { get; set; }
// One-to-many relationship
[WithMany("user_id")]
public ICollection<Post> Posts { get; set; } = new List<Post>();
}
[Table("Posts")]
public class Post
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("title")]
public required string Title { get; set; }
[Column("user_id")]
public int UserId { get; set; }
// Many-to-one relationship
[WithOne("user_id")]
public User? User { get; set; }
}
2. Create a Specification
using Azka.Common.Abstractions.Specifications;
public class ActiveUserPostsSpecification : Specification<User>
{
public ActiveUserPostsSpecification(int userId)
{
Query.Where(u => u.Id == userId);
Query.Include(u => u.Posts);
Query.OrderByDesc(u => u.Id);
}
}
3. Use Repository via Unit of Work
public interface IAppUnitOfWork : IUnitOfWork
{
IRepository<User> Users { get; }
}
// Inject only Unit of Work
public class UserService
{
private readonly IAppUnitOfWork _unitOfWork;
public UserService(IAppUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task<User?> GetUserWithPostsAsync(int userId)
{
var spec = new ActiveUserPostsSpecification(userId);
return await _unitOfWork.Users.SingleOrDefaultAsync(spec);
}
public async Task AddUserAsync(User user)
{
await _unitOfWork.Users.AddAsync(user);
}
}
4. Use Unit of Work
public interface IAppUnitOfWork : IUnitOfWork
{
IRepository<User> Users { get; }
IRepository<Post> Posts { get; }
}
public class UserService
{
private readonly IAppUnitOfWork _unitOfWork;
public UserService(IAppUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public async Task CreateUserWithPostAsync(User user, Post post)
{
var token = _unitOfWork.Begin();
try
{
user = await _unitOfWork.Users.AddAsync(user);
post.UserId = user.Id;
await _unitOfWork.Posts.AddAsync(post);
_unitOfWork.Commit(token);
}
catch
{
_unitOfWork.Rollback();
throw;
}
}
}
Specification Builder API
The Specification<T> base class provides a fluent API for building queries:
| Method | Description |
|---|---|
Where(Expression) |
Add a filter condition |
Include(Expression) |
Include related entities |
OrderBy(Expression) |
Sort ascending |
OrderByDesc(Expression) |
Sort descending |
Skip(int) |
Skip N records |
Take(int) |
Take N records |
public class ComplexQuerySpecification : Specification<User>
{
public ComplexQuerySpecification(string email, int page, int pageSize)
{
Query.Where(u => u.Email.Contains(email));
Query.Include(u => u.Posts);
Query.OrderBy(u => u.Username);
Query.Skip((page - 1) * pageSize);
Query.Take(pageSize);
}
}
Attributes
| Attribute | Target | Description |
|---|---|---|
[Table] |
Class | Maps entity to table name and schema |
[Column] |
Property | Maps property to column name |
[Key] |
Property | Identifies primary key |
[WithOne] |
Property | Configures many-to-one relationship (takes foreign key column name) |
[WithMany] |
Property | Configures one-to-many relationship (takes foreign key column name) |
Note: Properties marked with [Key] must also include [Column("...")] so the primary key column name can be resolved.
Attribute Examples
[Table("Users", "dbo")] // With custom schema
public class User { }
[Column("first_name")] // Map to different column name
public string FirstName { get; set; }
[WithOne("department_id")] // Foreign key column name
public Department? Department { get; set; }
[WithMany("department_id")] // Foreign key column name
public ICollection<Employee> Employees { get; set; }
Mapping Rules (Entity Mapper)
- Entities must have a public parameterless constructor.
[Key]must be paired with[Column("...")]to resolve the primary key column.- Columns are read from
SELECTaliases in the formatalias_column(case-insensitive); this is handled automatically when using the built-in query helpers. - If a property has a backing field named
_{camelCaseProperty}, the mapper will set the backing field directly.
Backing Field Mapping Example
If a property has a private backing field named _{camelCaseProperty}, the mapper sets the backing field directly.
[Table("users")]
public class User
{
[Key]
[Column("id")]
public int Id { get; set; }
private string _username = string.Empty;
[Column("username")]
public string Username
{
get => _username;
set => _username = value;
}
[Column("email")]
public string Email { get; set; } = string.Empty;
}
Backing Field for One-to-Many (Read-Only List Exposure)
Use a private List<T> backing field for [WithMany], and expose it as a read-only collection to avoid sharing a mutable list reference.
[Table("users")]
public class User
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("username")]
public string Username { get; set; } = string.Empty;
[Column("email")]
public string Email { get; set; } = string.Empty;
private List<Post> _posts = new();
[WithMany("user_id")]
public IReadOnlyCollection<Post> Posts => _posts.AsReadOnly();
public void AddPost(Post post)
{
if (post == null) throw new ArgumentNullException(nameof(post));
if (_posts.Contains(post)) return;
post.UserId = Id;
post.User = this;
_posts.Add(post);
}
public void RemovePost(Post post)
{
if (post == null) throw new ArgumentNullException(nameof(post));
if (_posts.Remove(post))
{
post.UserId = default;
post.User = null;
}
}
}
[Table("posts")]
public class Post
{
[Key]
[Column("id")]
public int Id { get; set; }
[Column("title")]
public string Title { get; set; } = string.Empty;
[Column("user_id")]
public int UserId { get; set; }
[WithOne("user_id")]
public User? User { get; set; }
}
Unit of Work API
The Unit of Work pattern manages transactions with support for nested transactions:
public interface IUnitOfWork : IDisposable
{
TransactionToken Begin(); // Returns a token for tracking
void Commit(TransactionToken token); // Commit with token
void Rollback(); // Rollback all
}
Note: Create a custom UnitOfWork implementation (e.g., IAppUnitOfWork) that inherits from BaseUnitOfWork to expose repositories, so services only inject the Unit of Work:
public sealed class AppUnitOfWork : BaseUnitOfWork, IAppUnitOfWork
{
public AppUnitOfWork(IServiceProvider serviceProvider, IDbConnection connection)
: base(serviceProvider, connection)
{
}
public IRepository<User> Users => GetRepository<IRepository<User>>();
public IRepository<Post> Posts => GetRepository<IRepository<Post>>();
}
Nested Transaction Support
public async Task ComplexOperationAsync()
{
var token = _unitOfWork.Begin();
try
{
// This can call other methods that also call Begin()
await NestedOperationAsync();
_unitOfWork.Commit(token);
}
catch
{
_unitOfWork.Rollback();
throw;
}
}
private async Task NestedOperationAsync()
{
var nestedToken = _unitOfWork.Begin();
try
{
// Do work here
_unitOfWork.Commit(nestedToken); // Only commits if parent commits
}
catch
{
_unitOfWork.Rollback();
throw;
}
}
Requirements
- .NET 10.0 or higher
- Microsoft.Extensions.DependencyInjection.Abstractions 10.0.1 or higher
- Microsoft.Extensions.Logging.Abstractions 10.0.1 or higher
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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.1)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Azka:
| Package | Downloads |
|---|---|
|
Azka.PostgreSQL
PostgreSQL database provider for Azka Framework, providing high-performance data access using Npgsql. |
|
|
Azka.CQRS
A lightweight CQRS library for .NET that provides command/query pipelines, validation, hooks, and DI integration. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0-alpha3 | 93 | 2/1/2026 |
| 10.0.0-alpha2 | 87 | 1/31/2026 |
| 10.0.0-alpha1 | 82 | 1/19/2026 |
| 4.0.0-alpha.48 | 166 | 4/30/2025 |
| 4.0.0-alpha.47 | 124 | 4/25/2025 |
| 4.0.0-alpha.46 | 113 | 4/25/2025 |
| 4.0.0-alpha.45 | 129 | 4/25/2025 |
| 4.0.0-alpha.44 | 169 | 4/24/2025 |
| 4.0.0-alpha.43 | 200 | 4/15/2025 |
| 4.0.0-alpha.42 | 190 | 4/14/2025 |
| 4.0.0-alpha.41 | 192 | 4/14/2025 |
| 4.0.0-alpha.40 | 175 | 4/10/2025 |
| 4.0.0-alpha.39 | 169 | 4/10/2025 |
| 4.0.0-alpha.38 | 172 | 4/10/2025 |
| 4.0.0-alpha.37 | 175 | 4/9/2025 |
| 4.0.0-alpha.36 | 168 | 4/8/2025 |
| 4.0.0-alpha.35 | 164 | 4/8/2025 |
| 4.0.0-alpha.34 | 115 | 12/7/2024 |
| 4.0.0-alpha.33 | 115 | 12/7/2024 |
| 4.0.0-alpha.32 | 104 | 12/7/2024 |
| 4.0.0-alpha.31 | 99 | 12/6/2024 |
| 4.0.0-alpha.30 | 104 | 12/6/2024 |
| 4.0.0-alpha.29 | 105 | 12/6/2024 |
| 4.0.0-alpha.28 | 101 | 12/2/2024 |
| 4.0.0-alpha.27 | 106 | 12/2/2024 |
| 4.0.0-alpha.26 | 106 | 12/2/2024 |
| 4.0.0-alpha.25 | 108 | 12/1/2024 |
| 4.0.0-alpha.24 | 108 | 12/1/2024 |
| 4.0.0-alpha.23 | 101 | 12/1/2024 |
| 4.0.0-alpha.22 | 108 | 12/1/2024 |
| 4.0.0-alpha.21 | 101 | 12/1/2024 |
| 4.0.0-alpha.20 | 112 | 12/1/2024 |
| 4.0.0-alpha.19 | 106 | 11/30/2024 |
| 4.0.0-alpha.18 | 112 | 11/24/2024 |
| 4.0.0-alpha.17 | 114 | 11/12/2024 |
| 4.0.0-alpha.16 | 125 | 5/6/2024 |
| 4.0.0-alpha.15 | 142 | 2/8/2024 |
| 4.0.0-alpha.14 | 167 | 1/1/2024 |
| 4.0.0-alpha.13 | 176 | 12/16/2023 |
| 4.0.0-alpha.12 | 132 | 12/16/2023 |
| 4.0.0-alpha.11 | 135 | 12/16/2023 |
| 4.0.0-alpha.10 | 137 | 12/15/2023 |
| 4.0.0-alpha.9 | 169 | 11/12/2023 |
| 4.0.0-alpha.8 | 127 | 11/10/2023 |
| 4.0.0-alpha.7 | 209 | 4/28/2023 |
| 4.0.0-alpha.6 | 209 | 4/28/2023 |
| 4.0.0-alpha.5 | 204 | 4/26/2023 |
| 4.0.0-alpha.4 | 194 | 4/24/2023 |
| 4.0.0-alpha.3 | 200 | 4/24/2023 |
| 4.0.0-alpha.2 | 211 | 4/20/2023 |
| 4.0.0-alpha.1 | 216 | 4/20/2023 |
| 3.0.0-alpha.6 | 348 | 12/29/2022 |
| 3.0.0-alpha.5 | 232 | 12/5/2022 |
| 3.0.0-alpha.4 | 247 | 10/15/2022 |
| 3.0.0-alpha.2 | 239 | 10/15/2022 |
| 3.0.0-alpha.1 | 246 | 10/15/2022 |
| 2.0.1-alpha1 | 331 | 6/8/2022 |
| 2.0.1-alpha.6 | 253 | 10/14/2022 |
| 2.0.1-alpha.5 | 246 | 10/2/2022 |
| 2.0.1-alpha.4 | 256 | 9/19/2022 |
| 2.0.1-alpha.3 | 242 | 9/10/2022 |
| 2.0.1-alpha.2 | 279 | 7/14/2022 |
| 2.0.1-alpha.1 | 282 | 6/11/2022 |
| 2.0.0 | 747 | 5/21/2022 |
| 0.0.1-alpha.4 | 274 | 5/9/2022 |
| 0.0.1-alpha.3 | 270 | 2/6/2022 |
| 0.0.1-alpha.2 | 305 | 2/5/2022 |
| 0.0.1-alpha.1 | 297 | 1/22/2022 |
Initial release of Azka Framework
- Repository pattern with CRUD operations
- Unit of Work pattern with nested transaction support
- Specification pattern for composable queries
- High-performance object mapper
- Support for one-to-many and many-to-one relationships