Documentation

reference/patterns/entity-constants.md

Entity Constant Patterns

Status: Active
Enforced By: Roslyn Analyzers ACSIS0019-ACSIS0022
Last Updated: 2025-01-04

Overview

Dynaplex enforces consistent entity constant conventions for all database entity classes. These constants provide type-safe references to table names and platform type identifiers, preventing magic strings and enabling better refactoring support.

Required Constants

TABLE_NAME Constant (ACSIS0019, ACSIS0020)

All entity classes with a [Table] attribute must define a TABLE_NAME constant:

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";

    // ... entity properties
}

Rules:

  • Must be public const string TABLE_NAME
  • Value must match PostgreSQL naming convention (lowercase, snake_case)
  • The [Table] attribute must reference the constant, not a string literal
  • Wrong: [Table("items")] (literal string)
  • Correct: [Table(TABLE_NAME)] (constant reference)

Benefits:

  • Single source of truth for table names
  • Refactoring support (rename constant updates all references)
  • Prevents typos in table name strings
  • Enables compile-time verification

PTID Constant (ACSIS0021, ACSIS0022)

Passport-enabled entities (entities with PlatformTypeId property) must define a PTID constant:

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";
    public const short PTID = PTIDS.ITEM;

    [Key]
    [Column("id")]
    public Guid Id { get; set; }

    [Column("platform_type_id")]
    public short PlatformTypeId { get; set; } = PTID;

    // ... other properties
}

Rules:

  • Must be public const short PTID
  • Value must be a reference to PTIDS.CONSTANT_NAME from CoreData.Abstractions
  • The PlatformTypeId property default value must use the PTID constant
  • Wrong: PlatformTypeId { get; set; } = 801; (hardcoded literal)
  • Correct: PlatformTypeId { get; set; } = PTID; (constant reference)

Benefits:

  • Type-safe platform type identifier references
  • Prevents hardcoded "magic numbers"
  • Ensures consistency with PTIDS registry
  • Enables compile-time verification of platform types

Analyzer Rules

ACSIS0019: Entity missing TABLE_NAME constant

Severity: Warning

Trigger: Entity class has [Table] attribute but no TABLE_NAME constant

Example Violation:

[Table("items")]  // Has Table attribute
public class Item  // ❌ Missing TABLE_NAME constant
{
    // ... properties
}

Fix:

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";  // ✅ Added constant
    // ... properties
}

Code Fix: Auto-generates TABLE_NAME constant from [Table] attribute value


ACSIS0020: [Table] attribute should use TABLE_NAME constant

Severity: Info (auto-fixable)

Trigger: Entity has TABLE_NAME constant but [Table] uses string literal

Example Violation:

[Table("items")]  // ❌ Using literal string
public class Item
{
    public const string TABLE_NAME = "items";
    // ... properties
}

Fix:

[Table(TABLE_NAME)]  // ✅ Using constant
public class Item
{
    public const string TABLE_NAME = "items";
    // ... properties
}

Code Fix: Automatically replaces literal with constant reference


ACSIS0021: Passport-enabled entity missing PTID constant

Severity: Warning

Trigger: Entity has PlatformTypeId property but no PTID constant

Example Violation:

[Table(TABLE_NAME)]
public class Item  // ❌ Missing PTID constant
{
    public const string TABLE_NAME = "items";

    public short PlatformTypeId { get; set; } = 303;
}

Fix:

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";
    public const short PTID = PTIDS.ITEM;  // ✅ Added PTID constant

    public short PlatformTypeId { get; set; } = PTID;
}

Code Fix: Auto-generates PTID constant (may need manual PTIDS value lookup)


ACSIS0022: PlatformTypeId should use PTID constant

Severity: Info (auto-fixable)

Trigger: Entity has PTID constant but PlatformTypeId uses hardcoded literal

Example Violation:

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";
    public const short PTID = PTIDS.ITEM;

    public short PlatformTypeId { get; set; } = 303;  // ❌ Hardcoded literal
}

Fix:

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";
    public const short PTID = PTIDS.ITEM;

    public short PlatformTypeId { get; set; } = PTID;  // ✅ Using constant
}

Code Fix: Automatically replaces literal with PTID constant

Entity Classification

1. Passport-Enabled Entities

Entities that integrate with the Prism Passport system.

