Skip to content

Carrot.Data.Scaffold.Core

net

Foundational infrastructure for the scaffold system - type reflection, graph model, naming, annotations, definitions, and property resolution.

Installation

xml
<PackageReference Include="Carrot.Data.Scaffold.Core" />

Architecture / How It Works

Type Reflection (Cached)

ScaffoldReflectedType and ScaffoldReflectedProperty use ConcurrentDictionary caches keyed by Type and PropertyInfo respectively. Once created, they expose a rich IScaffoldReflectedTypeShape surface (15+ boolean shape flags, CLR properties, interfaces, attributes). ScaffoldReflectedBase is the base class that composes a CLR type with its reflected type and underlying element type.

Graph Model

The graph is a hierarchy:

  • IScaffoldGraph extends IScaffoldGraphNamed - every graph node has an identity
  • IScaffoldGraphType adds CLR type awareness and annotations
  • IScaffoldGraphConversion is a marker for type conversion nodes

Naming System

Five naming strategies via an interface/class hierarchy:

  1. Raw (IScaffoldGraphNamed) - just RawIdentifier
  2. Fixed (ScaffoldGraphNamedFixed) - GetName() returns the raw identifier
  3. Customisable (ScaffoldGraphNamedCustomisable) - fixed but supports rename via CustomName
  4. CodeName (ScaffoldGraphNamedCodeName) - integrates with Carrot.Text.Code.Naming.CodeName for casing, pluralization, humanization
  5. CodeName Customisable (ScaffoldGraphNamedCodeNameCustomisable) - code-name with rename support

Annotation System

ScaffoldGraphAnnotationProvider is a dictionary-backed provider (string -> IScaffoldGraphAnnotation) supporting four merge modes: Set (once), Overwrite, ListAdd, ListRemove. Annotations come in two flavours: ScaffoldGraphAnnotation (single value) and ScaffoldGraphAnnotationList (multi-value). ScaffoldConfigureAnnotations provides a fluent API over the provider.

Provider Infrastructure

Two generic provider base classes power all graph collections:

  • ScaffoldGraphProviderListBase<T> - ordered list with typed Add<TSpecific>
  • ScaffoldGraphProviderDictionaryBase<TKey, TValue> - dictionary with GetOrCreate overloads, optional overwrite protection, typed Get<TSpecific>

Definition Marker Interfaces

IScaffoldDefine hierarchy uses generic keys to distinguish definition strategies:

  • IScaffoldDefineDeclarative - string-keyed (name-driven)
  • IScaffoldDefineIntrospective / IScaffoldDefineIntrospectiveAbstract - Type-keyed (reflection-driven)
  • IScaffoldDefineConversion<TA, TB> - type conversion definitions

Property Resolution

PropertyNameResolverCore resolves property names from expression trees (x => x.Prop, x => new { x.A, x.B }, value tuples, member init) or string lists. Validates against IScaffoldReflectedTypeShape and enforces scalar-only, no-duplicates.

Dependencies

Project references:

  • Carrot.Data.Modelling - IEntity and data modelling primitives
  • Carrot.Text.Code - CodeName, Casing, naming utilities

Build

bash
dotnet build

Usage Guide

Foundational infrastructure for building scaffold process domains - provides type reflection, graph modelling, naming, annotations, and definition contracts.

Getting Started

This package is not typically consumed directly. It underpins the four scaffold process packages (Api, Cloud, Database, Integrations) and the top-level Carrot.Data.Scaffold orchestrator. Use it when building a new scaffold process domain or extending an existing one.

csharp
using Carrot.Data.Scaffold.Reflected;
using Carrot.Data.Scaffold.Graph;
using Carrot.Data.Scaffold.Graph.Naming;
using Carrot.Data.Scaffold.Definitions;

Common Patterns

Reflecting a Type

csharp
ScaffoldReflectedType reflected = ScaffoldReflectedType.Create(typeof(MyEntity));

