VSATutorialSeptember 2026 · 7 min read

VSA Todo App — Part 11: Auto-Number Generation & EF Core Fluent API Configuration

TL;DR

Part 11 dives into auto-number generation in VSA — the consonant-based template system from Blazor CRM that produces readable identifiers like "BKG/2026/00001". Covers EF Core Fluent API entity configuration, shadow properties, value converters, and how auto-numbers integrate with VSA handlers without breaking the slice boundary.

Part 11 of 20 in the VSA Todo App Tutorial Series|← Previous: Part 10|Next: Part 12 →

Help Us Grow

Love this tutorial? Explore our ready-to-use enterprise starter kits built with VSA in .NET 10.

GUIDs are great for database keys but terrible for humans. When a user calls support about order "a3f2b1c4-d5e6-7890-abcd-ef1234567890", both parties are frustrated. The Blazor CRM solves this with auto-number generation — a system that produces short, readable, sequential identifiers like "BKG/2026/00001" using consonant-based templates. This part shows how auto-numbers work in VSA, how they integrate with handlers, and how EF Core Fluent API configures the underlying entity model.

The auto-number system is an extension method on AppDbContext called GenerateAutoNumberAsync. It takes an entity name (used to find the right sequence), a prefix template (with placeholders like {Year}), and produces the next sequential number. The system uses a dedicated AutoNumber table in the database to track sequences per entity type. This is called inside the Create handler before entity creation, keeping the handler clean while providing production-quality identifiers.

The GenerateAutoNumberAsync Extension

The extension method queries the AutoNumber table for the entity's current sequence, increments it, formats the prefix template, and returns the result. The consonant-based short name is generated by taking the first 3 consonants from the entity name — "Booking" becomes "BKG", "Todo" becomes "TD". The {Year} placeholder is replaced with the current year:

var autoNo = await _context.GenerateAutoNumberAsync(
    entityName: nameof(Data.Entities.Booking),
    prefixTemplate: $"BKG/{{Year}}/",
    ct: cancellationToken);

The handler stores the auto-number on the entity before saving. The entity's AutoNumber property is a simple string — no special database configuration needed. The extension method handles all the sequencing logic, concurrency protection, and formatting.

EF Core Fluent API Configuration

While auto-numbers are simple strings on the entity, other configurations benefit from EF Core's Fluent API. Shadow properties store data without cluttering the entity class — useful for audit fields managed by the DbContext. Value converters transform data between the database and the entity — useful for enums stored as strings:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<Todo>(entity =>
    {
        entity.HasKey(e => e.Id);
        entity.Property(e => e.Name).HasMaxLength(200).IsRequired();
        entity.Property(e => e.Description).HasMaxLength(1000);
        entity.Property(e => e.AutoNumber).HasMaxLength(50);

        // Shadow property for soft delete (not on the entity class)
        entity.Property<bool>("IsDeleted").HasDefaultValue(false);

        // Value converter for enum stored as string
        entity.Property(e => e.Priority)
            .HasConversion<string>()
            .HasMaxLength(20);
    });

    // Global query filter for soft delete
    builder.Entity<Todo>().HasQueryFilter(e => !EF.Property<bool>(e, "IsDeleted"));
}

The Fluent API configuration lives in OnModelCreating in the DbContext — not in the feature folder. This is intentional: entity configuration is infrastructure, not feature logic. The feature handlers work with the configured entities without knowing about shadow properties or query filters. This separation keeps VSA slices focused on business logic.

Concurrency Protection for Auto-Numbers

Auto-numbers must be unique and sequential, even under concurrent requests. The GenerateAutoNumberAsync method uses a database transaction with row-level locking to prevent duplicate numbers. It queries the current max number for the entity type within a transaction, increments it, saves the new sequence, and returns the formatted number. If two requests arrive simultaneously, the database lock ensures they get different numbers. This is production-grade concurrency handling that most auto-number implementations miss.

Integration with VSA Handlers

The auto-number generation is called inside the Create handler, right after validation but before entity creation. The handler doesn't need to know about the AutoNumber table or the sequencing logic — it just calls the extension method and assigns the result. This is the VSA pattern for cross-cutting concerns: build infrastructure as extensions, call them from handlers, keep handlers focused on their feature's business logic. The auto-number system is available to any feature without coupling features together.

Key Takeaways

  • Auto-number generation produces human-readable identifiers like "BKG/2026/00001" using consonant-based templates
  • EF Core Fluent API configures entities in OnModelCreating — shadow properties and query filters stay out of feature handlers
  • Value converters transform data between database and entity — enums as strings, dates as UTC, complex types as JSON
  • Database transactions with row-level locking prevent duplicate auto-numbers under concurrent requests
  • Cross-cutting infrastructure (auto-numbers, audit, soft delete) lives as extensions — handlers call them without coupling

Frequently Asked Questions

Q: How to generate sequential IDs in VSA without GUIDs?

Use a dedicated auto-number table with row-level locking in a transaction. Query the current sequence, increment it, format the prefix, and return. The Blazor CRM's GenerateAutoNumberAsync extension implements this pattern. Call it in the Create handler before entity creation.

Q: Should entity configuration live in the feature folder or the DbContext?

In the DbContext's OnModelCreating. Entity configuration is infrastructure, not feature logic. Shadow properties, query filters, and value converters are configured once and apply everywhere. Feature handlers work with configured entities without knowing about the configuration details.

Q: How to avoid race conditions with auto-numbers in VSA?

Use a database transaction with row-level locking. Query the current max number, increment, save the new sequence, and return — all within the transaction. The database ensures only one request gets each number. For high concurrency, consider a database sequence.

Q: What are shadow properties in EF Core and why use them in VSA?

Shadow properties are columns in the database with no corresponding property on the entity class. They're perfect for infrastructure concerns like IsDeleted or tenant IDs that handlers shouldn't touch. Configure them once in OnModelCreating; EF Core manages them transparently.

Part 11 of 20 in the VSA Todo App Tutorial Series|← Previous: Part 10|Next: Part 12 →

Ready to study real VSA code?

Every Indotalent product is a complete .NET 10 application built with Vertical Slice Architecture. Complete source code — $21 each.

Explore Products

Looking for a ready-to-use traditional monolithic multilayered clean architecture?

Monolithic Clean Architecture — 1,300+ devs, 500+ forks. Clean Arch + CQRS + Repository Pattern. Free & open source for commercial use.

Star on GitHub