DatabaseDtoGenerator.SourceGenerator 0.1.3

There is a newer version of this package available.
See the version list below for details.
dotnet add package DatabaseDtoGenerator.SourceGenerator --version 0.1.3
                    
NuGet\Install-Package DatabaseDtoGenerator.SourceGenerator -Version 0.1.3
                    
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="DatabaseDtoGenerator.SourceGenerator" Version="0.1.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DatabaseDtoGenerator.SourceGenerator" Version="0.1.3" />
                    
Directory.Packages.props
<PackageReference Include="DatabaseDtoGenerator.SourceGenerator" />
                    
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 DatabaseDtoGenerator.SourceGenerator --version 0.1.3
                    
#r "nuget: DatabaseDtoGenerator.SourceGenerator, 0.1.3"
                    
#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 DatabaseDtoGenerator.SourceGenerator@0.1.3
                    
#: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=DatabaseDtoGenerator.SourceGenerator&version=0.1.3
                    
Install as a Cake Addin
#tool nuget:?package=DatabaseDtoGenerator.SourceGenerator&version=0.1.3
                    
Install as a Cake Tool

DatabaseDtoGenerator

A Roslyn incremental source generator that produces CRUD DTOs, repositories, services, paged list queries, and DI registration from annotated EF Core entities.

What gets generated

For an entity like Product, the generator can emit:

  • ProductCreateDto
  • ProductReadDto
  • ProductUpdateDto
  • ProductDeleteDto
  • ProductListQuery
  • IProductRepository, ProductRepositoryBase<TDbContext>, ProductRepository
  • IProductService, ProductServiceBase<TRepository>, ProductService
  • GeneratedCrudServiceCollectionExtensions.AddGeneratedCrudServices()

Basic usage

Annotate an entity with GenerateCrud, mark DTO fields with DtoField, and optionally add operation-level role metadata with DtoRole.

using DatabaseDtoGenerator.Abstractions;
using Microsoft.EntityFrameworkCore;

namespace MyApp.Models;

public sealed class AppDbContext : DbContext
{
}

[GenerateCrud(typeof(AppDbContext))]
[DtoRole(DtoOperation.Create, "Admin")]
[DtoRole(DtoOperation.Read, "Admin", "User")]
public class Product
{
    [DtoField(DtoOperation.Read)]
    public int Id { get; set; }

    [DtoField(DtoOperation.Create | DtoOperation.Read | DtoOperation.Update)]
    public string Name { get; set; } = string.Empty;

    [DtoField(DtoOperation.Create | DtoOperation.Read | DtoOperation.Update)]
    public decimal Price { get; set; }
}

Accumulation DTOs

In addition to CRUD DTOs, you can define named accumulation DTOs directly on model properties with AccumulationDtoField.

  • names are unique within a model
  • referenced virtual model properties must explicitly set NestedDtoName
  • there is no default nested accumulation DTO selection
[GenerateCrud(typeof(AppDbContext))]
public class Category
{
    [DtoField(DtoOperation.Read)]
    [AccumulationDtoField("Tiny")]
    [AccumulationDtoField("Basic")]
    public int Id { get; set; }

    [AccumulationDtoField("Basic")]
    public string Name { get; set; } = string.Empty;
}

[GenerateCrud(typeof(AppDbContext))]
public class Product
{
    [DtoField(DtoOperation.Read)]
    [AccumulationDtoField("Summary")]
    public int Id { get; set; }

    [AccumulationDtoField("Summary")]
    public string Name { get; set; } = string.Empty;

    [AccumulationDtoField("Summary", NestedDtoName = "Basic")]
    public virtual Category? Category { get; set; }

    [AccumulationDtoField("Summary", NestedDtoName = "Tiny")]
    public virtual List<Category> RelatedCategories { get; set; } = new();
}

This generates additional types such as:

  • CategoryTinyAccumulationDto
  • CategoryBasicAccumulationDto
  • ProductSummaryAccumulationDto

and the nested collection keeps the list shape:

public System.Collections.Generic.List<CategoryTinyAccumulationDto> RelatedCategories { get; set; } = default!;

Generated services also expose accumulation DTO operations and mapping hooks, for example:

