Source files
  • Editor/Defaults/DefaultsManager.cs — static facade + profile registry
  • Editor/Defaults/DefaultsManager.Apply.cs — Apply overloads (Dictionary, POCO, DataRow)
  • Editor/Defaults/EntityDefaultsProfile.cs — fluent profile builder & FieldDefaultRule
  • Editor/Defaults/IDefaultsManager.cs — DI interface
  • Editor/Defaults/Resolvers/*.cs — 10 built-in resolvers
  • Editor/Defaults/RuleParsing/DotStyleRuleParser.cs — dot-style DSL parser
  • Editor/Defaults/Helpers/*.cs — validation & value helpers
  • Editor/Defaults/Registry/*.cs — resolver descriptor & registry

Overview

DefaultsManager is a partial class that provides a dual surface:

  • Static facade — call-site convenience methods, all accept IDMEEditor
  • Instance / IDefaultsManager — dependency-injection friendly interface

The manager maintains a thread-safe in-memory profile registry keyed by "datasource::entity". When Apply() is called, every FieldDefaultRule in the matching EntityDefaultsProfile is evaluated: expressions (starting with ":") are dispatched to the resolver pipeline; literals are used as-is.

Folder map

Editor/Defaults/
  DefaultsManager.cs              static facade, profile registry, Initialize()
  DefaultsManager.Apply.cs        Apply(Dictionary), Apply<T>(POCO), Apply(DataRow)
  EntityDefaultsProfile.cs        EntityDefaultsProfile + FieldDefaultRule
  IDefaultsManager.cs             DI contract
  Helpers/
    DefaultValueHelper.cs         CRUD for persisted DefaultValue entries
    DefaultValueValidationHelper.cs rule syntax validation
  Interfaces/
    IDefaultValueHelper.cs
    IDefaultValueInterfaces.cs    DefaultValue model
    IDefaultValueResolver.cs
    IDefaultValueResolverManager.cs
    IDefaultValueValidationHelper.cs
    IResolverCapabilities.cs      Priority, IsDeterministic, SupportsCaching, SupportsAsync
    ResolverContext.cs
    ResolverExecutionResult.cs
  Registry/
    DefaultResolverDescriptor.cs  resolver metadata
    DefaultResolverRegistry.cs    resolver catalogue
  Resolvers/
    BaseDefaultValueResolver.cs   abstract base with logging helpers
    DateTimeResolver.cs
    GuidResolver.cs
    UserContextResolver.cs
    SystemInfoResolver.cs
    EnvironmentResolver.cs
    ExpressionResolver.cs
    FormulaResolver.cs
    ConfigurationResolver.cs
    DataSourceResolver.cs
    ObjectPropertyResolver.cs
    DefaultValueResolverManager.cs pipeline manager
  RuleParsing/
    DotStyleRuleParser.cs         dot-segment DSL (v1) parser
    ParsedRule.cs                 parsed rule model
    RuleNormalizer.cs             normalises v1-dot to function style

Quick Start

// 1. Initialize (idempotent — safe to call multiple times)
DefaultsManager.Initialize(dmeEditor);

// 2. Build a profile for the "Users" entity
var profile = EntityDefaultsProfile.For("Users")
    .Set("Id",        ":NEWGUID")     // expression — new Guid each record
    .Set("CreatedAt", ":NOW")          // expression — current DateTime
    .Set("CreatedBy", ":USERNAME")     // expression — current OS user
    .Set("Status",    "Active");       // literal    — used as-is

// 3. Register it (thread-safe, replaces any existing profile for "mydb::Users")
DefaultsManager.RegisterProfile("mydb", "Users", profile);

// 4a. Apply to a Dictionary record
var record = new Dictionary<string, object> { ["Name"] = "Alice" };
DefaultsManager.Apply(dmeEditor, "mydb", "Users", record);
// record["Id"]        = "3f2504e0-..."
// record["CreatedAt"] = 2026-04-03 12:00:00
// record["Status"]    = "Active"

// 4b. Apply to a POCO (reflection-based)
var user = new User { Name = "Alice" };
DefaultsManager.Apply(dmeEditor, "mydb", "Users", user);

// 4c. Apply to a DataRow
DataRow row = myTable.NewRow();
DefaultsManager.Apply(dmeEditor, "mydb", "Users", row);

Rule String Convention

The rule string passed to FieldDefaultRule (and to Resolve()) follows a simple prefix convention:

PrefixMeaningExample
:Expression — parsed and dispatched to the resolver pipeline at runtime":NOW", ":USERNAME", ":NEWGUID"
(none)Literal — used as-is without any resolution"Active", "1", "pending"

Inside the expression body (after the leading ":"), tokens can use two syntaxes:

SyntaxExampleNotes
Function style (v2 canonical)ADDDAYS(TODAY, 7)Recommended for new code
Dot-style DSL (v1 legacy)ADDDAYS.TODAY.7Automatically normalized by RuleNormalizer
Note on ApplyOnlyIfNull — FieldDefaultRule has a third constructor parameter (default true). When true the rule is skipped if the field already has a non-null, non-empty value. Pass false to always overwrite.

EntityDefaultsProfile — Fluent Builder

Building a profile

// Factory entry point
var profile = EntityDefaultsProfile.For("Orders")
    .Set("OrderId",    ":NEWGUID",           applyOnlyIfNull: true)    // expression, skip if set
    .Set("OrderDate",  ":NOW")                                          // expression, skip if set
    .Set("Status",     "Pending")                                       // literal
    .Set("CreatedBy",  ":USERNAME")                                     // expression
    .Set("Version",    "1",                  applyOnlyIfNull: false);   // literal, always overwrite

DefaultsManager.RegisterProfile("OrdersDB", "Orders", profile);

FieldDefaultRule properties

PropertyTypeDescription
FieldNamestringTarget field name; matched case-insensitively
RuleStringstringRule text including the : prefix for expressions
ApplyOnlyIfNullboolWhen true (default), skip if field already has a value
IsExpressionboolComputed: true when RuleString starts with ":"
ExpressionBodystringRule text stripped of the leading ":", passed to the resolver pipeline

EntityDefaultsProfile properties

PropertyTypeDescription
EntityNamestringEntity/table name this profile applies to
RulesIReadOnlyList<FieldDefaultRule>Ordered list of field rules; evaluated in declaration order

Fluent Set method

// Replaces any existing rule for the same fieldName (case-insensitive)
EntityDefaultsProfile Set(string fieldName, string ruleString, bool applyOnlyIfNull = true)

Apply API

All three overloads look up the profile registered under datasource::entity, iterate its rules, and write resolved values to the target.

Apply to Dictionary

IErrorsInfo Apply(
    IDMEEditor                        editor,
    string                            datasourceName,
    string                            entityName,
    Dictionary<string, object>       record,
    IPassedArgs                       context = null
);

Fields not present in the dictionary are inserted. Fields with existing values are skipped when ApplyOnlyIfNull = true.

Apply to POCO (generic)

IErrorsInfo Apply<T>(
    IDMEEditor     editor,
    string         datasourceName,
    string         entityName,
    T              poco,
    IPassedArgs    context = null
) where T : class;

Uses reflection to discover public writable properties. Type conversion from object to the property type is handled automatically via ConvertValue.

Apply to DataRow

IErrorsInfo Apply(
    IDMEEditor     editor,
    string         datasourceName,
    string         entityName,
    DataRow        row,
    IPassedArgs    context = null
);

Only columns that exist in row.Table.Columns are processed. Missing columns in the schema are silently skipped.

Return value

All overloads return IErrorsInfo:

FlagMeaning
Errors.OkAll rules applied successfully (or no profile registered)
Errors.WarningOne or more fields threw during resolution — partial apply

Resolve & Validate API

Resolve a single rule string

// Call-site resolution without a profile
object value = DefaultsManager.Resolve(editor, ":NOW");
object guid  = DefaultsManager.Resolve(editor, ":NEWGUID");
object lit   = DefaultsManager.Resolve(editor, "Active");  // literal — returned unchanged

TestRule — resolve + error info

// Useful for validation UIs and preview tooling
var (info, value) = DefaultsManager.TestRule(editor, ":ADDDAYS(TODAY, 30)");
if (info.Flag == Errors.Ok)
    Console.WriteLine($"Resolved: {value}");
else
    Console.WriteLine($"Error: {info.Message}");

ValidateRule — syntax check only

IErrorsInfo result = DefaultsManager.ValidateRule(editor, ":ADDDAYS(TODAY, 30)");
// Checks syntax without executing the resolver

GetAvailableResolvers

// Returns map of resolverName → example rule tokens
var resolvers = DefaultsManager.GetAvailableResolvers(editor);
foreach (var (name, examples) in resolvers)
    Console.WriteLine($"{name}: {string.Join(", ", examples)}");

Profile Registry

The profile registry is a static Dictionary<string, EntityDefaultsProfile> guarded by a lock object. Keys are "datasource::entity" (case-insensitive).

// Register (or replace) a profile
DefaultsManager.RegisterProfile("mydb", "Users", profile);

// Retrieve an existing profile (returns null if not found)
EntityDefaultsProfile p = DefaultsManager.GetProfile("mydb", "Users");

// Remove a profile (no-op if missing)
DefaultsManager.RemoveProfile("mydb", "Users");
Thread safety — RegisterProfile, GetProfile, and RemoveProfile all acquire a shared lock. Profiles themselves (EntityDefaultsProfile) are immutable once built — the fluent builder returns a new instance via For() and each Set() mutates and returns the same builder.

Built-in Resolvers

All resolvers extend BaseDefaultValueResolver and are auto-registered into DefaultValueResolverManager on initialization.

DateTime resolver

Token / ExpressionReturns
:NOW / :CURRENTDATETIMEDateTime.Now
:TODAY / :CURRENTDATEDateTime.Today
:YESTERDAY / :TOMORROW±1 day from today
:CURRENTTIMEDateTime.Now.TimeOfDay
:STARTOFMONTH / :ENDOFMONTHFirst/last day of current month
:STARTOFYEAR / :ENDOFYEARJan 1 / Dec 31 of current year
:STARTOFWEEK / :ENDOFWEEKMonday / Sunday of current week
:ADDDAYS(TODAY, 7)Seven days from today
:ADDHOURS(NOW, 2)Two hours from now
:ADDMONTHS(TODAY, 1)One month from today
:ADDYEARS(TODAY, -1)One year ago
:FORMAT(NOW, 'yyyy-MM-dd')Formatted date string

Guid resolver

Token / ExpressionReturns
:NEWGUID / :GUID / :UUID / :GENERATEUNIQUEIDNew GUID with hyphens (default D format)
:GUID(N)GUID without hyphens: 3f2504e04f8941d39a0c0305e82c3301
:GUID(D)Default: 3f2504e0-4f89-41d3-9a0c-0305e82c3301
:GUID(B)Braces: {3f2504e0-...}
:GUID(P)Parentheses: (3f2504e0-...)

UserContext resolver

TokenReturns
:USERNAME / :CURRENTUSER / :USERLOGINEnvironment.UserName
:USERIDCurrent user SID / identifier
:USEREMAILUser email (if available)
:USERDOMAINEnvironment.UserDomainName
:USERPROFILEUser profile directory path
:USERGROUPPrimary user group
:USERROLEUser role (from context parameters if provided)
:USERPRINCIPALWindows principal name

SystemInfo resolver

TokenReturnsCacheable
:MACHINENAME / :HOSTNAMEEnvironment.MachineNameYes
:VERSION.NET version stringYes
:APPVERSIONEntry assembly versionYes
:OSVERSIONOS version stringYes
:PLATFORMOS platform stringYes
:PROCESSORCOUNTNumber of processorsYes
:WORKINGSETProcess working set bytesNo
:TIMESTAMPUnix epoch (seconds)No
:TICKSDateTime.Now.TicksNo

Environment resolver

Token / ExpressionReturns
:TEMP / :TEMPPATH%TEMP% path
:SYSTEMPATHSystem PATH variable
:USERPATHUser-scope PATH
:ENV(VARNAME) / :ENVIRONMENTVARIABLE(VARNAME)Named environment variable value
:ENV:VARNAMEKey-value shorthand

Formula resolver

Token / ExpressionReturns
:SEQUENCE(1000)Sequence starting at 1000
:INCREMENT(FieldName)Field value incremented by 1
:RANDOM(1, 100)Random integer in [1, 100]
:ADD(a, b)a + b
:SUBTRACT(a, b) / :SUB(a, b)a - b
:MULTIPLY(a, b) / :MUL(a, b)a × b
:DIVIDE(a, b) / :DIV(a, b)a ÷ b (0 on divide-by-zero)
:ROUND(val, digits)Rounded value
:CALCULATE(expr) / :COMPUTE(expr)General arithmetic expression

Expression resolver (conditionals)

Token / ExpressionReturns
:IF(condition, thenVal, elseVal)Ternary branch
:ISNULL(value, fallback)value if not null, else fallback
:COALESCE(a, b, c)First non-null argument
:CASE(value, when1, then1, when2, then2, default)Multi-branch switch
:EQ(a, b) / :NE(a,b)Equality / inequality (bool)
:GT(a, b) / :LT(a, b)Numeric comparison (bool)
:AND(a, b) / :OR(a, b) / :NOT(a)Logical operators

Configuration resolver

Token / ExpressionReturns
:CONFIG(KeyName) / :CONFIGURATIONVALUE(KeyName)App config value by key
:APPSETTING(KeyName) / :SETTING(KeyName)AppSettings value
:CONNECTIONSTRING(Name)Named connection string
:CONFIG:KeyNameKey-value shorthand

DataSource resolver

Queries any registered IDataSource via GetEntity + AppFilter. Aggregates are computed in-memory via LINQ.

Token / ExpressionReturns
:LOOKUP(ds, entity, valueField, filterField, @param)Single cell from a data source
:QUERY(ds, entity, valueField, filterField, @param)Alias for LOOKUP
:COUNT(ds, entity)Row count
:MAX(ds, entity, field)Max value of a column
:MIN(ds, entity, field)Min value
:SUM(ds, entity, field)Sum of a column
:AVG(ds, entity, field)Average of a column

Filter values prefixed with @ are bound from the runtime IPassedArgs context parameters.

ObjectProperty resolver

Extracts values from runtime objects passed in IPassedArgs via reflection.

Token / ExpressionReturns
:PROPERTY(propName) / :GETPROPERTY(propName)Named public property from context object
:FIELD(fieldName)Public field from context object
:NESTED(obj.sub.prop)Dot-path traversal
:ARRAYITEM(propName, index)Array element by index
:DICTVALUE(propName, key)Dictionary entry by key

Resolver capabilities

All resolvers implement IResolverCapabilities:

PropertyDefaultDescription
Priority100Lower values are tried first in the pipeline
IsDeterministicfalseIf true, the resolver output may be cached
SupportsCaching= IsDeterministicWhether the manager may cache this resolver's output
SupportsAsyncfalseReserved for future async resolver support

Dot-Style DSL (v1)

The DotStyleRuleParser understands a legacy dot-segment format where the first segment is the operator and subsequent segments are arguments. RuleNormalizer converts this to function style before handing off to the resolver pipeline.

Syntax

Operator.arg1.arg2...argN

Examples:
  :ADDDAYS.TODAY.7          →   ADDDAYS(TODAY, 7)
  :ADD.10.5                 →   ADD(10, 5)
  :LOOKUP.MyDB.Users.Email.Id.@userId
  :FORMAT.NOW.'yyyy-MM-dd'  →   quotes protect embedded dots

IsDotStyleRule detection

A rule is detected as dot-style when its first segment (before the first dot) matches one of the known operators. If a parenthesis appears before any dot, the rule is treated as function style.

Known operator set

DateTime, Formula/math, Expression/conditionals, Query/datasource, User/system, Object property operators — see DotStyleRuleParser._knownOperators for the full list.

Creating a Custom Resolver

Extend BaseDefaultValueResolver and register it via RegisterCustomResolver.

public class BusinessResolver : BaseDefaultValueResolver
{
    public BusinessResolver(IDMEEditor editor) : base(editor) { }

    public override string ResolverName        => "Business";
    public override int    Priority            => 50;      // run before built-ins (100)
    public override bool   IsDeterministic     => false;   // values vary per call

    public override IEnumerable<string> SupportedRuleTypes => new[]
    {
        "NEXTORDERID", "CUSTOMERCODE", "SALESREP"
    };

    public override bool CanHandle(string rule)
    {
        var upper = rule?.ToUpperInvariant().Trim() ?? "";
        return upper.StartsWith("NEXTORDERID") ||
               upper.StartsWith("CUSTOMERCODE") ||
               upper.StartsWith("SALESREP");
    }

    public override object ResolveValue(string rule, IPassedArgs parameters)
    {
        return rule.ToUpperInvariant().Trim() switch
        {
            "NEXTORDERID"   => GetNextOrderId(),
            "CUSTOMERCODE"  => GenerateCustomerCode(),
            var r when r.StartsWith("SALESREP(") => GetSalesRep(r),
            _               => null
        };
    }

    public override IEnumerable<string> GetExamples() => new[]
    {
        "NEXTORDERID - next sequential order ID",
        "CUSTOMERCODE - generated customer code",
        "SALESREP(region) - default sales rep for a region"
    };

    private string GetNextOrderId() { /* ... */ return "ORD-001"; }
    private string GenerateCustomerCode() { /* ... */ return "CUST-0042"; }
    private string GetSalesRep(string rule) { /* ... */ return "John Smith"; }
}