bool isRecord     = reflected.IsRecord;
bool isCollection = reflected.IsCollection;
bool isScalar     = reflected.IsScalar;

// Access CLR metadata
IReadOnlyDictionary<string, PropertyInfo> props = reflected.ClrProperties;
IReadOnlyList<Type> interfaces = reflected.ClrInterfaces;

Reflecting a Property

csharp
PropertyInfo pi = typeof(MyEntity).GetProperty("Name")!;
ScaffoldReflectedProperty prop = ScaffoldReflectedProperty.Create(pi);

bool isEntity     = prop.IsEntity;
bool isCollection = prop.IsCollection;
bool isNullable   = prop.IsNullable;
Type rawType      = prop.ClrTypeRaw;       // e.g. IList<Child>
Type unwrapped    = prop.ClrType;          // e.g. Child

Implementing a Definition

csharp
// Introspective: keyed by Type
public class MyEntityDefinition : IScaffoldDefineIntrospective
{
    public string LogicalName => "MyEntity";
    public Type GraphKey => typeof(MyEntity);
}

// Declarative: keyed by string
public class MyRuleDefinition : IScaffoldDefineDeclarative
{
    public string LogicalName => "SomeRule";
    public string GraphKey => "some-rule";
}

Building a Named Graph Node

csharp
// With CodeName support (casing, pluralization)
public class MyGraphNode : ScaffoldGraphNamedCodeNameCustomisable
{
    public MyGraphNode(string rawIdentifier) : base(rawIdentifier) { }

    public override Pluralization DefaultPluralization => Pluralization.Singular;
}

var node = new MyGraphNode("OrderItem");
string pascal   = node.GetCodeName(Casing.PascalCase);           // "OrderItem"
string camelPl  = node.GetCodeName(Casing.CamelCase, Pluralization.Plural); // "orderItems"
string humanized = node.HumanizedName;                            // "Order Item"

node.CustomName = "LineItem";  // rename
string renamed = node.GetCodeName(Casing.PascalCase);            // "LineItem"

Working with Annotations

csharp
// On any IScaffoldHasAnnotations node:
var annotations = node.Annotations;

// Set once (throws if already set)
annotations.Set(node, "table-name", "orders");

// Overwrite (always succeeds)
annotations.Overwrite(node, "display-name", "Customer Order");

// List operations
annotations.ListAdd(node, "tags", "auditable");
annotations.ListAdd(node, "tags", "soft-delete");
annotations.ListRemove(node, "tags", "soft-delete");

// Merge with explicit mode
annotations.Merge(node, "tags", "versioned", AnnotationMergeMode.ListAdd);

Resolving Properties from Expressions

csharp
ScaffoldReflectedType shape = ScaffoldReflectedType.Create<MyEntity>();

// Single property
IReadOnlyList<string> names = PropertyNameResolverCore.Expressions<MyEntity>(
    shape, x => x.Name);

// Composite (anonymous type)
IReadOnlyList<string> composite = PropertyNameResolverCore.Expressions<MyEntity>(
    shape, x => new { x.FirstName, x.LastName });

// From string names
IReadOnlyList<string> byName = PropertyNameResolverCore.Names(
    shape, ["FirstName", "LastName"], allowShadow: false);

Tips / Gotchas

  • ScaffoldReflectedType and ScaffoldReflectedProperty are globally cached - they are safe to call Create() repeatedly without allocation overhead.
  • PropertyNameResolverCore rejects non-scalar properties (collections, entities) - this is intentional, as it is used for key/index column resolution.
  • The Set annotation mode throws if the key already exists. Use Overwrite if you need idempotent writes.
  • ScaffoldGraphNamedCodeNameCustomisable.CodeName lazily rebuilds when CustomName changes - no explicit refresh needed.
  • ScaffoldGraphProviderDictionaryBase has an AllowOverwrite virtual property (default true). Override to false for strict single-assignment semantics.

Carrot