Task<PagedResult<ProductSummaryAccumulationDto>> ListSummaryAsync(ProductListQuery query, CancellationToken cancellationToken = default);
Task<ProductSummaryAccumulationDto?> GetSummaryByIdAsync(int id, CancellationToken cancellationToken = default);

and protected mapping methods such as:

protected virtual ProductSummaryAccumulationDto MapToSummaryAccumulationDto(Product entity)
protected virtual void MapEntityToSummaryAccumulationDto(Product entity, ProductSummaryAccumulationDto dto)

Dependency injection

The generator also emits a registration helper:

using DatabaseDtoGenerator.Generated;

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("app-db"));

builder.Services.AddGeneratedCrudServices();

Generated CRUD surface

Generated services expose:

Task<PagedResult<ProductReadDto>> ListAsync(ProductListQuery query, CancellationToken cancellationToken = default);
Task<ProductReadDto?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
Task<ProductReadDto?> CreateAsync(ProductCreateDto dto, CancellationToken cancellationToken = default);
Task<ProductReadDto?> UpdateAsync(int id, ProductUpdateDto dto, CancellationToken cancellationToken = default);
Task DeleteAsync(int id, CancellationToken cancellationToken = default);

Generated repositories expose matching persistence methods over entities.

Pagination and list queries

Generated repositories and services include list methods with optional paging.

By default, paging is disabled and all matching items are returned:

var result = await productService.ListAsync(new ProductListQuery());

Set a positive PageSize to enable paging:

var result = await productService.ListAsync(new ProductListQuery
{
    PageNumber = 1,
    PageSize = 20
});

The abstractions package provides:

  • PagedRequest
  • PagedResult<T>

PagedRequest defaults:

  • PageNumber = 1
  • PageSize = 0 (unpaged; returns all items)

Generated repositories normalize invalid paging values and cap positive page sizes at 200.

The generated repository base class also exposes extensibility hooks for list behavior:

  • BuildListQuery() for the default ordered list query
  • ApplyListQuery(...) for custom filtering or sorting
  • NormalizePagedQuery(...) for paging normalization

The generated service base class exposes:

  • BeforeListAsync(...)
  • AfterListAsync(...)
  • CreatePagedReadResult(...)

Customizing list behavior

You can override repository hooks to add filtering or sorting:

public sealed class ProductRepository : ProductRepositoryBase<AppDbContext>
{
    public ProductRepository(AppDbContext dbContext) : base(dbContext)
    {
    }

    protected override IQueryable<Product> ApplyListQuery(IQueryable<Product> query, ProductListQuery listQuery)
        => query.Where(product => product.Price > 0);
}

You can also override service hooks to adjust the paged DTO result.

Naming and output options

GenerateCrudAttribute supports configuration for generated names:

[GenerateCrud(
    typeof(AppDbContext),
    OutputNamespace = "MyApp.Generated",
    DtoSuffix = "Model",
    ServiceSuffix = "CrudService",
    RepositorySuffix = "Store")]
public class Product
{
    [DtoField(DtoOperation.Read)]
    public int Id { get; set; }
}

This generates types like:

  • ProductCreateModel
  • ProductReadModel
  • IProductStore
  • ProductStoreBase<TDbContext>
  • IProductCrudService
  • ProductCrudService

Validation rules

The generator validates configuration values and reports diagnostics when needed:

  • OutputNamespace must be a valid C# namespace
  • DtoSuffix, ServiceSuffix, and RepositorySuffix may contain only letters, digits, and underscores

When an invalid value is provided, the generator reports a diagnostic and falls back to the default naming behavior.

Diagnostics

Current diagnostics include:

  • DDG001 missing DbContext type
  • DDG002 missing key property
  • DDG003 missing readable DTO fields
  • DDG004 invalid DtoField configuration
  • DDG005 invalid DtoRole configuration
  • DDG006 invalid OutputNamespace
  • DDG007 invalid generated type suffix
  • DDG008 invalid AccumulationDtoField configuration
  • DDG009 invalid accumulation DTO reference

Current scope and limitations

Current support focuses on:

  • single-entity CRUD generation
  • operation-level authorization roles
  • paged list generation
  • repository/service extensibility through virtual hooks

Not generated yet:

  • HTTP controllers or endpoints
  • attribute-driven filtering/sorting metadata on list queries
  • property-level authorization rules

