InstantMetadata
Unit: InstantMetadataCategory: Core
Overview
The InstantMetadata unit provides the complete metadata system for InstantObjects. It defines how business objects map to database structures, including class definitions, attributes, tables, fields, and indexes. This metadata drives both the Model (business classes) and the Scheme (database structure), enabling InstantObjects to automatically manage object-relational mapping.
Key Concepts:
- Model - Business object definitions (classes and attributes)
- Scheme - Database structure definitions (tables, fields, indexes)
- Metadata - Descriptive information about classes, attributes, and storage
- Catalog - Source of metadata (from Model or existing database)
- Attribute Maps - Connect object attributes to database fields
- Validation - Runtime validation of attribute values
The Metadata Bridge:
Business Model Metadata System Database Schema
┌────────────┐ ┌────────────┐ ┌────────────┐
│ TContact │ │ Attribute │ │ Contacts │
│ - Name │ ←────→ │ Map │ ←────→ │ Name │
│ - Email │ │ Storage │ │ Email │
└────────────┘ └────────────┘ └────────────┘Class Hierarchy
TInstantMetadata (abstract base)
├── TInstantClassMetadata (business class)
├── TInstantAttributeMetadata (class attribute)
├── TInstantTableMetadata (database table)
├── TInstantFieldMetadata (table field)
└── TInstantIndexMetadata (table index)
TInstantModel (business model container)
TInstantScheme (database schema container)
TInstantCatalog (abstract metadata source)
├── TInstantModelCatalog (from business model)
└── TInstantBrokerCatalog (from existing database)
TInstantAttributeMap (attribute-to-field mapping)
TInstantAttributeMaps (collection of maps)
TInstantValidator (attribute validation)TInstantMetadata
Abstract base class for all metadata objects.
Inheritance: TInstantCollectionItem → TInstantMetadata
Key Methods
// Compare metadata objects
function Equals(const Other: TInstantMetadata): Boolean;
// Assign from another metadata object
procedure Assign(Source: TPersistent); override;Properties
All metadata objects have a Name property inherited from TInstantCollectionItem.
TInstantClassMetadata
Describes a business class in the model.
Inheritance: TInstantMetadata → TInstantClassMetadata
Key Properties
| Property | Type | Description |
|---|---|---|
AttributeMetadatas | TInstantAttributeMetadatas | Collection of attribute definitions |
Collection | TInstantClassMetadatas | Owning collection |
DefaultContainerName | string | Default container for Parts |
IsEmpty | Boolean | True if class has no attributes |
IsStored | Boolean | True if persistence enabled |
MemberMap | TInstantAttributeMap | All attributes (including inherited) |
Parent | TInstantClassMetadata | Parent class metadata |
ParentName | string | Parent class name |
Persistence | TInstantPersistence | Persistence mode |
StorageMap | TInstantAttributeMap | Root table attributes only |
StorageMaps | TInstantAttributeMaps | All storage maps (root + external) |
StorageName | string | Physical table name |
TableName | string | Alias for StorageName |
TInstantPersistence Values
type
TInstantPersistence = (
peStored, // Normal persistence (default)
peEmbedded, // Stored as blob in parent
peVirtual // Not persisted (transient)
);Example Usage
var
ClassMeta: TInstantClassMetadata;
Attr: TInstantAttributeMetadata;
begin
// Create class metadata
ClassMeta := Model.ClassMetadatas.Add;
ClassMeta.Name := 'TContact';
ClassMeta.ParentName := 'TInstantObject';
ClassMeta.Persistence := peStored;
ClassMeta.StorageName := 'CONTACTS';
// Add attributes
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Name';
Attr.AttributeType := atString;
Attr.Size := 100;
Attr.IsRequired := True;
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Email';
Attr.AttributeType := atString;
Attr.Size := 100;
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Category';
Attr.AttributeType := atReference;
Attr.ObjectClassName := 'TCategory';
end;Accessing Metadata at Runtime
var
Contact: TContact;
ClassMeta: TInstantClassMetadata;
AttrMeta: TInstantAttributeMetadata;
begin
Contact := TContact.Create;
try
// Get class metadata
ClassMeta := Contact.Metadata;
ShowMessage(Format('Class: %s, Table: %s',
[ClassMeta.Name, ClassMeta.TableName]));
// Enumerate attributes
for I := 0 to ClassMeta.AttributeMetadatas.Count - 1 do
begin
AttrMeta := ClassMeta.AttributeMetadatas[I];
Memo1.Lines.Add(Format('%s: %s (%d)',
[AttrMeta.Name,
GetEnumName(TypeInfo(TInstantAttributeType), Ord(AttrMeta.AttributeType)),
AttrMeta.Size]));
end;
finally
Contact.Free;
end;
end;Inheritance and Attribute Maps
// TContact → TInstantObject
// TPerson → TContact (adds FirstName, LastName)
var
PersonMeta: TInstantClassMetadata;
begin
PersonMeta := Model.ClassMetadatas.Find('TPerson');
// MemberMap includes ALL attributes (from TContact + TPerson)
ShowMessage(Format('Total attributes: %d',
[PersonMeta.MemberMap.Count])); // Includes Name, Email, FirstName, LastName
// StorageMap includes only root table attributes
ShowMessage(Format('Root table attributes: %d',
[PersonMeta.StorageMap.Count])); // Attributes stored in main table
// AttributeMetadatas contains only TPerson's own attributes
ShowMessage(Format('Own attributes: %d',
[PersonMeta.AttributeMetadatas.Count])); // FirstName, LastName only
end;TInstantAttributeMetadata
Describes an attribute of a business class.
Inheritance: TInstantMetadata → TInstantAttributeMetadata
Key Properties
| Property | Type | Description |
|---|---|---|
AttributeType | TInstantAttributeType | Type of attribute |
AttributeTypeName | string | Type name as string |
AttributeClass | TInstantAbstractAttributeClass | Runtime attribute class |
AttributeClassName | string | Attribute class name |
Category | TInstantAttributeCategory | Simple, Complex, or Container |
ClassMetadata | TInstantClassMetadata | Owner class metadata |
DefaultValue | string | Default value expression |
UseNull | Boolean | Allow NULL values |
DisplayLabel | string | UI display label |
DisplayWidth | Integer | UI display width |
EditMask | string | Input mask |
EnumName | string | Enumeration type name |
ExternalStorageName | string | External table name for Parts/References |
FieldName | string | Database field name |
ForeignKeyFields | string | Custom foreign key fields |
IsDefault | Boolean | Default attribute for display |
IsDescription | Boolean | Description attribute |
IsIndexed | Boolean | Create index on field |
IsLocalized | Boolean | Localized content |
IsPrimaryKey | Boolean | Part of primary key |
IsRequired | Boolean | Required (NOT NULL) |
IsUnique | Boolean | Unique constraint |
IndexName | string | Index name |
ObjectClass | TInstantAbstractObjectClass | Referenced object class |
ObjectClassName | string | Referenced class name (for Reference/Part) |
Size | Integer | Field size (for strings, memos) |
StorageKind | TInstantStorageKind | Embedded, External, or Virtual |
StorageName | string | Database column name |
TableName | string | Physical table name |
ValidCharsString | string | Valid character set for validation |
TInstantAttributeType Values
type
TInstantAttributeType = (
atUnknown, // Not set
// Simple types
atInteger, // Integer value
atFloat, // Floating point value
atCurrency, // Currency value
atBoolean, // Boolean value
atString, // String value
atDateTime, // Date and time
atDate, // Date only
atTime, // Time only
atEnumeration, // Enumeration value
// Complex types
atBlob, // Binary large object
atMemo, // Text memo
atGraphic, // Image/graphic
// Container types
atPart, // Single owned object
atReference, // Single referenced object
atParts, // Collection of owned objects
atReferences // Collection of referenced objects
);TInstantAttributeCategory Values
type
TInstantAttributeCategory = (
acSimple, // Simple value types
acComplex, // Blob, Memo, Graphic
acContainer // Part, Parts, Reference, References
);TInstantStorageKind Values
type
TInstantStorageKind = (
skEmbedded, // Stored in class table (default)
skExternal, // Stored in separate table
skVirtual // Not stored (transient)
);Example: Defining Attributes
// String attribute with validation
var
Attr: TInstantAttributeMetadata;
begin
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Email';
Attr.AttributeType := atString;
Attr.Size := 100;
Attr.IsRequired := True;
Attr.IsUnique := True;
Attr.IsIndexed := True;
Attr.DisplayLabel := 'E-mail Address';
Attr.ValidCharsString := 'a-zA-Z0-9@._-';
end;
// Reference attribute
var
Attr: TInstantAttributeMetadata;
begin
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Category';
Attr.AttributeType := atReference;
Attr.ObjectClassName := 'TCategory';
Attr.StorageKind := skEmbedded; // Class and Id stored in Contact table
Attr.IsRequired := True;
end;
// Parts collection with external storage
var
Attr: TInstantAttributeMetadata;
begin
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Phones';
Attr.AttributeType := atParts;
Attr.ObjectClassName := 'TPhone';
Attr.StorageKind := skExternal; // Stored in separate table
Attr.ExternalStorageName := 'ContactPhones';
end;
// Enumeration attribute
var
Attr: TInstantAttributeMetadata;
begin
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Status';
Attr.AttributeType := atEnumeration;
Attr.EnumName := 'TContactStatus'; // Type name: (csActive, csInactive, csDeleted)
end;Validation
// Define valid characters
Attr.ValidCharsString := 'a-zA-Z0-9._-'; // Alphanumeric, dot, underscore, hyphen
// At runtime, validation occurs automatically
Contact.Name := 'John@Doe'; // Raises exception if @ not in ValidCharsString
// Or validate manually
var
ErrorMsg: string;
begin
if not Attr.Validator.IsValid(Contact._Name, 'Invalid$Name', ErrorMsg) then
ShowMessage('Validation failed: ' + ErrorMsg);
end;Creating Attributes at Runtime
// Create attribute instance from metadata
var
Contact: TContact;
AttrMeta: TInstantAttributeMetadata;
Attribute: TInstantAttribute;
begin
Contact := TContact.Create;
try
AttrMeta := Contact.Metadata.AttributeMetadatas.Find('Name');
// Create attribute instance
Attribute := AttrMeta.CreateAttribute(Contact) as TInstantAttribute;
ShowMessage(Format('Created: %s (%s)',
[Attribute.Name, Attribute.ClassName]));
finally
Contact.Free;
end;
end;TInstantTableMetadata
Describes a database table in the schema.
Inheritance: TInstantMetadata → TInstantTableMetadata
Key Properties
| Property | Type | Description |
|---|---|---|
FieldMetadatas | TInstantFieldMetadatas | Collection of fields |
FieldMetadataCount | Integer | Number of fields |
IndexMetadatas | TInstantIndexMetadatas | Collection of indexes |
IndexMetadataCount | Integer | Number of indexes |
Scheme | TInstantScheme | Owner scheme |
Key Methods
// Find field by name
function FindFieldMetadata(const AName: string): TInstantFieldMetadata;
// Find index by name
function FindIndexMetadata(const AName: string): TInstantIndexMetadata;
// Assign from another table
procedure Assign(Source: TPersistent); override;Example Usage
var
Table: TInstantTableMetadata;
Field: TInstantFieldMetadata;
Index: TInstantIndexMetadata;
begin
// Create table
Table := Scheme.TableMetadataCollection.Add;
Table.Name := 'CONTACTS';
// Add fields
Table.FieldMetadatas.AddFieldMetadata('Class', dtString, 32, [foRequired]);
Table.FieldMetadatas.AddFieldMetadata('Id', dtString, 32, [foRequired]);
Table.FieldMetadatas.AddFieldMetadata('UpdateCount', dtInteger, 0, []);
Table.FieldMetadatas.AddFieldMetadata('Name', dtString, 100, [foRequired]);
Table.FieldMetadatas.AddFieldMetadata('Email', dtString, 100, []);
Table.FieldMetadatas.AddFieldMetadata('CategoryClass', dtString, 32, []);
Table.FieldMetadatas.AddFieldMetadata('CategoryId', dtString, 32, []);
// Add indexes
Table.IndexMetadatas.AddIndexMetadata('PK_CONTACTS', 'Class;Id', [ixPrimary, ixUnique]);
Table.IndexMetadatas.AddIndexMetadata('IX_EMAIL', 'Email', []);
Table.IndexMetadatas.AddIndexMetadata('IX_CATEGORY', 'CategoryClass;CategoryId', []);
end;Reading Table Metadata from Database
var
Scheme: TInstantScheme;
Catalog: TInstantCatalog;
Table: TInstantTableMetadata;
Field: TInstantFieldMetadata;
begin
Scheme := TInstantScheme.Create;
try
// Create catalog from broker
Catalog := FireDACConnector.Broker.CreateCatalog(Scheme);
Scheme.Catalog := Catalog;
// Read from database
Catalog.InitTableMetadatas(Scheme.TableMetadataCollection);
// Enumerate tables and fields
for I := 0 to Scheme.TableMetadataCount - 1 do
begin
Table := Scheme.TableMetadatas[I];
Memo1.Lines.Add('Table: ' + Table.Name);
for J := 0 to Table.FieldMetadataCount - 1 do
begin
Field := Table.FieldMetadatas[J];
Memo1.Lines.Add(Format(' %s: %s(%d)',
[Field.Name,
GetEnumName(TypeInfo(TInstantDataType), Ord(Field.DataType)),
Field.Size]));
end;
end;
finally
Scheme.Free;
end;
end;TInstantFieldMetadata
Describes a field in a database table.
Inheritance: TInstantMetadata → TInstantFieldMetadata
Key Properties
| Property | Type | Description |
|---|---|---|
Collection | TInstantFieldMetadatas | Owning collection |
DataType | TInstantDataType | Field data type |
AlternateDataTypes | TInstantDataTypes | Alternative data types |
Options | TInstantFieldOptions | Field options |
Size | Integer | Field size (for strings, etc.) |
TableMetadata | TInstantTableMetadata | Owner table |
TInstantDataType Values
type
TInstantDataType = (
dtString, // String/VARCHAR
dtInteger, // Integer
dtFloat, // Float/DOUBLE
dtCurrency, // Currency/DECIMAL
dtBoolean, // Boolean/BIT
dtDateTime, // DateTime/TIMESTAMP
dtDate, // Date
dtTime, // Time
dtBlob, // Binary blob
dtMemo // Text memo/CLOB
);
TInstantDataTypes = set of TInstantDataType;TInstantFieldOptions Values
type
TInstantFieldOption = (
foRequired, // NOT NULL
foIndexed, // Create index
foUnique, // Unique constraint
foPrimaryKey // Part of primary key
);
TInstantFieldOptions = set of TInstantFieldOption;Example Usage
var
Field: TInstantFieldMetadata;
begin
// Create field with options
Field := Table.FieldMetadatas.Add;
Field.Name := 'Email';
Field.DataType := dtString;
Field.Size := 100;
Field.Options := [foRequired, foIndexed, foUnique];
// Or use helper method
Table.FieldMetadatas.AddFieldMetadata('Name', dtString, 100, [foRequired]);
end;Data Type Comparison
// Compare data types (considering alternates)
var
Field1, Field2: TInstantFieldMetadata;
begin
Field1.DataType := dtInteger;
Field1.AlternateDataTypes := [dtBoolean]; // DB represents both as INT
Field2.DataType := dtBoolean;
if Field1.DataTypesEqual(Field2) then
ShowMessage('Fields are compatible'); // True, because dtBoolean in alternates
end;TInstantIndexMetadata
Describes an index on a database table.
Inheritance: TInstantMetadata → TInstantIndexMetadata
Key Properties
| Property | Type | Description |
|---|---|---|
Collection | TInstantIndexMetadatas | Owning collection |
Fields | string | Semicolon-separated field list |
Options | TIndexOptions | Index options (from DB unit) |
TableMetadata | TInstantTableMetadata | Owner table |
TIndexOptions Values
type
TIndexOptions = set of TIndexOption;
TIndexOption = (
ixPrimary, // Primary key index
ixUnique, // Unique index
ixDescending, // Descending sort
ixCaseInsensitive, // Case-insensitive
ixExpression, // Expression-based index
ixNonMaintained // Not automatically maintained
);Key Methods
// Check if field is part of this index
function IsFieldIndexed(const AFieldMetadata: TInstantFieldMetadata): Boolean;
// Assign from another index
procedure Assign(Source: TPersistent); override;Example Usage
var
Index: TInstantIndexMetadata;
begin
// Create primary key
Index := Table.IndexMetadatas.Add;
Index.Name := 'PK_CONTACTS';
Index.Fields := 'Class;Id'; // Composite key
Index.Options := [ixPrimary, ixUnique];
// Create unique index
Index := Table.IndexMetadatas.Add;
Index.Name := 'UQ_EMAIL';
Index.Fields := 'Email';
Index.Options := [ixUnique];
// Create non-unique index
Index := Table.IndexMetadatas.Add;
Index.Name := 'IX_NAME';
Index.Fields := 'Name';
Index.Options := []; // No special options
// Or use helper method
Table.IndexMetadatas.AddIndexMetadata('IX_CATEGORY', 'CategoryClass;CategoryId', []);
end;Checking Field Indexes
var
EmailField: TInstantFieldMetadata;
Index: TInstantIndexMetadata;
begin
EmailField := Table.FindFieldMetadata('Email');
// Check if field is indexed
if Table.IndexMetadatas.IsFieldIndexed(EmailField) then
ShowMessage('Email field is indexed');
// Find specific index
for I := 0 to Table.IndexMetadataCount - 1 do
begin
Index := Table.IndexMetadatas[I];
if Index.IsFieldIndexed(EmailField) then
ShowMessage('Found in index: ' + Index.Name);
end;
end;TInstantModel
Container for business class metadata (the business model).
Inheritance: TPersistent → TInstantModel
Key Properties
| Property | Type | Description |
|---|---|---|
ClassMetadatas | TInstantClassMetadatas | Collection of class definitions |
Key Methods
destructor Destroy;
// Load/save model from/to file
procedure LoadFromFile(const FileName: string; Format: TInstantStreamFormat = sfXML);
procedure SaveToFile(const FileName: string; Format: TInstantStreamFormat = sfXML);
// Load/save from/to resource file
procedure LoadFromResFile(const FileName: string);
procedure SaveToResFile(const FileName: string);
procedure MergeFromResFile(const FileName: string);Example: Creating a Model
var
Model: TInstantModel;
ClassMeta: TInstantClassMetadata;
Attr: TInstantAttributeMetadata;
begin
Model := TInstantModel.Create;
try
// Define Contact class
ClassMeta := Model.ClassMetadatas.Add;
ClassMeta.Name := 'TContact';
ClassMeta.StorageName := 'CONTACTS';
ClassMeta.Persistence := peStored;
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Name';
Attr.AttributeType := atString;
Attr.Size := 100;
Attr.IsRequired := True;
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Email';
Attr.AttributeType := atString;
Attr.Size := 100;
// Define Category class
ClassMeta := Model.ClassMetadatas.Add;
ClassMeta.Name := 'TCategory';
ClassMeta.StorageName := 'CATEGORIES';
Attr := ClassMeta.AttributeMetadatas.Add;
Attr.Name := 'Name';
Attr.AttributeType := atString;
Attr.Size := 50;
Attr.IsRequired := True;
// Save model
Model.SaveToFile('Model.xml');
finally
Model.Free;
end;
end;Example: Loading and Using Model
var
Model: TInstantModel;
ClassMeta: TInstantClassMetadata;
begin
Model := TInstantModel.Create;
try
// Load from file
Model.LoadFromFile('Model.xml');
// Access class metadata
ClassMeta := Model.ClassMetadatas.Find('TContact');
if Assigned(ClassMeta) then
begin
ShowMessage(Format('Class: %s, Table: %s, Attributes: %d',
[ClassMeta.Name,
ClassMeta.StorageName,
ClassMeta.AttributeMetadatas.Count]));
end;
finally
Model.Free;
end;
end;Embedding Model in Application
// At design time, save model as resource
Model.SaveToResFile('MyAppModel.res');
// At runtime, load from embedded resource
var
Model: TInstantModel;
begin
Model := TInstantModel.Create;
try
Model.LoadFromResFile('MyAppModel.res');
// Model is now loaded from embedded resource
finally
Model.Free;
end;
end;
// Or merge additional classes
Model.MergeFromResFile('PluginModel.res'); // Adds classes without replacingTInstantScheme
Container for database table metadata (the database schema).
Inheritance: TInstantStreamable → TInstantScheme
Key Properties
| Property | Type | Description |
|---|---|---|
Catalog | TInstantCatalog | Metadata source (Model or Database) |
TableMetadatas[Index] | TInstantTableMetadata | Table by index |
TableMetadataCount | Integer | Number of tables |
BlobStreamFormat | TInstantStreamFormat | Format for blob serialization |
IdDataType | TInstantDataType | Data type for Id fields |
IdSize | Integer | Size for Id fields |
Events
| Event | Type | Description |
|---|---|---|
OnWarning | TInstantWarningEvent | Non-fatal warning messages |
Key Methods
constructor Create;
destructor Destroy;
// Find table by name
function FindTableMetadata(const AName: string): TInstantTableMetadata;
// Convert attribute type to data type
function AttributeTypeToDataType(AttributeType: TInstantAttributeType): TInstantDataType; virtual;Example: Creating Schema from Model
var
Model: TInstantModel;
Scheme: TInstantScheme;
Catalog: TInstantModelCatalog;
begin
Model := TInstantModel.Create;
try
Model.LoadFromFile('Model.xml');
// Create scheme from model
Scheme := TInstantScheme.Create;
try
Catalog := TInstantModelCatalog.Create(Scheme, Model);
Scheme.Catalog := Catalog;
// Initialize tables from model
Catalog.InitTableMetadatas(Scheme.TableMetadataCollection);
// Now Scheme contains table definitions
ShowMessage(Format('Generated %d tables', [Scheme.TableMetadataCount]));
// Save scheme
Scheme.WriteToFile('Scheme.xml');
finally
Scheme.Free;
end;
finally
Model.Free;
end;
end;Example: Reading Schema from Database
var
Scheme: TInstantScheme;
Catalog: TInstantCatalog;
begin
Scheme := TInstantScheme.Create;
try
// Create catalog from broker (reads existing database)
Catalog := FireDACConnector.Broker.CreateCatalog(Scheme);
Scheme.Catalog := Catalog;
// Read schema from database
Catalog.InitTableMetadatas(Scheme.TableMetadataCollection);
// Schema now contains current database structure
for I := 0 to Scheme.TableMetadataCount - 1 do
Memo1.Lines.Add(Scheme.TableMetadatas[I].Name);
finally
Scheme.Free;
end;
end;Configuration
// Configure ID field settings
Scheme.IdDataType := dtString; // or dtInteger
Scheme.IdSize := 32; // For string IDs
// Configure blob format
Scheme.BlobStreamFormat := sfBinary; // or sfXML for debuggingTInstantCatalog
Abstract base class for metadata sources.
Inheritance: TObject → TInstantCatalog
Key Properties
| Property | Type | Description |
|---|---|---|
Scheme | TInstantScheme | Associated scheme |
Features | TInstantCatalogFeatures | Supported features |
Events
| Event | Type | Description |
|---|---|---|
OnWarning | TInstantWarningEvent | Non-fatal warning messages |
Key Methods
constructor Create(const AScheme: TInstantScheme);
// Initialize table metadata (must override)
procedure InitTableMetadatas(ATableMetadatas: TInstantTableMetadatas); virtual; abstract;TInstantCatalogFeatures
type
TInstantCatalogFeature = (
cfReadTableInfo, // Can read table names
cfReadFieldInfo, // Can read field definitions
cfReadIndexInfo // Can read index definitions
);
TInstantCatalogFeatures = set of TInstantCatalogFeature;TInstantModelCatalog
Catalog that generates schema from business model.
Inheritance: TInstantCatalog → TInstantModelCatalog
Key Properties
| Property | Type | Description |
|---|---|---|
Model | TInstantModel | Source business model |
Scheme | TInstantScheme | Target database schema |
Key Methods
constructor Create(const AScheme: TInstantScheme; const AModel: TInstantModel);
// Generate table metadata from model
procedure InitTableMetadatas(ATableMetadatas: TInstantTableMetadatas); override;Example: Model to Schema Conversion
var
Model: TInstantModel;
Scheme: TInstantScheme;
Catalog: TInstantModelCatalog;
ClassMeta: TInstantClassMetadata;
Table: TInstantTableMetadata;
begin
Model := TInstantModel.Create;
try
// Define model
ClassMeta := Model.ClassMetadatas.Add;
ClassMeta.Name := 'TContact';
ClassMeta.StorageName := 'CONTACTS';
// ... add attributes ...
// Generate schema from model
Scheme := TInstantScheme.Create;
try
Catalog := TInstantModelCatalog.Create(Scheme, Model);
Scheme.Catalog := Catalog;
Catalog.InitTableMetadatas(Scheme.TableMetadataCollection);
// Schema contains generated tables
Table := Scheme.FindTableMetadata('CONTACTS');
if Assigned(Table) then
ShowMessage(Format('Table %s has %d fields',
[Table.Name, Table.FieldMetadataCount]));
finally
Scheme.Free;
end;
finally
Model.Free;
end;
end;TInstantAttributeMap
Maps object attributes to database fields for a specific storage table.
Inheritance: TInstantNamedList → TInstantAttributeMap
Key Properties
| Property | Type | Description |
|---|---|---|
ClassMetadata | TInstantClassMetadata | Owner class metadata |
IsRootMap | Boolean | True if this is the root (main) table map |
Items[Index] | TInstantAttributeMetadata | Attribute by index |
Name | string | Map name (table name) |
Key Methods
constructor Create(AClassMetadata: TInstantClassMetadata);
// Add attribute to map
function Add(Item: TInstantAttributeMetadata): Integer;
// Add only if not already present
function AddUnique(Item: TInstantAttributeMetadata): Integer;
// Find attribute by name
function Find(const AName: string): TInstantAttributeMetadata;
// Get index of attribute
function IndexOf(Item: TInstantAttributeMetadata): Integer;
// Insert at specific position
procedure Insert(Index: Integer; Item: TInstantAttributeMetadata);
// Remove attribute from map
function Remove(Item: TInstantAttributeMetadata): Integer;Example Usage
var
Contact: TContact;
RootMap: TInstantAttributeMap;
ExternalMap: TInstantAttributeMap;
Attr: TInstantAttributeMetadata;
begin
Contact := TContact.Create;
try
// Get root storage map (main table)
RootMap := Contact.Metadata.StorageMap;
ShowMessage(Format('Root table: %s, Fields: %d',
[RootMap.Name, RootMap.Count]));
// Enumerate attributes in root table
for I := 0 to RootMap.Count - 1 do
begin
Attr := RootMap[I];
Memo1.Lines.Add(Format('%s -> %s',
[Attr.Name, Attr.FieldName]));
end;
// Get external storage maps (e.g., for Parts)
if Contact.Metadata.StorageMaps.Count > 1 then
begin
ExternalMap := Contact.Metadata.StorageMaps[1]; // First external table
ShowMessage(Format('External table: %s for %d attributes',
[ExternalMap.Name, ExternalMap.Count]));
end;
finally
Contact.Free;
end;
end;TInstantAttributeMaps
Collection of attribute maps (one per storage table).
Inheritance: TObjectList → TInstantAttributeMaps
Key Properties
| Property | Type | Description |
|---|---|---|
ClassMetadata | TInstantClassMetadata | Owner class metadata |
RootMap | TInstantAttributeMap | Main table map |
Items[Index] | TInstantAttributeMap | Map by index |
Key Methods
constructor Create(AClassMetadata: TInstantClassMetadata);
// Add new map
function Add: TInstantAttributeMap; overload;
function Add(Item: TInstantAttributeMap): Integer; overload;
// Find map by name (table name)
function Find(const AName: string): TInstantAttributeMap;
// Find map containing specific attribute
function FindMap(const AttributeName: string): TInstantAttributeMap;
// Ensure map exists
function EnsureMap(const AName: string): TInstantAttributeMap;
// Standard collection methods
function IndexOf(Item: TInstantAttributeMap): Integer;
procedure Insert(Index: Integer; Item: TInstantAttributeMap);
function Remove(Item: TInstantAttributeMap): Integer;Example: Multi-Table Storage
// Contact class with external Phones storage
// Root table: CONTACTS (Name, Email, etc.)
// External table: ContactPhones (Phone numbers)
var
Contact: TContact;
Maps: TInstantAttributeMaps;
Map: TInstantAttributeMap;
begin
Contact := TContact.Create;
try
Maps := Contact.Metadata.StorageMaps;
ShowMessage(Format('Storage tables: %d', [Maps.Count]));
// Root map (CONTACTS table)
Map := Maps.RootMap;
Memo1.Lines.Add(Format('Root: %s (%d attributes)',
[Map.Name, Map.Count]));
// External maps (ContactPhones, etc.)
for I := 1 to Maps.Count - 1 do
begin
Map := Maps[I];
Memo1.Lines.Add(Format('External: %s (%d attributes)',
[Map.Name, Map.Count]));
end;
// Find map for specific attribute
Map := Maps.FindMap('Phones'); // Returns ContactPhones map
if Assigned(Map) then
ShowMessage('Phones stored in: ' + Map.Name);
finally
Contact.Free;
end;
end;TInstantValidator
Base class for attribute validation.
Inheritance: TObject → TInstantValidator
Key Properties
| Property | Type | Description |
|---|---|---|
Metadata | TInstantAttributeMetadata | Associated attribute metadata |
Key Methods
// Create validator for metadata (class method)
class function CreateValidator(const AMetadata: TInstantAttributeMetadata): TInstantValidator; virtual;
// Validate value (must override)
function IsValid(const AAttribute: TInstantAbstractAttribute;
const AValue: string; out AValidationErrorText: string): Boolean; virtual; abstract;
// Validate and raise exception if invalid
procedure Validate(const AAttribute: TInstantAbstractAttribute; const AValue: string);Example: Custom Validator
type
TEmailValidator = class(TInstantValidator)
public
class function CreateValidator(const AMetadata: TInstantAttributeMetadata): TInstantValidator; override;
function IsValid(const AAttribute: TInstantAbstractAttribute;
const AValue: string; out AValidationErrorText: string): Boolean; override;
end;
class function TEmailValidator.CreateValidator(const AMetadata: TInstantAttributeMetadata): TInstantValidator;
begin
// Apply to attributes named 'Email'
if SameText(AMetadata.Name, 'Email') then
Result := TEmailValidator.Create
else
Result := nil;
end;
function TEmailValidator.IsValid(const AAttribute: TInstantAbstractAttribute;
const AValue: string; out AValidationErrorText: string): Boolean;
begin
Result := Pos('@', AValue) > 0;
if not Result then
AValidationErrorText := 'Email must contain @';
end;
// Register validator
InstantRegisterValidatorClass(TEmailValidator);
// Validation occurs automatically
Contact.Email := 'invalid'; // Raises exception: "Email must contain @"Common Usage Patterns
Accessing Runtime Metadata
// Get metadata from object instance
var
Contact: TContact;
ClassMeta: TInstantClassMetadata;
AttrMeta: TInstantAttributeMetadata;
begin
Contact := TContact.Create;
try
// Class metadata
ClassMeta := Contact.Metadata;
// Attribute metadata
AttrMeta := ClassMeta.AttributeMetadatas.Find('Email');
// Check properties
if AttrMeta.IsRequired then
ShowMessage('Email is required');
// Access attribute through metadata
Contact.AttributeByName('Email').AsString := 'john@example.com';
finally
Contact.Free;
end;
end;Enumerating All Classes and Attributes
var
Model: TInstantModel;
ClassMeta: TInstantClassMetadata;
AttrMeta: TInstantAttributeMetadata;
begin
Model := InstantDefaultModel; // Global model
for I := 0 to Model.ClassMetadatas.Count - 1 do
begin
ClassMeta := Model.ClassMetadatas[I];
Memo1.Lines.Add('Class: ' + ClassMeta.Name);
for J := 0 to ClassMeta.AttributeMetadatas.Count - 1 do
begin
AttrMeta := ClassMeta.AttributeMetadatas[J];
Memo1.Lines.Add(Format(' %s: %s',
[AttrMeta.Name, AttrMeta.AttributeTypeName]));
end;
end;
end;Building Schema from Model
// Generate database schema from business model
procedure BuildDatabaseFromModel(Connector: TInstantConnector);
var
Builder: TInstantDBBuilder;
begin
Builder := TInstantDBBuilder.Create(Connector);
try
// Schema is generated from Model automatically
Builder.Execute;
ShowMessage('Database created successfully');
finally
Builder.Free;
end;
end;Comparing Schemas for Evolution
uses
InstantDBEvolution;
procedure EvolveDatabaseSchema(Connector: TInstantConnector);
var
CurrentScheme: TInstantScheme;
TargetScheme: TInstantScheme;
Evolution: TInstantDBEvolution;
Catalog: TInstantCatalog;
begin
// Get current schema from database
CurrentScheme := TInstantScheme.Create;
try
Catalog := Connector.Broker.CreateCatalog(CurrentScheme);
CurrentScheme.Catalog := Catalog;
Catalog.InitTableMetadatas(CurrentScheme.TableMetadataCollection);
// Get target schema from model
TargetScheme := Connector.CreateScheme;
try
// Compare and evolve
Evolution := TInstantDBEvolution.Create(Connector);
try
Evolution.CurrentScheme := CurrentScheme;
Evolution.TargetScheme := TargetScheme;
Evolution.Execute; // Apply schema changes
finally
Evolution.Free;
end;
finally
TargetScheme.Free;
end;
finally
CurrentScheme.Free;
end;
end;Dynamic Attribute Access
// Access attributes dynamically using metadata
var
Contact: TContact;
AttrMeta: TInstantAttributeMetadata;
Attribute: TInstantAttribute;
begin
Contact := TContact.Create;
try
// Enumerate and display all attributes
for I := 0 to Contact.Metadata.MemberMap.Count - 1 do
begin
AttrMeta := Contact.Metadata.MemberMap[I];
Attribute := Contact.AttributeByName(AttrMeta.Name);
Memo1.Lines.Add(Format('%s = %s',
[AttrMeta.Name, Attribute.AsString]));
end;
finally
Contact.Free;
end;
end;Best Practices
Use Model Files
- Store model in XML/resource file
- Embed model in application for deployment
- Version control model files
Consistent Naming
- Use descriptive attribute names
- Use uppercase for table/field names
- Follow naming conventions consistently
Define Constraints in Metadata
- Set
IsRequiredfor NOT NULL - Set
IsUniquefor unique constraints - Set
IsIndexedfor frequently queried fields
- Set
External Storage for Collections
- Use
StorageKind = skExternalfor Parts/References - Specify
ExternalStorageNameexplicitly - Keep external tables organized
- Use
Validation
- Use
ValidCharsStringfor simple validation - Implement custom validators for complex rules
- Register validators at application startup
- Use
Schema Evolution
- Always compare current vs. target schema
- Test evolution on backup database first
- Use warning events to log changes
Performance
- Index foreign key fields
- Create composite indexes for common queries
- Use appropriate field sizes
Troubleshooting
Problem: "Attribute not found"
Cause: Metadata not loaded or attribute name mismatch Solution: Ensure model is loaded; check spelling and case sensitivity
Problem: "Table already exists"
Cause: Attempting to build database twice Solution: Use DBEvolution instead of DBBuild for updates
Problem: "Invalid attribute type"
Cause: AttributeType not set or incorrect Solution: Set AttributeType explicitly in metadata
Problem: "External storage not created"
Cause: StorageKind not set to skExternal Solution: Set StorageKind = skExternal and specify ExternalStorageName
Problem: "Validation always fails"
Cause: Invalid ValidCharsString format Solution: Use proper format: 'a-z' for range, 'abc' for specific chars
Problem: "Schema comparison fails"
Cause: Catalog features not supported Solution: Check Catalog.Features to see what's available
See Also
- InstantPersistence - Object persistence foundation
- InstantBrokers - Broker and resolver architecture
- InstantTypes - Type definitions and enumerations
- InstantDBBuild - Building database from model
- InstantDBEvolution - Evolving database schema
- Creating the Business Model - Model Explorer guide
- Defining Classes - Class definition guide
Version History
- Version 3.0 - Initial metadata system
- Version 3.5 - Added validation framework
- Version 4.0 - Enhanced attribute maps
- Version 4.2 - 64-bit support
- Version 4.3 - Neon serialization attributes
