DKNet is not a framework you adopt wholesale — it is 28 independent NuGet packages. Getting started means picking the two or three you need and wiring them up. This page covers the prerequisites, the smallest setup that actually runs, and where to go next.
- .NET 10.0 SDK — every package you reference targets
net10.0(the two Roslyn source generators,DKNet.EfCore.DtoGeneratorandDKNet.SlimBus.Generators, targetnetstandard2.0so the compiler can load them — that does not change what your app targets), andsrc/global.jsonpins SDK10.0.0withrollForward: latestMajor. - Visual Studio 2022 (17.13+), Visual Studio Code, or JetBrains Rider.
- A relational database if you use the EF Core packages. The samples below use SQL Server; the packages
themselves are provider-agnostic apart from
DKNet.EfCore.Relational.Helpersand the two relational idempotency stores. - Working knowledge of EF Core. Familiarity with DDD helps, but no package requires it.
The Which package do I need? table maps problems to packages. A typical DDD-style API starts with four:
# Entity base classes and the domain-event contracts
dotnet add package DKNet.EfCore.Abstractions
# Entity configuration discovery, global query filters, seeding, GUID v7 keys
dotnet add package DKNet.EfCore.Extensions
# Querying and persistence through IRepositorySpec
dotnet add package DKNet.EfCore.Specifications
# CQRS handlers with automatic SaveChanges (brings DKNet.SlimBus.Extensions with it)
dotnet add package DKNet.SlimBus.Extensions.EfCoreAdd more as the need appears — nothing above depends on the others being present, and no package needs a companion "core" package.
DKNet.EfCore.ReposandDKNet.EfCore.Repos.Abstractionswere removed and were never published to NuGet.dotnet add packagewill not find them. UseDKNet.EfCore.Specifications— see Migrating-Repos-To-Specifications if you are upgrading off them.
Derive from AuditedEntity (Guid-keyed, with created/updated tracking and a domain-event queue) or from plain
Entity if you do not want the audit fields. Change state through methods, not public setters:
using DKNet.EfCore.Abstractions.Entities;
public class Product : AuditedEntity
{
private Product() { } // EF Core
public static Product Create(string name, decimal price, string createdBy)
{
var product = new Product { Name = name, Price = price, IsActive = true };
product.SetCreatedBy(createdBy);
return product;
}
public string Name { get; private set; } = string.Empty;
public decimal Price { get; private set; }
public bool IsActive { get; private set; }
public void Deactivate(string updatedBy)
{
IsActive = false;
SetUpdatedBy(updatedBy);
}
}AddDbContextWithHook<TDbContext> registers the DbContext and the shared hook interceptor in one call;
UseAutoConfigModel<TContext>() is a DbContextOptionsBuilder<TContext> extension, so it goes here rather than
in OnModelCreating:
using DKNet.EfCore.Hooks; // AddDbContextWithHook
using DKNet.EfCore.Specifications; // AddSpecRepo
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextWithHook<AppDbContext>(options => options
.UseSqlServer(builder.Configuration.GetConnectionString("Default")!)
// Discovers every IEntityTypeConfiguration<T> in AppDbContext's assembly
.UseAutoConfigModel<AppDbContext>());
// One IRepositorySpec serves every entity type in the context
builder.Services.AddSpecRepo<AppDbContext>();
var app = builder.Build();
app.Run();AddDbContextWithHook is only needed when something hooks into SaveChanges — domain events, audit logs, or
ownership stamping. If none of those are in play, a plain AddDbContext<AppDbContext> works and
UseAutoConfigModel<AppDbContext>() still applies.
A Specification<TEntity> is configured from its constructor and carries the filter, includes, and ordering as
one reusable object:
using DKNet.EfCore.Specifications.Definitions;
using DKNet.EfCore.Specifications.Extensions;
using DKNet.EfCore.Specifications.Repositories;
public sealed class ActiveProductsSpec : Specification<Product>
{
public ActiveProductsSpec()
{
WithFilter(p => p.IsActive);
AddOrderBy(p => p.Name);
}
}
public sealed class Catalogue(IRepositorySpec repo)
{
public Task<IList<Product>> ActiveAsync(CancellationToken cancellationToken = default) =>
repo.ToListAsync(new ActiveProductsSpec(), cancellationToken);
}Every ring of the onion is a separate package and every dependency points inward. The Architecture Guide has the full picture, including the package dependency graph and two end-to-end walkthroughs (an HTTP request and a domain event):
- Which package do I need? — pick the rest by problem.
- Architecture Guide — what a package may depend on, and how a request and an event travel through the suite.
- Configuration & Setup — the four registration conventions the packages share, and which page owns each package's own option table.
- Examples & Recipes — a CRUD slice, domain events, specifications, multi-tenancy, blob storage.
- SlimBus.ApiEndpoints template — a complete reference implementation in the DKNet.Templates repository.
| Goal | Read next |
|---|---|
| A CRUD API with commands and queries separated | DKNet.SlimBus.Extensions, then Examples |
| Side effects that run after a write commits | DKNet.EfCore.Events, then A domain event end to end |
| Row-level isolation between tenants or owners | DKNet.EfCore.DataAuthorization, then Examples |
A POST a client can safely retry |
DKNet.AspCore.Idempotency on its own for local development, plus one store package for deployed traffic |
| Files in Azure, S3, or on disk behind one interface | DKNet.Svc.BlobStorage.Abstractions |
- Documentation: Documentation hub
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Contributing: Contributing Guide