Characteristics:

  • Primary key: Guid Id (not auto-generated)
  • Has short PlatformTypeId property
  • FK relationship to prism.passports
  • Registered in PTIDS registry

Required Constants:

  • TABLE_NAME
  • PTID

Example:

using Acsis.Dynaplex.Engines.CoreData.Abstractions.Primitives;

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";
    public const short PTID = PTIDS.ITEM;

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    [Column("id")]
    public Guid Id { get; set; }

    [Column("platform_type_id")]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public short PlatformTypeId { get; set; } = PTID;

    // ... other properties
}

2. Reference Data Entities

Simple lookup tables or reference data without passport integration.

Characteristics:

  • Primary key: int or short (often auto-generated)
  • No PlatformTypeId property
  • No Passport integration

Required Constants:

  • TABLE_NAME
  • PTID (not applicable)

Example:

[Table(TABLE_NAME)]
public class UserStatus
{
    public const string TABLE_NAME = "user_statuses";

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("id")]
    public int Id { get; set; }

    [Column("description")]
    public string Description { get; set; } = null!;
}

3. Excluded Entity Types

Some entity types are excluded from these analyzer rules:

DbContext Classes

  • Reason: Not entity classes
  • DbContexts have their own constants (e.g., SCHEMA)

Journal/Log Classes

  • Pattern: Class name ends with "Log"
  • Reason: Follow different patterns (inherit from JournalRowBase)

Legacy IoT Entities

  • Pattern: Class name starts with "Tn" or "Zb"
  • Reason: External system integrations with different conventions

PlatformType Entity

  • Reason: This IS the platform type registry itself

PTIDS Registry

The PTIDS class in CoreData.Abstractions defines all platform type identifiers:

// engines/core-data/src/Acsis.Dynaplex.Engines.CoreData.Abstractions/Primitives/PTIDS.cs

public static class PTIDS
{
    // Core Data: 0-99
    public const short UNIT_OF_MEASURE = 30;

    // System Environment: 100-199
    public const short ORGANIZATION = 110;

    // Identity: 200-299
    public const short TENANT = 201;
    public const short USER = 203;

    // Catalog: 300-399
    public const short ITEM_CATEGORY = 301;
    public const short ITEM_TYPE = 302;
    public const short ITEM = 303;

    // Spatial: 400-499
    public const short LOCATION = 403;

    // Workflow: 600-699
    public const short WORKFLOW = 602;

    // Printing: 800-899
    public const short PRINTER = 801;

    // Prism: 1000-1100
    public const short PASSPORT = 1001;

    // Events: 5000-5100
    public const short EVENT = 5001;
}

PTID Allocation:

  • Each component has a reserved range (e.g., Identity: 200-299)
  • New PTIDs must be added to the PTIDS class first
  • Namespace prevents conflicts across components

Code Fix Providers

All four analyzer rules have code fix providers that can automatically remediate violations:

Auto-Fix Capabilities

  1. Add TABLE_NAME constant (ACSIS0019)

    • Extracts table name from [Table] attribute
    • Generates public const string TABLE_NAME = "value";
    • Inserts at top of class
  2. Replace [Table] literal with constant (ACSIS0020)

    • Changes [Table("literal")][Table(TABLE_NAME)]
    • Preserves whitespace and formatting
  3. Add PTID constant (ACSIS0021)

    • Extracts PTID value from PlatformTypeId property initializer
    • Generates public const short PTID = PTIDS.CONSTANT_NAME;
    • Inserts after TABLE_NAME constant
    • Note: May require manual adjustment of PTIDS.CONSTANT_NAME value
  4. Replace PlatformTypeId literal with PTID (ACSIS0022)

    • Changes = 801= PTID
    • Preserves property structure

Using Code Fixes in Visual Studio / Rider

  1. Place cursor on warning/error squiggle
  2. Press Ctrl+. (Windows/Linux) or Cmd+. (Mac)
  3. Select "Add TABLE_NAME constant" or appropriate fix
  4. Code is automatically updated

Batch Fixing

Use "Fix All" in Visual Studio to apply code fixes across:

  • Document: Current file only
  • Project: Entire project
  • Solution: All projects

Migration Guide

Migrating Existing Entities

For entities that don't follow these patterns:

Step 1: Run analyzers

dotnet build
# Analyzer warnings will appear

Step 2: Apply code fixes

  • In IDE: Use Quick Actions (Ctrl+. / Cmd+.)
  • Batch: Use "Fix All in Project" or "Fix All in Solution"

Step 3: Verify PTID values

  • Ensure PTID constants reference correct PTIDS.CONSTANT_NAME
  • Add new PTIDs to PTIDS registry if needed

Step 4: Update [Table] attributes

  • Replace all [Table("literal")] with [Table(TABLE_NAME)]

Step 5: Build and test

dotnet build
# Should have no ACSIS0019-0022 warnings

Adding New Entities

When creating a new entity class:

1. Define constants first:

[Table(TABLE_NAME)]
public class MyNewEntity
{
    public const string TABLE_NAME = "my_new_entities";
    public const short PTID = PTIDS.MY_NEW_ENTITY;  // Add to PTIDS if needed

2. Add to PTIDS registry (if passport-enabled):

// In CoreData.Abstractions/Primitives/PTIDS.cs
public static class PTIDS
{
    // ... existing constants

    public const short MY_NEW_ENTITY = 999;  // Use appropriate range
}

3. Use constants in properties:

    [Column("platform_type_id")]
    public short PlatformTypeId { get; set; } = PTID;

Best Practices

✅ DO

  • Define constants at the top of the class (after class declaration)
  • Use TABLE_NAME in [Table] attribute
  • Use PTID for PlatformTypeId default value
  • Add new PTIDs to PTIDS registry before using
  • Follow PostgreSQL naming: lowercase, snake_case, plural for tables

❌ DON'T

  • Use string literals in [Table] attributes
  • Hardcode platform type ID numbers
  • Create PTIDs without adding to registry
  • Use different constant names (e.g., TABLE, TABLENAME, PLATFORM_TYPE_ID)

Troubleshooting

"Missing PTID constant" but entity shouldn't have one

Solution: Your entity may not need passport integration.

If the entity has int or short primary key (not Guid), it's reference data and doesn't need PTID. The analyzer only triggers for entities with PlatformTypeId property.

Code fix generates wrong PTID value

Solution: Manually correct the PTIDS constant name.

The code fix provider guesses the PTID constant name from the class name. Verify it matches an existing value in the PTIDS registry:

// Code fix may generate:
public const short PTID = PTIDS.MY_ENTITY;

// Verify PTIDS.MY_ENTITY exists in CoreData.Abstractions/Primitives/PTIDS.cs

Analyzer not running in IDE

Solution: Rebuild the analyzer project.

dotnet build strata/analyzers/src/Acsis.RoslynAnalyzers/

Restart your IDE if analyzer still doesn't appear.

Examples

Exemplary Entity: Item.cs

Perfect implementation of all patterns:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Acsis.Dynaplex.Engines.CoreData.Abstractions.Primitives;

namespace Acsis.Dynaplex.Engines.Catalog.Database;

[Table(TABLE_NAME)]
public class Item
{
    public const string TABLE_NAME = "items";
    public const short PTID = PTIDS.ITEM;

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    [Column("id", Order = 0)]
    public Guid Id { get; set; }

    [Column("platform_type_id", Order = 1)]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public short PlatformTypeId { get; set; } = PTID;

    [Required]
    [Column("name", Order = 2)]
    [StringLength(256)]
    public string Name { get; set; } = null!;

    [Column("tenant_id", Order = 99)]
    public Guid TenantId { get; set; }
}

Reference Entity: UserStatus.cs

Correct implementation for non-passport entity:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Acsis.Dynaplex.Engines.Identity.Database;

[Table(TABLE_NAME)]
public class UserStatus
{
    public const string TABLE_NAME = "user_statuses";

    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column("id")]
    public int Id { get; set; }

    [Column("description")]
    [StringLength(50)]
    public string Description { get; set; } = null!;
}

Summary

Entity constant patterns ensure:

  • Consistency: All entities follow the same conventions
  • Maintainability: Constants enable safe refactoring
  • Type Safety: Compile-time verification prevents runtime errors
  • Clarity: No magic strings or numbers in code

The Roslyn analyzers (ACSIS0019-0022) automatically enforce these patterns with helpful code fixes to guide developers toward correct implementations.