// Registration:
DefaultsManager.RegisterCustomResolver(editor, new BusinessResolver(editor));

Legacy / Persistence API

These methods delegate to DefaultValueHelper and operate on DefaultValue entries stored in ConnectionProperties.DatasourceDefaults. They provide backward compatibility with code written against the original DataSyncManager/configuration-based defaults system.

// Get all persisted defaults for a data source
List<DefaultValue> defaults = DefaultsManager.GetDefaults(editor, "MyDatabase");

// Save a list of defaults back to config
DefaultsManager.SaveDefaults(editor, defaults, "MyDatabase");

// Set a single column default (isRule=false → static literal; isRule=true → rule string)
DefaultsManager.SetColumnDefault(editor, "MyDatabase", "Users", "Status",    "Active",    isRule: false);
DefaultsManager.SetColumnDefault(editor, "MyDatabase", "Users", "CreatedAt", "NOW",       isRule: true);
DefaultsManager.SetColumnDefault(editor, "MyDatabase", "Users", "Id",        "NEWGUID",   isRule: true);

// Retrieve a persisted default
DefaultValue def = DefaultsManager.GetColumnDefault(editor, "MyDatabase", "Users", "Status");

// Apply persisted defaults to a record
DefaultsManager.ApplyDefaults(editor, "MyDatabase", "Users", record);
Recommendation — for new code prefer the EntityDefaultsProfile + RegisterProfile pattern, which is type-safe, fluent, and does not require persistence. Use the legacy API when you need defaults to survive application restarts via the config file.

