NashTech Blog

Automating Code Mappings in .NET with Roslyn Source Generators

Table of Contents

Use Case

Many business apps store reference codes in a database and reuse them across services, UI labels, and rules. Example:

Code Description
1010001 Processing
1010002 Completed
1010003 Cancelled

The traditional pattern is to hard-code these values in C#:

public static class OperationCodes
{
    public const string Processing = "1010001";
    public const string Completed  = "1010002";
    public const string Cancelled  = "1010003";
}

Problems: every DB change requires code edits and redeploys, mismatches can slip in, and the process is tedious.

Solution: Roslyn Source Generators

Roslyn Source Generators create C# code during compilation. Instead of hand-writing constants, the compiler emits them automatically from your source of truth (DB, JSON, etc.).

  • Runs at compile-time (no runtime reflection or warm-up).
  • Works in .NET 6/7/8+ with Visual Studio 2022 and the dotnet CLI.
  • Generator project should target netstandard2.0 for analyzer compatibility.

Project Layout

CodeGenDemo.sln
├─ CodeGenDemo/                 ← Console app (uses generated code)
│  └─ CodeGenDemo.csproj
└─ CodeGenDemo.Generators/      ← Roslyn source generator (analyzer)
   └─ CodeGenDemo.Generators.csproj

1) Generator Project (CodeGenDemo.Generators)

CodeGenDemo.Generators.csproj — target netstandard2.0 and reference Roslyn packages:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
  </ItemGroup>
</Project>

OperationCodeGenerator.cs — emits the OperationCodes class:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace CodeGenDemo.Generators
{
    [Generator]
    public class OperationCodeGenerator : ISourceGenerator
    {
        public void Initialize(GeneratorInitializationContext context) { }

        public void Execute(GeneratorExecutionContext context)
        {
            // In a real app, load from SQL/JSON/config.
            var codes = new (string Code, string Name)[]
            {
                ("1010001", "Processing"),
                ("1010002", "Completed"),
                ("1010003", "Cancelled")
            };

            var sb = new StringBuilder();
            sb.AppendLine("namespace CodeGenDemo");
            sb.AppendLine("{");
            sb.AppendLine("    public static class OperationCodes");
            sb.AppendLine("    {");

            foreach (var c in codes)
            {
                var safeName = c.Name.Replace(" ", "");
                sb.AppendLine($"        public const string {safeName} = \"{c.Code}\";");
            }

            sb.AppendLine("    }");
            sb.AppendLine("}");

            context.AddSource("OperationCodes.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
        }
    }
}

Why netstandard2.0? Analyzers are loaded by the compiler’s host. Targeting net8.0 often causes load failures like: Could not load file or assembly 'System.Runtime, Version=8.0.0.0'. netstandard2.0 avoids this.

2) Console App Project (CodeGenDemo)

CodeGenDemo.csproj — reference the generator as an Analyzer (not a normal library):

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\\CodeGenDemo.Generators\\CodeGenDemo.Generators.csproj"
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="false" />
  </ItemGroup>
</Project>

Important: Build the project before using OperationCodes. The source generator creates OperationCodes.g.cs at compile-time, so the class and its constants don’t exist until after a build. In microservices, you can generate the code in a shared service, package it as a NuGet, and let other services consume it safely.

Program.cs — use the generated constants:

using System;

namespace CodeGenDemo
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine(OperationCodes.Processing);
            Console.WriteLine(OperationCodes.Completed);
            Console.WriteLine(OperationCodes.Cancelled);
        }
    }
}

Build & Verify

  1. Build the solution (Visual Studio or dotnet build).
  2. The generator runs and emits OperationCodes.g.cs under:
    CodeGenDemo/obj/Debug/net8.0/generated/CodeGenDemo.Generators/OperationCodes.g.cs
  3. Run the app (F5 / Ctrl+F5 or dotnet run --project CodeGenDemo).

Expected output:

1010001
1010002
1010003

Adapting to Your Data Source

Replace the hardcoded array in OperationCodeGenerator with a loader that reads from your chosen source of truth:

  • JSON/CSV file in the solution (easy, deterministic).
  • SQL via a pre-build step that exports a JSON file for the generator to read (generators should avoid runtime DB connections).

The generator consumes that file at compile time and regenerates constants automatically.

Benefits

  • No manual syncing between DB and code.
  • Compile-time safety with strongly typed constants.
  • Zero runtime overhead (all code is generated at build time).
  • Works across environments (CI, CLI, VS) with no extensions.

Conclusion

Roslyn Source Generators are the modern, reliable way to keep reference code mappings in sync across your .NET apps. Use a generator project targeting netstandard2.0, reference it as an analyzer from your app, and let the compiler create (and keep updating) your OperationCodes class automatically.

Picture of Huỳnh Minh Tú

Huỳnh Minh Tú

Senior Software Engineer

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top