DKNet

Generate CRUD requests from attributed members

Part of DKNet.SlimBus.Generators. Mark a public constructor or method with [CrudCreate], [CrudUpdate] or [CrudAction] — all three live in DKNet.EfCore.Abstractions.Attributes, so the domain layer takes on no messaging dependency — and the generator turns it into a sealed partial record request:

// Domain project
public class Product : Entity
{
    private Product() { } // EF

    [CrudCreate]
    public Product([Required, MaxLength(100)] string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    public string Name { get; private set; } = null!;
    public decimal Price { get; private set; }

    [CrudUpdate]
    public void UpdatePrice([Range(0, 1_000_000)] decimal price) => Price = price;
}

// API project
[GenerateDto(typeof(Product))]
public partial record ProductDto;

emits — verbatim, from obj/.../generated/.../ProductCrudRequests.g.cs after compiling the Quick Start project:

// <auto-generated by DKNet.SlimBus.Generators />
#nullable enable
namespace QsApi.Crud;

/// <summary>Create request generated from Product's [CrudCreate] constructor.</summary>
public sealed partial record CreateProductRequest : global::DKNet.SlimBus.Extensions.Fluents.Requests.IWitResponse<global::Api.ProductDto>
{
    /// <summary>Maps to constructor parameter 'name'.</summary>
    [global::System.ComponentModel.DataAnnotations.Required]
    [global::System.ComponentModel.DataAnnotations.MaxLength(100)]
    public required string Name { get; init; }

    /// <summary>Maps to constructor parameter 'price'.</summary>
    public required decimal Price { get; init; }
}

/// <summary>Update request generated from Product.UpdatePrice.</summary>
public sealed partial record UpdatePriceProductRequest :
    global::DKNet.SlimBus.Extensions.Fluents.Requests.IWitResponse<global::Api.ProductDto>,
    global::DKNet.SlimBus.Extensions.Fluents.Requests.IWithKey<global::System.Guid>
{
    /// <summary>The target Product identifier (bound from route).</summary>
    public global::System.Guid Id { get; set; }

    /// <summary>Maps to method parameter 'price'.</summary>
    [global::System.ComponentModel.DataAnnotations.Range(0, 1000000)]
    public required decimal Price { get; init; }
}

/// <summary>Delete request generated for Product, carrying the target's key bound from the route.</summary>
public sealed partial record DeleteProductRequest : global::DKNet.SlimBus.Extensions.Fluents.Requests.IWithKey<global::System.Guid>
{
    /// <summary>The target Product identifier (bound from route).</summary>
    public global::System.Guid Id { get; set; }
}

![An attributed member is checked for public accessibility, then the compiling project’s [GenerateDto] DTO and the entity’s IEntity key are resolved; any failure reports DKCRUDGEN001/002/004/006 instead of emitting the request.](/DKNet/DKNet.SlimBus.Generators/diagrams/generate-crud-requests.svg)

The generator scans both the compiling project and its referenced assemblies, so an entity in a separate Domain project is discovered from the API project with nothing redeclared. Referenced assemblies whose name is, or starts with a dotted prefix of, System, Microsoft, netstandard, mscorlib, FluentResults, Mapster, SlimMessageBus, Shouldly, or xunit are skipped — the match is exact-or-dot-prefixed, so a project named Systemic.Domain is still scanned.

DTO resolution. The generated IWitResponse<TDto> needs a concrete DTO, resolved at generation time by scanning the compiling project for the partial record marked [GenerateDto(typeof(TEntity))] (from DKNet.EfCore.DtoGenerator). Exactly one match is required — zero is DKCRUDGEN001, more than one is DKCRUDGEN002 — because the domain-side attribute cannot reference an API-layer type itself. GenerateDtoAttribute is internal, which is fine for a package consumer: the attribute ships as a source file compiled directly into the consuming assembly.

Request shape. Properties mirror the member’s parameters (PascalCase names, original types), required unless the parameter is nullable. System.ComponentModel.DataAnnotations attributes on the parameters ([Required], [Range], …) are copied onto the generated properties — metadata only, see Gotchas.

⚙️ Configuration reference

Option Type Default Effect
[CrudCreate].Name / [CrudUpdate].Name string? null Overrides the generated request type name (default Create{Entity}Request / {Method}{Entity}Request).

🧱 Where it fits

The entity must implement DKNet.EfCore.Abstractions.Entities.IEntity<TKey> (DKCRUDGEN006), and every marked member must be public (DKCRUDGEN004). DKNet.EfCore.DtoGenerator supplies the DTO this step resolves against; Generate handlers is what dispatches the request this step produces.

⚠️ Gotchas & limits