Dependency Injection Interface

IDefaultsManager mirrors the static DefaultsManager surface for DI-friendly scenarios.

// Registration (example with Microsoft.Extensions.DependencyInjection)
services.AddSingleton<IDefaultsManager>(sp =>
{
    var editor = sp.GetRequiredService<IDMEEditor>();
    var mgr = new DefaultsManager();
    mgr.Initialize(editor);
    return mgr;
});

// Usage in a service
public class OrderService
{
    private readonly IDefaultsManager _defaults;
    private readonly IDMEEditor       _editor;

    public OrderService(IDefaultsManager defaults, IDMEEditor editor)
    {
        _defaults = defaults;
        _editor   = editor;
    }

    public void CreateOrder(Order order)
    {
        _defaults.Apply(_editor, "OrdersDB", "Orders", order);
        // order.Id, CreatedAt, CreatedBy, Status are now populated
    }
}

IDefaultsManager method summary

MethodDescription
Initialize(editor)Init or re-init the manager
RegisterProfile(ds, entity, profile)Register an EntityDefaultsProfile
GetProfile(ds, entity)Retrieve a registered profile
RemoveProfile(ds, entity)Remove a profile
Apply(editor, ds, entity, dict)Apply to Dictionary<string,object>
Apply<T>(editor, ds, entity, poco)Apply to POCO
Apply(editor, ds, entity, row)Apply to DataRow
Resolve(editor, rule)Resolve a single rule string
TestRule(editor, rule)Resolve + return error info
ValidateRule(editor, rule)Syntax check without execution
RegisterCustomResolver(editor, resolver)Add a custom resolver to the pipeline
GetAvailableResolvers(editor)List all resolvers with examples
GetDefaults / SaveDefaultsLegacy persistence API
SetColumnDefault / GetColumnDefaultLegacy per-column helpers

