Appearance
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:
IScaffoldGraphextendsIScaffoldGraphNamed- every graph node has an identityIScaffoldGraphTypeadds CLR type awareness and annotationsIScaffoldGraphConversionis a marker for type conversion nodes
Naming System
Five naming strategies via an interface/class hierarchy:
- Raw (
IScaffoldGraphNamed) - justRawIdentifier - Fixed (
ScaffoldGraphNamedFixed) -GetName()returns the raw identifier - Customisable (
ScaffoldGraphNamedCustomisable) - fixed but supports rename viaCustomName - CodeName (
ScaffoldGraphNamedCodeName) - integrates withCarrot.Text.Code.Naming.CodeNamefor casing, pluralization, humanization - 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 typedAdd<TSpecific>ScaffoldGraphProviderDictionaryBase<TKey, TValue>- dictionary withGetOrCreateoverloads, optional overwrite protection, typedGet<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-IEntityand data modelling primitivesCarrot.Text.Code-CodeName,Casing, naming utilities
Build
bash
dotnet buildUsage 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. ChildImplementing 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
ScaffoldReflectedTypeandScaffoldReflectedPropertyare globally cached - they are safe to callCreate()repeatedly without allocation overhead.PropertyNameResolverCorerejects non-scalar properties (collections, entities) - this is intentional, as it is used for key/index column resolution.- The
Setannotation mode throws if the key already exists. UseOverwriteif you need idempotent writes. ScaffoldGraphNamedCodeNameCustomisable.CodeNamelazily rebuilds whenCustomNamechanges - no explicit refresh needed.ScaffoldGraphProviderDictionaryBasehas anAllowOverwritevirtual property (defaulttrue). Override tofalsefor strict single-assignment semantics.