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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NDB.Platform.Data" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NDB.Platform.Data" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="NDB.Platform.Data" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NDB.Platform.Data --version 1.0.0
                    
#r "nuget: NDB.Platform.Data, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NDB.Platform.Data@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NDB.Platform.Data&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=NDB.Platform.Data&version=1.0.0
                    
Install as a Cake Tool

NDB.Platform.Data

<div align="center">

The data layer library for every NDB Platform project

Built by PT. Navigate Digital BoundariesNavigate Digital Boundaries

NuGet NuGet Downloads License: GPL v3 .NET

</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 SaveChanges and 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.Core is 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);
// 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);
// 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 WhereContainsIgnoreCase for production search on PostgreSQL. Use ApplySearch in 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.idNavigate Digital Boundaries


License

GPL v3 — Copyright © PT. Navigate Digital Boundaries

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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