Dynamic Enums - Hybrid Enum/Lookup Table System
Overview
All enums now support a hybrid approach that combines:
- Enums for type safety and standard values
- Lookup Tables for extensibility and user-defined values
This allows adding new values (like new rod materials, knot types, fishing methods) without code changes or deployments.
How It Works
Entity Structure
Each enum property now has an optional lookup table reference:
public class Knot
{
// Enum for type safety
public KnotCategory Category { get; set; }
// Optional lookup table for custom/extended values
public int? CategoryLookupId { get; set; }
public LookupTable? CategoryLookup { get; set; }
}
Usage Patterns
Pattern 1: Use Enum (Standard Values)
knot.Category = KnotCategory.LineToLine; // Standard enum value
// CategoryLookupId remains null
Pattern 2: Use Lookup Table (Custom Values)
// Create custom category
var customCategory = new LookupTable
{
Category = "KnotCategory",
Name = "Braid to Wire",
Code = "BraidToWire",
IsSystemDefined = false,
DisplayOrder = 9
};
context.LookupTables.Add(customCategory);
// Use custom category
knot.Category = KnotCategory.LineToLine; // Closest enum (for compatibility)
knot.CategoryLookupId = customCategory.Id; // Custom value
Pattern 3: Query Both
// Get category name (enum or lookup)
var categoryName = knot.CategoryLookup != null
? knot.CategoryLookup.Name
: knot.Category.ToString();
Enums Made Dynamic
Rod Building
- ✅ RodPower - New power ratings
- ✅ RodAction - New action types
- ✅ RodMaterial - New materials (e.g., "Carbon Nanotube", "Hybrid Composite")
- ✅ RodComponentType - New component types
- ✅ GuideType - New guide types
- ✅ GuideFrameType - New frame types
- ✅ GuideRingType - New ring materials (e.g., "Diamond", "Titanium Nitride")
- ✅ WrapType - New wrap types
- ✅ WrapPattern - New patterns (e.g., "Zigzag", "Herringbone")
- ✅ FinishType - New finish types (e.g., "Nano Coating")
- ✅ FinishGloss - New gloss levels
- ✅ RodBuildCondition - New condition states
Line & Knots
- ✅ KnotCategory - New knot categories (e.g., "Braid to Wire", "Wire to Leader")
- ✅ KnotDifficulty - New difficulty levels
- ✅ SpoolingCondition - New condition states
- ✅ LineSegmentPlacement - New placement types
- ✅ ConnectionPosition - New connection positions
Fishing Methods
- ✅ FishingMethod - New fishing techniques (e.g., "Kayak Fishing", "Drone Fishing")
Weather & Environmental
- ✅ PressureTrend - New pressure trends
- ✅ PrecipitationType - New precipitation types (e.g., "Freezing Drizzle")
- ✅ CloudCoverType - New cloud cover types
- ✅ TideType - New tide types
- ✅ TideRangeType - New range types
Catch & Verification
- ✅ CatchCondition - New condition states
- ✅ CatchDisposition - New disposition types
- ✅ VerificationType - New verification types
- ✅ VerificationStatus - New status values
- ✅ VerificationMethod - New verification methods
- ✅ RecordType - New record types
- ✅ EvidenceType - New evidence types
Seeding Enum Values
The EnumLookupSeeder automatically seeds all enum values into lookup tables:
// During database initialization
EnumLookupSeeder.SeedEnumLookups(context);
This creates:
- LookupTable entries for each enum value
- EnumToLookupMapping entries to track the relationship
Adding New Values
Example: New Rod Material
Without Dynamic System (Old Way):
// 1. Update enum in code
public enum RodMaterial
{
// ... existing values
CarbonNanotube // NEW - requires code change
}
// 2. Create migration
// 3. Deploy code
// 4. Run migration
With Dynamic System (New Way):
// No code changes needed!
var newMaterial = new LookupTable
{
Category = "RodMaterial",
Name = "Carbon Nanotube",
Code = "CarbonNanotube",
Description = "Ultra-lightweight carbon nanotube composite",
IsSystemDefined = false, // User-defined
DisplayOrder = 6,
IsActive = true
};
context.LookupTables.Add(newMaterial);
// Use immediately
rodBlank.Material = RodMaterial.Other; // Closest enum
rodBlank.MaterialLookupId = newMaterial.Id; // New material
Example: New Knot Category
// Add new knot category for specialized connections
var braidToWire = new LookupTable
{
Category = "KnotCategory",
Name = "Braid to Wire",
Code = "BraidToWire",
Description = "For connecting braided line to wire leader",
IsSystemDefined = false,
DisplayOrder = 9
};
context.LookupTables.Add(braidToWire);
// Use in knot
knot.Category = KnotCategory.Joining; // Closest enum
knot.CategoryLookupId = braidToWire.Id; // Custom category
Example: New Fishing Method
// Add new fishing method
var droneFishing = new LookupTable
{
Category = "FishingMethod",
Name = "Drone Fishing",
Code = "DroneFishing",
Description = "Using drones to deploy baits and lines",
IsSystemDefined = false,
DisplayOrder = 60
};
context.LookupTables.Add(droneFishing);
// Use in log entry
logEntry.FishingMethod = FishingMethod.Casting; // Closest enum
logEntry.FishingMethodLookupId = droneFishing.Id; // Custom method
Querying Dynamic Values
Get All Values (Enum + Custom)
// Get all rod materials (system + custom)
var allMaterials = context.LookupTables
.Where(lt => lt.Category == "RodMaterial" && lt.IsActive)
.OrderBy(lt => lt.DisplayOrder)
.ToList();
// Or use helper
var materials = EnumLookupSeeder.GetLookupsForEnumType<RodMaterial>(context);
Get Lookup for Enum Value
// Get lookup table entry for enum value
var graphiteLookup = EnumLookupSeeder.GetLookupForEnum(context, RodMaterial.Graphite);
Display Value
// Helper method to get display name
public static string GetDisplayName<T>(T enumValue, LookupTable? lookup) where T : Enum
{
return lookup != null ? lookup.Name : enumValue.ToString();
}
// Usage
var materialName = GetDisplayName(rodBlank.Material, rodBlank.MaterialLookup);
Migration Strategy
Phase 1: Seed Existing Enums
// Run once during initial migration
EnumLookupSeeder.SeedEnumLookups(context);
Phase 2: Use Hybrid Approach
- Entities use enums for standard values
- Entities can optionally use lookup tables for custom values
- Both work together seamlessly
Phase 3: Full Dynamic (Future)
- Consider migrating frequently extended enums to lookup-only
- Keep enums for stable, well-defined categories
- Use lookup tables for user-extensible categories
Best Practices
- Use Enums for Standard Values: Keep enums for well-defined, stable categories
- Use Lookup Tables for Extensible Values: Use lookup tables for categories that may grow
- Seed Enums on Migration: Always seed enum values into lookup tables
- Display Lookup Over Enum: When displaying, prefer lookup name over enum name
- Validate Lookup Categories: Ensure lookup table category matches enum type
- Track Usage: Monitor which custom values are being used
- Consider Promoting: If custom value becomes common, consider adding to enum
Benefits
- No Code Changes: Add new values without deployments
- User-Defined: Users can create custom categories
- Type Safety: Enums still provide compile-time safety
- Backward Compatible: Existing code using enums continues to work
- Future-Proof: System can evolve without breaking changes
- Flexible: Support both standard and custom values simultaneously
Example: Complete Workflow
// 1. Seed enums (one-time, during migration)
EnumLookupSeeder.SeedEnumLookups(context);
// 2. User creates custom rod material
var customMaterial = new LookupTable
{
Category = "RodMaterial",
Name = "Graphene Composite",
Code = "GrapheneComposite",
IsSystemDefined = false,
DisplayOrder = 7
};
context.LookupTables.Add(customMaterial);
context.SaveChanges();
// 3. Use in rod blank
var rodBlank = new RodBlank
{
Material = RodMaterial.Other, // Closest enum
MaterialLookupId = customMaterial.Id, // Custom value
// ... other properties
};
context.RodBlanks.Add(rodBlank);
context.SaveChanges();
// 4. Query and display
var materialName = rodBlank.MaterialLookup?.Name ?? rodBlank.Material.ToString();
// Result: "Graphene Composite"