Examples

Audit fields pattern

// Common audit pattern for any entity
var auditProfile = EntityDefaultsProfile.For("Orders")
    .Set("Id",        ":NEWGUID")
    .Set("CreatedAt", ":NOW")
    .Set("CreatedBy", ":USERNAME")
    .Set("UpdatedAt", ":NOW",      applyOnlyIfNull: false)  // always stamp
    .Set("UpdatedBy", ":USERNAME", applyOnlyIfNull: false)  // always stamp
    .Set("Status",    "Draft");

DefaultsManager.RegisterProfile("OrdersDB", "Orders", auditProfile);

var order = new Order { CustomerId = "C001", Total = 99.99m };
DefaultsManager.Apply(dmeEditor, "OrdersDB", "Orders", order);

DataSource lookup default

// Default the SalesRepId field by looking up the active rep for a region
// Filter value @Region is bound from IPassedArgs.Parameters at runtime
var profile = EntityDefaultsProfile.For("Invoices")
    .Set("SalesRepId", ":LOOKUP(CrmDB, SalesReps, Id, Region, @Region)")
    .Set("InvoiceDate", ":TODAY")
    .Set("DueDate",     ":ADDDAYS(TODAY, 30)");

DefaultsManager.RegisterProfile("InvoicesDB", "Invoices", profile);