End-to-end example

1. Define the entity

using DatabaseDtoGenerator.Abstractions;
using Microsoft.EntityFrameworkCore;

namespace MyApp.Models;

public sealed class AppDbContext : DbContext
{
    public DbSet<Product> Products => Set<Product>();

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }
}

[GenerateCrud(typeof(AppDbContext))]
[DtoRole(DtoOperation.Create, "Admin")]
[DtoRole(DtoOperation.Read, "Admin", "User")]
public class Product
{
    [DtoField(DtoOperation.Read)]
    public int Id { get; set; }

    [DtoField(DtoOperation.Create | DtoOperation.Read | DtoOperation.Update)]
    public string Name { get; set; } = string.Empty;

    [DtoField(DtoOperation.Create | DtoOperation.Read | DtoOperation.Update)]
    public decimal Price { get; set; }
}

2. Register the generated services

using DatabaseDtoGenerator.Generated;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("app-db"));

builder.Services.AddGeneratedCrudServices();

3. Consume the generated service

public sealed class ProductFacade
{
    private readonly IProductService _productService;

    public ProductFacade(IProductService productService)
    {
        _productService = productService;
    }

    public async Task<ProductReadDto?> CreateAsync(CancellationToken cancellationToken = default)
    {
        return await _productService.CreateAsync(new ProductCreateDto
        {
            Name = "Ball",
            Price = 9.99m
        }, cancellationToken);
    }

    public async Task<PagedResult<ProductReadDto>> ListAsync(CancellationToken cancellationToken = default)
    {
        return await _productService.ListAsync(new ProductListQuery
        {
            PageNumber = 1,
            PageSize = 20
        }, cancellationToken);
    }
}

4. Extend the generated repository or service when needed

public sealed class CustomProductRepository : ProductRepositoryBase<AppDbContext>
{
    public CustomProductRepository(AppDbContext dbContext) : base(dbContext)
    {
    }

    protected override IQueryable<Product> ApplyListQuery(IQueryable<Product> query, ProductListQuery listQuery)
        => query.Where(product => product.Price > 0).OrderBy(product => product.Name);
}

Testing

The repository contains:

  • integration tests against the example app
  • source-generator tests for diagnostics and emitted output
  • snapshot-style tests for generated CRUD and registration source

Publishing

Package version

Package versioning is centralized in:

  • Library/Directory.Build.props

Update VersionPrefix before creating a release tag.

Versioning follows semantic versioning:

  • major for breaking changes
  • minor for new backward-compatible features
  • patch for backward-compatible fixes

Build packages locally

dotnet pack Library/src/DatabaseDtoGenerator.Abstractions/DatabaseDtoGenerator.Abstractions.csproj -c Release -o Library/artifacts
dotnet pack Library/src/DatabaseDtoGenerator.SourceGenerator/DatabaseDtoGenerator.SourceGenerator.csproj -c Release -o Library/artifacts

This produces:

  • DatabaseDtoGenerator.Abstractions.<version>.nupkg
  • DatabaseDtoGenerator.Abstractions.<version>.snupkg
  • DatabaseDtoGenerator.SourceGenerator.<version>.nupkg

Publish with GitLab CI

For a step-by-step release checklist, see Library/RELEASE.md.

The repository includes .gitlab-ci.yml with these stages:

  • test
  • pack
  • publish

Publishing to NuGet requires a GitLab CI/CD variable:

  • NUGET_API_KEY

Recommended release flow:

  1. update VersionPrefix in Library/Directory.Build.props
  2. commit and push changes
  3. create and push a tag such as v0.1.3
  4. GitLab automatically runs test, pack, and publish

The publish job pushes both .nupkg and .snupkg files to https://api.nuget.org/v3/index.json.

Install from NuGet

In a consuming project, reference both packages:

<ItemGroup>
  <PackageReference Include="DatabaseDtoGenerator.Abstractions" Version="0.1.3" />
  <PackageReference Include="DatabaseDtoGenerator.SourceGenerator" Version="0.1.3" PrivateAssets="all" />
</ItemGroup>
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.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
0.1.4 119 5/3/2026
0.1.3 100 5/3/2026
0.1.2 108 4/27/2026
0.1.1 110 4/26/2026