NDB.Platform.Data
1.0.0
dotnet add package NDB.Platform.Data --version 1.0.0
NuGet\Install-Package NDB.Platform.Data -Version 1.0.0
<PackageReference Include="NDB.Platform.Data" Version="1.0.0" />
<PackageVersion Include="NDB.Platform.Data" Version="1.0.0" />
<PackageReference Include="NDB.Platform.Data" />
paket add NDB.Platform.Data --version 1.0.0
#r "nuget: NDB.Platform.Data, 1.0.0"
#:package NDB.Platform.Data@1.0.0
#addin nuget:?package=NDB.Platform.Data&version=1.0.0
#tool nuget:?package=NDB.Platform.Data&version=1.0.0
NDB.Platform.Data
<div align="center">
The data layer library for every NDB Platform project
Built by PT. Navigate Digital Boundaries — Navigate Digital Boundaries
</div>
What is NDB.Platform.Data?
NDB.Platform.Data is the data layer package in the NDB Platform ecosystem. It sits on top of NDB.Platform.Core and adds everything you need to work with a database in a clean, consistent way across every NDB project.
Three production-ready building blocks:
- Audit Trail — intercepts
SaveChangesand records every mutation automatically, with actor identity and field-level diffs - IQueryable Extensions — composable pagination, filter, sort, and search extensions that work with any EF Core provider
- CRUD Code Generator — scaffolds a complete handler + controller stack from an EF entity in one command
Goal: Eliminate the repetitive boilerplate that every data layer project requires — audit wiring, paging patterns, search utilities, and scaffold setup.
Installation
dotnet add package NDB.Platform.Data
NDB.Platform.Coreis installed automatically as a dependency.
Install your database provider in the consuming project — this library is provider-agnostic and does not lock you to PostgreSQL or SQL Server:
# PostgreSQL
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
# SQL Server
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Table of Contents
| Namespace | Documentation | Description |
|---|---|---|
NDB.Platform.Audit |
📄 Audit | Automatic EF Core audit trail — actor, timestamp, field-level diffs |
NDB.Platform.Ef |
📄 Ef | IQueryable extensions — pagination, filter, sort, search, constraints |
NDB.Platform.CodeGen |
📄 CodeGen | Handlebars CRUD scaffold — handlers, DTOs, controllers |
| — | 🧪 Tests | Unit test suite — net8.0 + net10.0, coverage ≥ 80% |
Minimum Setup
// Program.cs
using NDB.Platform.Audit;
// Register audit services
builder.Services.AddNdbAudit();
// Register your audit storage implementation
builder.Services.AddScoped<IAuditWriter, MyAuditWriter>();
// Register EF Core with your provider
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
Core Concepts
1 · Audit Trail
Every enterprise application needs to answer "who changed what and when?" NDB.Platform.Data wires this up automatically through EF Core's ChangeTracker.
Mark an entity for auditing:
public class Invoice : IAuditableEntity
{
public Guid Id { get; set; }
public string Number { get; set; } = default!;
public decimal Amount { get; set; }
public string Status { get; set; } = "DRAFT";
}
Wire SaveChanges in your DbContext:
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<Invoice> Invoices => Set<Invoice>();
public override Task<int> SaveChangesAsync(CancellationToken ct = default)
=> this.SaveChangesWithAuditAsync(ct);
}
Implement IAuditWriter (you decide where audit entries go):
public class DbAuditWriter(AppDbContext db) : IAuditWriter
{
public async Task WriteAsync(IEnumerable<AuditEntry> entries, CancellationToken ct)
{
db.AuditLogs.AddRange(entries.Select(e => new AuditLog
{
Entity = e.Entity,
EntityId = e.EntityId,
Action = e.Action,
Actor = e.Actor,
ActorId = e.ActorId,
Timestamp = e.Timestamp,
Changes = JsonSerializer.Serialize(e.Changes)
}));
await db.SaveChangesAsync(ct);
}
}
What gets captured per operation:
Entity — class name (e.g. "Invoice")
EntityId — primary key, serialized to string
Action — "Added" | "Modified" | "Deleted"
Actor — username from JWT claim
ActorId — user ID from JWT sub claim
CorrelationId — request correlation ID
Timestamp — DateTime.UtcNow
Changes[] — field-level diffs (Modified only)
└ Property — property name (e.g. "Status")
└ OldValue — value before (e.g. "DRAFT")
└ NewValue — value after (e.g. "APPROVED")
Actor is resolved automatically via AuditActorBehavior (a Mediator pipeline behavior registered by AddNdbAudit()). It reads actor identity from IActorAccessor, which is populated from JWT claims when you call AddNdbJwt() from NDB.Platform.API. For background jobs, the fallback is SystemActorAccessor (returns "system").
You can also return entries alongside the row count if you need to react to them:
var (rows, entries) = await context.SaveChangesWithAuditResultAsync(ct);
// entries: IReadOnlyList<AuditEntry>
2 · IQueryable Extensions
A set of composable extensions that cover the most common EF Core query patterns. All extensions work with any provider (PostgreSQL, SQL Server, SQLite, InMemory).
Pagination
using NDB.Platform.Ef;
// With projection:
PagedResult<UserResponse> paged = await db.Users
.Where(u => u.IsActive)
.OrderBy(u => u.CreatedAt)
.ToPagedResultAsync(
paging: new PagingRequest(Page: 1, PageSize: 20),
selector: u => new UserResponse { Id = u.Id, Name = u.Name, Email = u.Email },
ct: ct);
// paged.Items — List<UserResponse>
// paged.TotalItems — total row count (before paging)
// paged.TotalPages — total pages
// paged.Page — current page
// paged.PageSize — items per page
// Without projection (entities directly):
PagedResult<User> paged = await db.Users.ToPagedResultAsync(paging, ct);
// Flat list (no pagination):
ListResult<RoleDto> list = await db.Roles
.Where(r => r.IsActive)
.ToListResultAsync(r => new RoleDto(r.Id, r.Name), ct);
Filter, Sort, Search
// Apply all at once — composable, order does not matter:
var paged = await db.Products
.ApplyFilter(new FilterRequest
{
Keyword = "laptop",
Fields = ["Name", "Description", "Sku"]
})
.ApplySort(new SortRequest
{
SortBy = "Price",
SortDirection = SortDirection.Asc
})
.ToPagedResultAsync(paging, p => p.Adapt<ProductResponse>(), ct);
// Multiple sorts — first is OrderBy, the rest are ThenBy:
var result = await db.Orders
.ApplySorts([
new SortRequest { SortBy = "Status" },
new SortRequest { SortBy = "CreatedAt", SortDirection = SortDirection.Desc }
])
.ToListAsync(ct);
Case-Insensitive Search
// WhereContainsIgnoreCase is provider-aware:
// — PostgreSQL → ILIKE (native, case-insensitive on all collations)
// — SQL Server / SQLite / InMemory → EF.Functions.Like (fallback)
var users = await db.Users
.WhereContainsIgnoreCase(u => u.Email, searchTerm, dbContext)
.ToListAsync(ct);
// ApplySearch uses ToLowerInvariant+Contains — always safe in unit tests,
// but does NOT generate ILIKE in Postgres (evaluates in-memory):
var users = db.Users.ApplySearch("john", ["Name", "Email"]);
Use
WhereContainsIgnoreCasefor production search on PostgreSQL. UseApplySearchin unit tests where there is no real database.
Constraint Violation Detection
try
{
await db.SaveChangesAsync(ct);
}
catch (DbUpdateException ex) when (ex.IsUniqueViolation())
{
// PostgreSQL: error 23505 / SQL Server: "duplicate key"
return Result.Conflict("Email is already in use.");
}
catch (DbUpdateException ex) when (ex.IsForeignKeyViolation())
{
// PostgreSQL: error 23503 / SQL Server: "FOREIGN KEY constraint"
return Result.BadRequest("Referenced record does not exist.");
}
catch (Exception ex) when (ex.IsConcurrencyViolation())
{
return Result.Conflict("Record was modified by another request. Please retry.");
}
These helpers use InnerException.Message pattern matching, not provider-specific exception types, so your handlers stay portable across databases.
Tracking Utilities
// Detach all tracked entities (useful before switching to read-only mode):
dbContext.DetachAll();
// Check if an entity is currently tracked:
bool tracked = dbContext.IsTracked(user);
// Guard — throw if an entity is in a non-idle state:
dbContext.EnsureNotTracked(entity);
// Throws InvalidOperationException if state is not Detached or Unchanged
3 · CRUD Code Generator
Scaffolds a full, ready-to-compile handler + controller stack from an EF entity. No more copying the same GetPagedHandler, AddHandler, DeleteHandler boilerplate for every new entity.
PowerShell (recommended — fastest path):
After installing the package, a PowerShell script is available at CodeTemplates/CodeGen/Scaffolding.ps1:
cd MyApp/
.\CodeTemplates\CodeGen\Scaffolding.ps1 `
-Entity "Invoice" `
-Namespace "MyApp.Core.Billing" `
-Provider "PostgreSql"
# -Provider "SqlServer" also supported
Generated output for -Entity Invoice:
src/MyApp.Core/Billing/Invoice/
├── Command/
│ ├── AddInvoiceHandler.cs ← validates + inserts, returns Result<Guid>
│ ├── EditInvoiceHandler.cs ← validates + updates, returns Result
│ └── DeleteInvoiceHandler.cs ← soft-delete if Active field exists, else hard-delete
├── Query/
│ ├── GetInvoiceByIdHandler.cs ← returns Result<InvoiceResponse>
│ ├── GetInvoiceListHandler.cs ← returns ListResult<InvoiceResponse>
│ └── GetInvoicePagedHandler.cs ← returns PagedResult<InvoiceResponse>
├── Object/
│ ├── InvoiceRequest.cs ← input DTO with FluentValidation hooks
│ ├── InvoiceResponse.cs ← output DTO
│ └── InvoiceContract.cs ← constant field names (prevents magic strings)
└── InvoiceController.cs ← REST controller wired to all 6 handlers
All generated files follow the same conventions as the rest of NDB Platform (Result pattern, CQRS, Mapster, FluentValidation). Edit them freely — they are regular source files once generated.
Programmatic API (for tooling or custom scaffold runners):
// Register:
builder.Services.AddNdbCodeGen();
// Generate from a live DbContext model:
public class ScaffoldRunner(CrudTemplateEngine engine, AppDbContext db)
{
public void GenerateAll(string outputPath)
{
foreach (var entity in db.Model.GetEntityTypes())
{
engine.Generate(
entity: entity,
contextNs: "MyApp.Data",
modelNs: "MyApp.Data.Model",
basePath: outputPath);
}
}
}
Custom templates:
Copy any .hbs file from CodeTemplates/CodeGen/ into your project, modify it, and register your own ITemplateProvider:
services.AddSingleton<ITemplateProvider>(
_ => new EmbeddedTemplateProvider(typeof(MyTemplates).Assembly));
Available template tokens:
| Token | Example |
|---|---|
{{ns}} |
MyApp.Core.Billing |
{{Entity}} |
Invoice |
{{PkName}} |
Id |
{{PkType}} |
Guid |
{{AddAttributes}} |
Generated property declarations for AddCommand |
{{ResponseAttributes}} |
Generated property declarations for Response DTO |
{{Block}} |
true if the entity has an Active field (soft-delete) |
{{IsView}} |
true if the entity is mapped to a database view |
Full Program.cs Example
using NDB.Platform.Audit;
var builder = WebApplication.CreateBuilder(args);
// NDB Core (CQRS + mapping):
builder.Services.AddNdbCqrs(typeof(Program).Assembly);
builder.Services.AddMediator(opt => opt.ServiceLifetime = ServiceLifetime.Scoped);
builder.Services.AddNdbMapping(typeof(Program).Assembly);
// NDB Data (audit trail):
builder.Services.AddNdbAudit();
builder.Services.AddScoped<IAuditWriter, DbAuditWriter>();
// EF Core:
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
var app = builder.Build();
app.Run();
Full Handler Example
// Query with pagination, search, and sort — all wired up:
public sealed record GetInvoicesQuery(
string? Search,
string? SortBy,
SortDirection SortDirection = SortDirection.Desc,
int Page = 1,
int PageSize = 20) : IQuery<Result<PagedResult<InvoiceResponse>>>;
public sealed class GetInvoicesHandler(AppDbContext db)
: IQueryHandler<GetInvoicesQuery, Result<PagedResult<InvoiceResponse>>>
{
public async ValueTask<Result<PagedResult<InvoiceResponse>>> Handle(
GetInvoicesQuery q, CancellationToken ct)
{
var result = await db.Invoices
.WhereContainsIgnoreCase(i => i.Number, q.Search, db)
.ApplySort(new SortRequest { SortBy = q.SortBy, SortDirection = q.SortDirection })
.ProjectToType<InvoiceResponse>()
.ToPagedResultAsync(new PagingRequest(q.Page, q.PageSize), ct);
return Result.Success(result);
}
}
Requirements
| Requirement | Detail |
|---|---|
| .NET | 8.0 or 10.0 |
| NDB.Platform.Core | 1.0.0 — installed automatically |
| EF Core provider | Install Npgsql.EFCore.PostgreSQL or EF Core SqlServer in the consuming project |
AddNdbCqrs() + AddMediator() |
Required if using AuditActorBehavior (the default) |
IAuditWriter implementation |
Required for audit entries to be persisted |
Ecosystem
| Package | Version | Description |
|---|---|---|
| NDB.Platform.Core | 1.0.0 | Foundation — Result pattern, CQRS, utilities, shared contracts |
| NDB.Platform.Data | 1.0.0 | Data layer ← you are here |
| NDB.Platform.API | coming soon | Web layer — JWT auth, Swagger, Hangfire, permission middleware |
Repository
github.com/ndbco/NDB.Platform.Data
About NDB
PT. Navigate Digital Boundaries — Indonesian technology company focused on enterprise digital platform development.
🌐 ndb.co.id — Navigate Digital Boundaries
License
GPL v3 — Copyright © PT. Navigate Digital Boundaries
| 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 was computed. 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
- Handlebars.Net (>= 2.1.6)
- Mediator.Abstractions (>= 2.1.7)
- Microsoft.EntityFrameworkCore (>= 8.0.14)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.14)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
- NDB.Platform.Core (>= 1.0.0)
-
net8.0
- Handlebars.Net (>= 2.1.6)
- Mediator.Abstractions (>= 2.1.7)
- Microsoft.EntityFrameworkCore (>= 8.0.14)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.14)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
- NDB.Platform.Core (>= 1.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on NDB.Platform.Data:
| Package | Downloads |
|---|---|
|
NDB.Platform.Api
The web API layer library for NDB Platform projects. Wraps ASP.NET Core infrastructure into consistent, pre-configured building blocks: Authentication — JWT Bearer dual-token (access 15 min / refresh 7 days), HMAC-SHA256, fail-fast option validation, Token-Expired header support, and configurable token refresh. Authorization — Granular permission-based authorization via IPermissionResolver (RequirePermissionAttribute + dynamic policy provider), role-based shorthand (NdbRequireRoleAttribute), and superadmin bypass via JWT claim or role. Result → HTTP — ResultActionFilter auto-converts Result/Result<T>/PagedResult<T>/ CollectionResult<T> return values to a consistent ApiResponse JSON envelope. Middleware — CorrelationIdMiddleware (X-Correlation-ID propagation), RequestLoggingMiddleware (method/path/duration/actor/status), GlobalExceptionHandler (unhandled exceptions → 500 ApiResponse JSON). Hangfire — Provider-agnostic background job wrapper. Install the storage provider in the consuming project. Dashboard protected by timing-safe Basic Auth. Swagger — Swashbuckle pre-configured with JWT Bearer security scheme and API versioning. Additional — CORS policy, ForwardedHeaders (reverse proxy), health check endpoints (/health/live, /health/ready), API versioning (URL segment), BaseController with lazy Mediator and current-user properties, IPdfRenderer abstraction. Requires NDB.Platform.Core and NDB.Platform.Data (installed automatically). |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 71 | 5/31/2026 |