// At call-site, pass the region via IPassedArgs:
var ctx = new PassedArgs();
ctx.ParameterString1 = "EU";  // e.g. bound to @Region by resolver
DefaultsManager.Apply(dmeEditor, "InvoicesDB", "Invoices", invoiceRecord, ctx);

Custom resolver + profile composition

// Register custom resolver first
DefaultsManager.RegisterCustomResolver(dmeEditor, new BusinessResolver(dmeEditor));

// Then build a profile that uses it
var profile = EntityDefaultsProfile.For("Tickets")
    .Set("TicketId",   ":NEXTORDERID")      // handled by BusinessResolver
    .Set("AssignedTo", ":SALESREP(EU)")     // handled by BusinessResolver
    .Set("CreatedAt",  ":NOW")
    .Set("Priority",   "Normal");

DefaultsManager.RegisterProfile("HelpDesk", "Tickets", profile);

Validate and test rules before registration

string[] rulesToCheck = { ":ADDDAYS(TODAY, 7)", ":NEWGUID", ":ENV(APPDATA)" };

foreach (var rule in rulesToCheck)
{
    var syntax = DefaultsManager.ValidateRule(dmeEditor, rule);
    if (syntax.Flag != Errors.Ok)
    {
        Console.WriteLine($"Syntax error in '{rule}': {syntax.Message}");
        continue;
    }

    var (info, value) = DefaultsManager.TestRule(dmeEditor, rule);
    Console.WriteLine($"{rule} → {value} [{info.Flag}]");
}

List all resolvers and their tokens

var resolvers = DefaultsManager.GetAvailableResolvers(dmeEditor);
foreach (var (name, examples) in resolvers.OrderBy(x => x.Key))
{
    Console.WriteLine($"\n{name}:");
    foreach (var ex in examples)
        Console.WriteLine($"  {ex}");
}

Full API Reference

IDefaultsManager — Complete Interface

CategoryMethodReturn
LifecycleInitialize(IDMEEditor)void
Profile RegistryRegisterProfile(ds, entity, profile)void
GetProfile(ds, entity)EntityDefaultsProfile?
RemoveProfile(ds, entity)void
ApplyApply(editor, ds, entity, Dict<string,object>)IErrorsInfo
Apply<T>(editor, ds, entity, T) where T:classIErrorsInfo
Apply(editor, ds, entity, DataRow)IErrorsInfo
ResolveResolve(editor, rule, context?)object
TestRule(editor, rule, params?)Tuple<IErrorsInfo,object>
ValidateRule(editor, rule)IErrorsInfo
Resolver MgmtRegisterCustomResolver(editor, resolver)void
GetAvailableResolvers(editor)Dict<string,IEnumerable>
Legacy/PersistenceGetDefaults(editor, ds)List<DefaultValue>
SaveDefaults(editor, defaults, ds)IErrorsInfo
SetColumnDefault(editor, ds, entity, col, val, isRule)IErrorsInfo
GetColumnDefault(editor, ds, entity, col, params?)object
RemoveColumnDefault(editor, ds, entity, col)IErrorsInfo
GetEntityDefaults(editor, ds, entity)Dict<string,DefaultValue>
SetMultipleColumnDefaults(editor, ds, entity, columnDefaults)IErrorsInfo
CreateDefaultValue(editor, field, val, rule?)Tuple<IErrorsInfo,DefaultValue>

DefaultsManager Static Accessors

AccessorTypeDescription
DefaultValueHelperIDefaultValueHelperCRUD for persisted DefaultValue entries. Throws if not initialized.
ResolverManagerIDefaultValueResolverManagerResolver pipeline manager. Throws if not initialized.
ValidationHelperIDefaultValueValidationHelperRule syntax/semantic/safety validation. Throws if not initialized.

