Kanject.Core.SqlDatabase.Abstractions
1.3.1
Prefix Reserved
dotnet add package Kanject.Core.SqlDatabase.Abstractions --version 1.3.1
NuGet\Install-Package Kanject.Core.SqlDatabase.Abstractions -Version 1.3.1
<PackageReference Include="Kanject.Core.SqlDatabase.Abstractions" Version="1.3.1" />
<PackageVersion Include="Kanject.Core.SqlDatabase.Abstractions" Version="1.3.1" />
<PackageReference Include="Kanject.Core.SqlDatabase.Abstractions" />
paket add Kanject.Core.SqlDatabase.Abstractions --version 1.3.1
#r "nuget: Kanject.Core.SqlDatabase.Abstractions, 1.3.1"
#:package Kanject.Core.SqlDatabase.Abstractions@1.3.1
#addin nuget:?package=Kanject.Core.SqlDatabase.Abstractions&version=1.3.1
#tool nuget:?package=Kanject.Core.SqlDatabase.Abstractions&version=1.3.1
Kanject.Core.SqlDatabase.Abstractions
Provider-neutral contracts for Kanject's relational data layer: ISqlEntity, ISqlRepository<TEntity>, ISqlUnitOfWork, ISqlDbContext, the SqlQuerySpec query model, and the table descriptors a provider's source generator emits. Application services depend on these interfaces; the Amazon Aurora DSQL provider implements them.
The contract is designed for Native AOT and deliberately has no EF Core, no LINQ provider and no runtime model discovery. A query is a plain object of column names, operators and values — no expression trees — and table shapes arrive as compile-time-generated descriptors instead of being reflected from your classes.
Installation
dotnet add package Kanject.Core.SqlDatabase.Abstractions
Targets .NET 8, .NET 9 and .NET 10. The package has no dependencies and is Native AOT / trimming compatible.
It contains no database driver and no in-memory implementation — pair it with a provider (see Related packages; providers are commercially licensed).
Quick start
ISqlEntity is a marker with no members; the provider's attributes declare the primary key.
using Kanject.Core.SqlDatabase.Abstractions.Interfaces;
public sealed class Customer : ISqlEntity
{
public Guid CustomerId { get; init; }
public string Email { get; set; } = string.Empty;
public string Status { get; set; } = "active";
public DateTime CreatedAt { get; set; }
}
Code application services against ISqlRepository<TEntity>:
using Kanject.Core.SqlDatabase.Abstractions.Enums;
using Kanject.Core.SqlDatabase.Abstractions.Interfaces;
using Kanject.Core.SqlDatabase.Abstractions.Models;
public sealed class CustomerService(ISqlRepository<Customer> customers)
{
public Task<Customer?> FindByEmailAsync(string email)
{
var spec = new SqlQuerySpec();
spec.Filters.Add(new SqlFilter("email", SqlOperator.Equal, email));
return customers.GetSingleAsync(spec);
}
public Task<IList<Customer>> ActivePageAsync(int pageIndex, int pageSize)
{
var spec = new SqlQuerySpec { ResultLimit = pageSize, ResultOffset = pageIndex * pageSize };
spec.Filters.Add(new SqlFilter("status", SqlOperator.Equal, "active"));
spec.OrderBy.Add(new SqlOrderBy("created_at", SqlOrderDirection.Descending));
return customers.GetAllAsync(spec);
}
public Task<long> CountActiveAsync()
{
var spec = new SqlQuerySpec();
spec.Filters.Add(new SqlFilter("status", SqlOperator.Equal, "active"));
return customers.CountAsync(spec);
}
public Task SaveAsync(Customer customer) => customers.AddOrUpdateAsync(customer);
}
Column names in a spec are SQL column names, not C# property names. The DSQL provider maps properties to snake_case by default (CreatedAt → created_at) unless a [Column] attribute overrides it.
With the DSQL provider, the entity implements IDsqlEntity (which extends ISqlEntity) and marks its key with [PrimaryKey]. The provider's AddDbContext<TContext>() registers the unit of work against ISqlUnitOfWork, and AddDsqlRepository<TRepository, TEntity>() registers each generated repository against ISqlRepository<TEntity>, so the service above resolves unchanged.
Repository operations
| Member | SQL shape |
|---|---|
GetSingleAsync(string id) |
Fetch by primary key (string form; composite keys use the provider's key composition) |
GetSingleAsync(SqlQuerySpec) / GetAllAsync(SqlQuerySpec?) |
SELECT … WHERE … ORDER BY … LIMIT/OFFSET |
AnyAsync(SqlQuerySpec?) / CountAsync(SqlQuerySpec?) |
Existence check / COUNT(*) (returns long) |
InsertAsync(entity) / InsertRangeAsync |
INSERT |
InsertAsync(entity, SqlQuerySpec) |
INSERT … ON CONFLICT DO NOTHING, conflict target from the spec's key predicates |
AddOrUpdateAsync / AddOrUpdateRangeAsync |
INSERT … ON CONFLICT (pk) DO UPDATE |
UpdateAsync / UpdateRangeAsync |
UPDATE by primary key |
RemoveAsync / RemoveRangeAsync |
DELETE by primary key |
SqlQueryAsync / SqlQueryAsync<TData> / ExecuteQueryAsync |
Raw-SQL escape hatches |
Every *RangeAsync method also has a CancellationToken overload whose default implementation forwards to the token-less one.
The raw SqlQueryAsync<TData> is only AOT-safe when the projection type has a generator-emitted mapper; otherwise it falls back to driver-default mapping.
Building query specs
SqlQuerySpec holds KeyPredicates, Filters, OrderBy, ExistsPredicates, ResultLimit and ResultOffset. You can fill it directly (as above) or through SqlQuerySpecOption<TEntity>, whose Config property is the spec:
using Kanject.Core.SqlDatabase.Abstractions.Extensions;
var option = new SqlQuerySpecOption<Customer>().Limit(20).Offset(40);
option.Config.Filters.Add(new SqlFilter("email", SqlOperator.ILike, "%@example.com"));
IList<Customer> page = await customers.GetAllAsync(option.Config);
Providers with a source generator emit typed per-column extensions on SqlQuerySpecOption<TEntity> — Where{Column}, OrderBy{Column} and friends in the DSQL provider. DbFunc builds the (SqlOperator, value) tuples those extensions accept:
using Kanject.Core.SqlDatabase.Abstractions.Helpers;
// WhereCreatedAt / WhereStatus are generated for the entity by the DSQL annotations package.
var since = DateTime.UtcNow.AddDays(-30);
option.WhereCreatedAt(DbFunc.GreaterThanOrEqual(since))
.WhereStatus(DbFunc.In("active", "trial"));
DbFunc covers Equal, NotEqual, LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual, Like, ILike, In, NotIn and Between. SqlOperator additionally has IsNull and IsNotNull.
Units of work
ISqlUnitOfWork resolves repositories and scopes a transaction:
// Account is an ISqlEntity like Customer above.
public sealed class TransferService(ISqlUnitOfWork unitOfWork)
{
public async Task MoveAsync(Account from, Account to, CancellationToken cancellationToken)
{
var accounts = unitOfWork.Repository<Account>();
unitOfWork.BeginTransaction();
await accounts.UpdateAsync(from);
await accounts.UpdateAsync(to);
await unitOfWork.CommitAsync(cancellationToken);
}
}
The contract leaves it to the implementation whether BeginTransaction/CommitAsync open a real database transaction. The DSQL provider does: it issues BEGIN on a dedicated connection that every repository call in the same scope enlists in, COMMIT on CommitAsync, and rolls back if the unit of work is disposed without committing. It registers the unit of work as scoped, and its own unit-of-work interface adds BeginTransactionAsync and RollbackAsync.
Schema descriptors
ISqlDbContext exposes the default Schema and GetTableDescriptors(). A provider's source generator emits one SqlTableDescriptor per repository-bound entity — Schema, Table, PrimaryKey (SqlPrimaryKey), Columns (SqlColumnDescriptor: name, database type, nullability) and Indexes (SqlIndexDescriptor). A provider's schema applier creates missing schemas, tables and indexes from that list (the DSQL provider does so at startup when its schema check is enabled). Nothing inspects your entity classes at runtime.
Public surface at a glance
| Type | Purpose |
|---|---|
ISqlEntity |
Marker for persistable entities |
ISqlRepository, ISqlRepository<TEntity> |
Untyped (raw SQL, context binding) and typed CRUD contracts |
ISqlUnitOfWork |
Repository resolution and transaction scope; IDisposable and IAsyncDisposable |
ISqlDbContext |
Default schema plus compile-time table descriptors |
SqlQuerySpec, SqlKeyPredicate, SqlFilter, SqlOrderBy, SqlExistsPredicate |
Structured WHERE / ORDER BY / EXISTS / LIMIT / OFFSET |
SqlQuerySpecOption<TEntity>, SqlQuerySpecExtensions (Limit, Offset), DbFunc |
Fluent spec building |
SqlTableDescriptor, SqlPrimaryKey, SqlColumnDescriptor, SqlIndexDescriptor |
Generated schema description |
BatchWriteConfig |
Range-write chunking defaults: 1,000 rows or 4 MiB per chunk; COPY FROM STDIN BINARY from 10,000 rows |
SqlOperator, SqlOrderDirection, SqlEnvironment (Remote, Local) |
Enumerations |
SqlUpdateOption<TEntity>, SqlUpdateExpression, SqlConditionalExpression, SqlUpdateOperator |
Update-side shapes; no ISqlRepository<TEntity> method takes them yet |
Related packages
| Package | Role | Availability |
|---|---|---|
Kanject.Core.NoSqlDatabase.Abstractions |
The NoSQL counterpart of this contract | nuget.org |
Kanject.Core.SqlDatabase.Provider.Dsql |
Amazon Aurora DSQL runtime and DI registration | Commercial license (not on nuget.org) |
Kanject.Core.SqlDatabase.Provider.Dsql.Abstractions |
DSQL contracts (IDsqlEntity, IDsqlRepository<TEntity>, IDsqlUnitOfWork) and configuration |
Commercial license (not on nuget.org) |
Kanject.Core.SqlDatabase.Provider.Dsql.Annotations |
Source generator and analyzers that emit the AOT-safe repositories | Commercial license (not on nuget.org) |
Kanject.Core.SqlDatabase.Provider.Dsql.Annotations.Attributes |
[Table], [PrimaryKey], [Column], [Repository] and related attributes |
Commercial license (not on nuget.org) |
License
Licensed under the Kanject Code Libraries License Agreement (KCLLA); the full text ships in this package as LICENSE.md. Organizations whose trailing-twelve-month gross revenue and total funding raised are each below US$250,000 may use it at no cost under the Free Tier. At or above either threshold a commercial license is required — contact commercial@kanjectbusiness.com.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.3.1 | 123 | 9/27/2026 |
| 1.3.0 | 86 | 9/27/2026 |
| 1.2.7 | 86 | 9/26/2026 |
| 1.2.6 | 119 | 9/7/2026 |
| 1.2.5 | 99 | 8/27/2026 |
| 1.2.4 | 100 | 8/22/2026 |
| 1.2.3 | 113 | 8/10/2026 |
| 1.2.2 | 107 | 8/9/2026 |
| 1.2.1 | 105 | 8/5/2026 |
| 1.2.0 | 101 | 8/5/2026 |
| 1.1.0 | 117 | 8/3/2026 |
| 1.0.5 | 116 | 7/30/2026 |
| 1.0.4 | 120 | 7/18/2026 |
| 1.0.3 | 127 | 7/13/2026 |
| 1.0.2 | 118 | 7/11/2026 |
| 1.0.1 | 116 | 7/11/2026 |
| 1.0.0 | 117 | 7/9/2026 |