IDefaultValueResolverManager — Complete Interface

MethodDetails
RegisterResolver(IDefaultValueResolver)At self-declared priority (default 100). Replaces by name.
RegisterResolver(resolver, int priority)Phase 4: explicit priority. Lower = tried first (0-49 overrides, 50-99 custom, 100 built-in, 200+ fallback).
UnregisterResolver(string name)Remove by name. No-op if missing.
ResolveValue(string rule, IPassedArgs)Normalizes rule, routes to first priority-ordered CanHandle match, checks caching if deterministic.
ResolveValueWithNormalization(rule, args)Same as ResolveValue but passes through RuleNormalizer first.
ResolveWithTelemetry(rule, args)Phase 5: timed resolution returning full ResolverExecutionResult (name, duration, fingerprint, fallback flags).
ParseRule(string rule)Phase 1: parses and normalizes to structured ParsedRule. Results may be cached.
CompileRule(string rule)Phase 6: pre-parses and caches rule to eliminate parse overhead during resolution.
InvalidateValueCache()Phase 6: clears value result cache. Call when datasource/config changes.
GetResolvers()Returns IReadOnlyDictionary<string, IDefaultValueResolver>
GetResolverForRule(string)Returns first resolver where CanHandle = true, or null.
CanResolveRule(string)True if any resolver can handle the rule.
GetResolverStatistics()Returns TotalResolvers, ResolverNames, RegisteredAt, ResolverTypes.
GetAllExamples()Resolver names → example strings from all registered resolvers.
ValidateResolvers()Validates each resolver for non-null name/types/examples.

ResolverExecutionResult (Phase 5 telemetry)

public sealed class ResolverExecutionResult
{
    public string ResolverName     { get; init; }   // Winning resolver or "none"
    public string OriginalRule     { get; init; }   // Verbatim rule string
    public string NormalizedRule   { get; init; }   // After dot-style normalization
    public object ResolvedValue    { get; init; }   // Value or null on failure
    public bool   Succeeded         { get; init; }   // True when resolver produced value
    public bool   FallbackUsed      { get; init; }   // Primary failed, fallback value used
    public TimeSpan Duration        { get; init; }   // Wall-clock time spent resolving
    public string ErrorMessage      { get; init; }   // Exception message when Succeeded=false
    public string RuleFingerprint   { get; init; }   // 8-char FNV-1a hex hash of NormalizedRule
}

ResolverContext

public class ResolverContext
{
    public IPassedArgs PassedArgs { get; }
    public Dictionary<string, object> Extras { get; }

    public ResolverContext(IPassedArgs passedArgs);
    public ResolverContext(IPassedArgs passedArgs, Dictionary<string, object> extras);

    public T GetExtra<T>(string key, T defaultValue = default);

    // Computed shortcuts:
    public string EntityName => PassedArgs?.CurrentEntity;
    public string FieldName  => /* Extras["FieldName"] ?? PassedArgs.ParameterString1 */;
}

IResolverCapabilities

PropertyTypeDefaultDescription
Priorityint100Lower tried first. 0-49=override, 50-99=custom, 100=built-in, 200+=fallback
IsDeterministicboolfalseSame rule+context always produces same value
SupportsCachingbool=IsDeterministicWhether resolver output may be cached
SupportsAsyncboolfalseReserved for future async resolver support

IDefaultValueResolver — Contract

public interface IDefaultValueResolver
{
    string ResolverName { get; }                                     // Unique name for registration
    IEnumerable<string> SupportedRuleTypes { get; }                  // Tokens this resolver handles
    object ResolveValue(string rule, IPassedArgs parameters);        // Produce concrete value
    bool CanHandle(string rule);                                     // Fast pre-check
    IEnumerable<string> GetExamples();                               // For documentation/tooling
}

BaseDefaultValueResolver — Protected Utilities

public abstract class BaseDefaultValueResolver : IDefaultValueResolver, IResolverCapabilities
{
    protected readonly IDMEEditor Editor;

    // Logging helpers
    protected virtual void LogError(string message, Exception ex = null);
    protected virtual void LogInfo(string message);
    protected virtual void LogWarning(string message);

    // Parameter extraction
    protected virtual T GetParameterValue<T>(IPassedArgs, string propertyName, T defaultValue);
    protected virtual object GetObjectFromParameters(IPassedArgs, string propertyName = "Objects");
    protected virtual IDataSource GetDataSource(IPassedArgs, string dataSourceName = null);

    // String parsing helpers
    protected virtual string ExtractParenthesesContent(string rule);
    protected virtual string[] SplitParameters(string parameterString);
    protected virtual string RemoveQuotes(string value);
    protected virtual bool TryConvert<T>(string value, out T result);
    protected virtual bool ValidateFunctionFormat(string rule, string functionName);
}

DefaultResolverRegistry — Discovery & Descriptor

public class DefaultResolverRegistry
{
    public DefaultResolverRegistry(IDMEEditor editor);

    // Scans assemblies for [DefaultResolver] classes, instantiates, registers via DefaultsManager
    public void Discover();
    public IReadOnlyDictionary<string, DefaultResolverDescriptor> GetDescriptors();
    public bool TryGet(string resolverName, out DefaultResolverDescriptor descriptor);
    public IDefaultValueResolver Create(string resolverName);  // Creates new instance
}

public sealed class DefaultResolverDescriptor
{
    public DefaultResolverAttribute Attribute { get; }
    public Type ImplementationType { get; }
}

Rule Parsing: ParsedRule, RuleNormalizer, DotStyleRuleParser

// ── Syntax versions ──
public enum RuleSyntaxVersion { V1Legacy = 0, V1Dot = 1, Literal = 2, Unknown = -1 }

// ── Normalized output ──
public sealed class ParsedRule
{
    public string Operator { get; }                     // Uppercase operator name
    public IReadOnlyList<string> Args { get; }          // Order argument strings
    public string OriginalRule { get; }                  // Verbatim input
    public string NormalizedRule { get; }                // Function-style equivalent
    public RuleSyntaxVersion SyntaxVersion { get; }      // Detected DSL syntax
    public bool IsValid { get; }                         // No Error-severity diagnostics
    public bool IsLiteral { get; }                       // SyntaxVersion == Literal
    public IReadOnlyList<RuleDiagnostic> Diagnostics { get; }
}

public sealed class RuleDiagnostic
{
    public RuleDiagnosticSeverity Severity { get; }    // Info / Warning / Error
    public string Code { get; }                        // e.g. "DSL001", "NRM001"
    public string Message { get; }
    public int Position { get; }                       // -1 = unknown
}

// ── Normalizer pipeline ──
public static class RuleNormalizer
{
    public static ParsedRule Normalize(string rule);
    // 1. Empty/null → NRM001
    // 2. Strip ":" prefix
    // 3. Bare legacy token (NOW, TODAY, USERNAME, etc.) → NRM002 deprecation warning
    // 4. Literal (no ":" and not a known operator) → Literal syntax
    // 5. Otherwise → NormalizeExpression (delegates to DotStyleRuleParser for dot-style)
}

public class DotStyleRuleParser
{
    public static ParsedRule Parse(string rule);
    // Converts "ADDDAYS.TODAY.7" → "ADDDAYS(TODAY,7)"
    // Detects operator from _knownOperators set
    // Skips dot-style if '(' appears before any '.'
    // Protects quoted segments: FORMAT.NOW.'yyyy-MM-dd' → FORMAT(NOW,'yyyy-MM-dd')
}

IDefaultValueValidationHelper — Three-Tier Validation

TierMethodChecks
1ValidateRuleSyntax(rule)Normalizes, checks IsValid, double punctuation, control chars, balanced parentheses
2ValidateRuleSemantic(rule)Operator arity (IF=3 args, LOOKUP=2), @Param references, known rule patterns
3ValidateRuleSafety(rule)Rejects EXEC, DROP, DELETE, TRUNCATE, SCRIPT, CMD, SHELL, POWERSHELL; blocks SQL injection

EntityDefaultsProfile — Template Factories

FactoryDefault Rules
AuditTemplate(entity)CreatedBy (:USERNAME), CreatedAt (:NOW), ModifiedBy (:USERNAME), ModifiedAt (:NOW), IsActive (true), Version (1), RowGuid (:NEWGUID)
TimestampTemplate(entity)CreatedAt (:NOW), ModifiedAt (:NOW)
UserStampTemplate(entity)CreatedBy (:USERNAME), ModifiedBy (:USERNAME)
IdentityTemplate(entity)Id (:NEWGUID)
FullTemplate(entity)Id (:NEWGUID), CreatedBy (:USERNAME), CreatedAt (:NOW), ModifiedBy (:USERNAME), ModifiedAt (:NOW), IsActive (true), Version (1)

All 10 Built-in Resolvers — Complete Reference

#ResolverTypePriorityCacheableKey Tokens
1DateTimeNon-deterministic100NoNOW, TODAY, YESTERDAY, TOMORROW, STARTOFMONTH, ENDOFMONTH, STARTOFYEAR, ENDOFYEAR, STARTOFWEEK, ENDOFWEEK, ADDDAYS, ADDHOURS, ADDMONTHS, ADDYEARS, FORMAT
2UserContextDeterministic100YesUSERNAME, USERID, USEREMAIL, USERLOGIN, USERDOMAIN, USERPROFILE, USERGROUP, USERROLE, USERPRINCIPAL
3SystemInfoDeterministic100YesMACHINENAME, VERSION, APPVERSION, OSVERSION, PLATFORM, PROCESSORCOUNT, WORKINGSET, TIMESTAMP, TICKS
4GuidNon-deterministic100NoNEWGUID, GUID, UUID, GENERATEUNIQUEID + format args: N, D, B, P
5FormulaNon-deterministic100NoSEQUENCE, INCREMENT, RANDOM, CALCULATE, ADD, SUBTRACT, MULTIPLY, DIVIDE, ROUND + math: PI, SQRT, ABS, SIN, COS, TAN
6ExpressionDeterministic100Yes*IF, CASE, ISNULL, COALESCE, AND, OR, NOT, EQ, NE, GT, GTE, LT, LTE
7ConfigurationDeterministic100YesCONFIG, CONFIGURATIONVALUE, APPSETTING, SETTING, CONNECTIONSTRING
8EnvironmentDeterministic100YesENV, ENVIRONMENTVARIABLE, TEMP, SYSTEMPATH, USERPATH
9DataSourceNon-deterministic100NoLOOKUP, QUERY, COUNT, MAX, MIN, SUM, AVG, GETENTITY
10ObjectPropertyDeterministic100YesPROPERTY, FIELD, NESTED, ARRAYITEM, DICTVALUE, PARENTVALUE

Source File Map (31 files, 4,200+ lines)

FileContent
DefaultsManager.csStatic facade, static accessors (Helper/Resolver/Validation), profile registry (lock-guarded Dictionary), Resolve, legacy API
DefaultsManager.Apply.cs3 ApplyInstance methods: Dictionary, POCO<T> via reflection, DataRow — ApplyOnlyIfNull logic + ConvertValue
IDefaultsManager.csDI contract — 22 methods across lifecycle, profiles, apply, resolve, resolver management, legacy persistence
EntityDefaultsProfile.csFieldDefaultRule + EntityDefaultsProfile: fluent Set/Remove builder, 5 template factories, computed IsExpression/ExpressionBody
Resolvers/BaseDefaultValueResolver.csAbstract base: IResolverCapabilities virtuals, 8 protected logging/string parsing/parameter extraction helpers
Resolvers/DefaultValueResolverManager.csPipeline manager: 10 built-in registrations, priority-ordered CanHandle dispatch, value/rule caching, telemetry
Resolvers/DateTimeResolver.csNOW, TODAY, YESTERDAY, TOMORROW, STARTOF*/ENDOF*, ADDDAYS/HOURS/MINUTES/MONTHS/YEARS, FORMAT
Resolvers/UserContextResolver.csUSERNAME, USERID, USEREMAIL, USERDOMAIN, USERPROFILE, USERGROUP via WindowsPrincipal, USERROLE
Resolvers/SystemInfoResolver.csMACHINENAME, VERSION, APPVERSION, OSVERSION, PLATFORM, PROCESSORCOUNT, WORKINGSET, TIMESTAMP, TICKS
Resolvers/GuidResolver.csNEWGUID, GUID, UUID, GENERATEUNIQUEID — format specifiers: N, D, B, P, X (lower+upper)
Resolvers/FormulaResolver.csSEQUENCE, INCREMENT, RANDOM, CALCULATE, COMPUTE, ADD, SUBTRACT, MULTIPLY, DIVIDE, ROUND — math: PI, SQRT, ABS, SIN, COS, TAN, FLOOR, CEILING
Resolvers/ExpressionResolver.csIF, CASE, ISNULL, COALESCE, AND, OR, NOT, EQ, NE, GT, GTE, LT, LTE — fully evaluated conditionals + logical + comparison
Resolvers/ConfigurationResolver.csCONFIG, APPSETTING, SETTING, CONNECTIONSTRING — reads app/web config at runtime
Resolvers/EnvironmentResolver.csENV, TEMP, SYSTEMPATH, USERPATH — targets (USER/MACHINE/PROCESS), GetAllEnvironmentVariables
Resolvers/DataSourceResolver.csLOOKUP, QUERY, COUNT, MAX, MIN, SUM, AVG, GETENTITY — @Param binding from context
Resolvers/ObjectPropertyResolver.csPROPERTY, FIELD, NESTED dot-paths, ARRAYITEM (negative index support), DICTVALUE
Registry/DefaultResolverRegistry.cs[DefaultResolverAttribute] assembly discovery, descriptor creation, resolver instantiation
Registry/DefaultResolverDescriptor.csAttribute + Type pairing for registered resolver metadata
Helpers/DefaultValueHelper.csGetDefaults, SaveDefaults, CreateDefaultValue, GetDefaultForField, GetMultipleDefaults, FindDefaultsByPropertyName
Helpers/DefaultValueValidationHelper.csThree-tier validation: syntax → semantic → safety; rejects dangerous patterns, SQL injection
Interfaces/IDefaultValueResolver.csResolver contract: ResolverName, SupportedRuleTypes, ResolveValue, CanHandle, GetExamples
Interfaces/IDefaultValueHelper.csGetDefaults, SaveDefaults, GetDefaultForField, CreateDefaultValue, ValidateDefaultValue
Interfaces/IDefaultValueValidationHelper.csValidateDefaultValue, ValidateRule, ValidateFieldName
Interfaces/IDefaultValueResolverManager.cs15 methods: registration, resolution, telemetry, parsing, caching, introspection
Interfaces/IResolverCapabilities.csPriority, IsDeterministic, SupportsCaching, SupportsAsync
Interfaces/ResolverContext.csIPassedArgs + Extras dictionary + EntityName/FieldName shortcuts + GetExtra<T>
Interfaces/ResolverExecutionResult.cs9 init-only telemetry properties + FNV-1a fingerprint computation
Interfaces/IDefaultValueInterfaces.csPlaceholder — each type lives in its own file
RuleParsing/ParsedRule.csRuleSyntaxVersion enum, RuleDiagnostic, ParsedRule — 9 properties, ToString → NormalizedRule
RuleParsing/RuleNormalizer.csNormalize pipeline: empty/null check, strip ":", legacy deprecation, literal detection, expression dispatch
RuleParsing/DotStyleRuleParser.csDot-style → function-style conversion: ADDDAYS.TODAY.7 → ADDDAYS(TODAY,7), quoted segment protection