Search Results for

    Show / Hide Table of Contents

    Multi-Model Design

    Most Aurelius applications uses single-model mapping. This means that all classes you map belongs to the same model. So for example when retrieving objects from the database, or creating the database structure, objects of all mapped classes will be available.

    But in some situations, you might need to have multiple mapping models. For example, you want your TCustomer entity class to belong to your default model, but you want TUserInfo entity class to belong to a different model ("Security" model for example). There are several reasons for this, for example:

    • You have more than one database you want to access from your application, with totally different structures.

    • You have some objects that you don't want to save to a database, but just want to use them in memory (using SQLite memory database).

    • You use other tools that uses Aurelius and you want to logically separate your entity classes for that. For example, when using TMS XData, you might want to use different models to create different server setups.

    • Any other reason you have to separate your classes into different mappings.

    There are two ways to define multiple mapping models: using Model attribute (preferrable), or manually creating a mapping setup. The following topics describe the two options and explain the concepts of multi-model design in Aurelius.

    Multi-Model Step-By-Step

    This topic explains very shortly how to use multiple mapping models with Aurelius. For more details about each step, please refer to main Multi-Model Design chapter.

    1. Add a Model attribute to each class indicating the model where the class belongs to:

    [Entity, Automapping]
    [Model('Sample')]
    TSampleCustomer = class
    {...}
    
    [Entity, Automapping]
    [Model('Security')]
    TUserInfo = class
    {...}
    
     // no model attribute means default model
    [Entity, Automapping]
    TCustomer = class
      {...}
    

    2. Retrieve the TMappingExplorer object associated with the model:

    uses 
      {...}, Aurelius.Mapping.Explorer;
    
    var
      SampleExplorer: TMappingExplorer;
      SecurityExplorer: TMappingExplorer;
      DefaultExplorer: TMappingExplorer;
    begin
      SampleExplorer := TMappingExplorer.Get('Sample');
      SecurityExplorer := TMappingExplorer.Get('Security');
      DefaultExplorer := TMappingExplorer.Default;
    

    3. Create an object manager using the proper mapping explorer:

    SampleManager := TObjectManager.Create(SampleConnection, SampleExplorer);
    SecurityManager := TObjectManager.Create(SecurityConnection, SecurityExplorer);
    DefaultManager := TObjectManager.Create(MyConnection, DefaultExplorer);
    

    or simply:

    SampleManager := TObjectManager.Create(SampleConnection, TMappingExplorer.Get('Sample'));
    SecurityManager := TObjectManager.Create(SecurityConnection, TMappingExplorer.Get('Security'));
    DefaultManager := TObjectManager.Create(MyConnection, TMappingExplorer.Default);
    

    For default manager you can simply omit the explorer:

    DefaultManager := TObjectManager.Create(MyConnection);
    

    4. You can also use the explorers in other needed places. For example, to create a database structure:

    // this example creates tables for "Sample" model in 
    // a SQL Server database using FireDac,
    // and "Security" model in a in-memory SQLite database
    SampleConnection := TFireDacConnectionAdapter.Create(FDConnection1, false);
    DBManager := TDatabaseManager.Create(SampleConnection, TMappingExplorer.Get('Sample'));
    DBManager.UpdateDatabase;
    DBManager.Free;
    
    SecurityConnection := TSQLiteNativeConnectionAdapter.Create(':memory:');
    DBManager := TDatabaseManager.Create(SecurityConnection, TMappingExplorer.Get('Security'));
    DBManager.UpdateDatabase;
    DBManager.Free;
    

    Using Model attribute

    Defining multiple mapping models in Aurelius is very straightforward if you use Model attribute. Basically all you need to do is annotate a class with the model attribute telling Aurelius the model where that class belongs to. For example, the following code specifies that class TUserInfo belongs to model "Security":

    // TUserInfo belongs to model "Security"
    [Entity, Automapping]
    [Model('Security')]
    TUserInfo = class
      {...}
    

    You can also include the class in multiple models, just by adding the Model attribute multiple times. The following example specifies that the class TSample belongs to both models "Security" and "Sample":

    // TSample belongs to model "Security" and "Sample"
    [Entity, Automapping]
    [Model('Security')]
    [Model('Sample')]
    TSample = class
      {...}
    

    In Aurelius, every mapped class belongs to a model. If you omit the Model attribute (since it's optional), the class will be included in the default model.

    // This class belongs to default model
    [Entity, Automapping]
    TCustomer = class
      {...}
    

    If you want to add a class to both default model and a different model, you can just add it to default model (named "Default"):

    // TUser belongs to both "Security" and default model
    [Entity, Automapping]
    [Model('Security')]
    [Model('Default')]
    TUser = class
      {...}
    

    You can then use the different models by retrieving the TMappingExplorer instance associated with a model.

    TMappingExplorer

    After Aurelius retrieves information about your mapping, it saves all that info in an object of class TMappingExplorer (declared in unit Aurelius.Mapping.Explorer). In other words, a TMappingExplorer object holds all mapping information. Although in some cases you might never need to deal with it directly, it is a key class when using Aurelius because that's the class it uses to perform all its operations on the entities.

    When you create an object manager, for example, you do it this way:

    Manager := TObjectManager.Create(DBConnection, MyMappingExplorer);
    

    And that is the same for the database manager. You can omit the parameter and create it like this:

    Manager := TObjectManager.Create(DBConnection);
    

    But this just means that you are telling the manager to use the default mapping explorer. It's the equivalent of doing this:

    Manager := TObjectManager.Create(DBConnection, TMappingExplorer.Default);
    

    Retrieving a TMappingExplorer instance

    As explained above, in single-model applications you will rarely need to deal with TMappingExplorer instances. All the mapping is available in the default TMappingExplorer instance, which is used automatically by the object manager and database manager. But when you have multiple mapping models in your application, you will need to tell the manager what mapping model it will be using. To help you in that task, Aurelius provides you with global TMappingExplorer instances. Aurelius creates (in a lazy way) one instance for each mapping model you have.

    To retrieve the TMappingExplorer instance associated with a model, just use the TMappingExplorer.Get class property passing the model name. In the following example, the object manager will use the "Security" model, instead of the default one.

    Manager := TObjectManager.Create(DBConnection, TMappingExplorer.Get('Security'));
    

    Note that you don't need to destroy the TMappingExplorer instance in this case, those are global instances that are destroyed automatically by Aurelius when application terminates. To retrieve the default instance, use the Default property:

    Manager := TObjectManager.Create(DBConnection, TMappingExplorer.Default);
    

    Creating a TMappingExplorer explicitly

    Usually you don't need to create a mapping explorer explicitly. As mentioned above, Aurelius automatically creates a default mapping explorer (available in class property TMappingExplorer.Default) and always uses it in any place where a TMappingExplorer object is needed but explicitly provided (like when creating the object manager). And you can also retrieve a mapping explorer instance for a specific model. So it's very rare you need to create one your own.

    But if you still need to do so, you can explicitly create a TMappingExplorer object using either a mapping setup or a model name. Here are the following available constructors.

    constructor Create(ASetup: TMappingSetup); overload;
    constructor Create(const ModelName: string); overload;
    

    To create a mapping explorer based on a mapping setup, just pass the setup to the constructor (check here to learn how to create mapping setups).

    MyExplorer := TMappingExplorer.Create(MyMappingSetup);
    

    Or, alternatively, you can just pass the model name. The explorer will only consider all entities belonging to the specified model:

    MyExplorer := TMappingExplorer.Create('Sample');
    
    Note

    You are responsible to destroy the TMappingExplorer instance you create explicitly.

    Mapping Setup

    Aurelius uses the mapping you have done to manipulate the objects. You do the mapping at design-time (adding attributes to your classes and class members), but this information is of course retrieved at run-time by Aurelius and is cached for better performance. This cached information is kept in an object of class TMappingExplorer. Whenever a TObjectManager object is created to manipulate the objects, a TMappingExplorer object must be provided to it, in order for the object manager to retrieve meta information about the mapping (or the default TMappingExplorer instance will be used).

    To create a TMappingExplorer object explicitly, you can pass an instance of a TMappingSetup object.

    So the order of "injection" of objects is illustrated below:

    TMappingSetup -> TMappingExplorer -> TObjectManager
    

    The following topics explain different ways of specifying the mapping setup and what custom settings you can do with mapping.

    Note

    Using Model attribute is a much easier way to create multi-model Aurelius applications when compared to mapping setup. Check the step-by-step topic to learn more about it.

    Defining a Mapping Setup

    To have full control over the mapping setup, the overall behavior is the following.

    1. Create and configure a TMappingSetup object.

    2. Create a TMappingExplorer object passing the TMappingSetup instance.

    3. Destroy the TMappingSetup object. Keep the TMappingExplorer instance.

    4. Create several TObjectManager instances passing the TMappingExplorer object.

    5. Destroy the TMappingExplorer object at the end of your application (or when all TObjectManager objects are destroyed and you have finished using Aurelius objects).

    The concept is that you obtain a TMappingExplorer object that contains an immutable cache of the mapping scheme, using some initial settings defined in TMappingSetup. Then you keep the instance of that TMappingeExplorer during the lifetime of the application, using it to create several object manager instances.

    Sample code:

    uses
      Aurelius.Mapping.Setup, 
      Aurelius.Mapping.Explorer, 
      Aurelius.Engine.ObjectManager;
    
    {...}
    
    var
      MapSetup: TMappingSetup;
    begin
      MapSetup := TMappingSetup.Create;
      try
        // Configure MapSetup object
        {..}
    
        // Now create exporer based on mapping setup
        FMappingExplorer := TMappingExplorer.Create(MapSetup);
      finally
        MapSetup.Free;
      end;
    
      // Now use FMappingExplorer to create instances of object manager
      FManager := TObjectManager.Create(MyConnection, FMappingExplorer);
      try
        // manipulate objects using the manager
      finally
        FManager.Free;
      end; 
    
      // Don't forget to destroy FMappingExplorer at the end of application
    end;
    

    Default Mapping Setup Behavior

    In most situations, you as a programmer don't need to worry about manually defining a mapping setup. This is because Aurelius provide some default settings and default instances that makes it transparent for you (and also for backward compatibility).

    There is a global TMappingExplorer object available in the following class function:

    class function TMappingExplorer.DefaultInstance: TMappingExplorer;
    

    that is lazily initialized that is used by Aurelius when you don't explicitly define a TMappingExplorer to use. That's what makes you possible to instantiate TObjectManager objects this way:

    Manager := TObjectManager.Create(MyConnection);
    

    The previous code is equivalent to this:

    Manager := TObjectManager.Create(MyConnection, TMappingExplorer.DefaultInstance);
    

    Note that the TMappingSetup object is not specified here. It means that the TMappingExplorer object initially available in TMappingExplorer.DefaultInstance internally uses an empty TMappingSetup object. This just means that no customization in the setup was done, and the default mapping (and all the design-time mapping done by you) is used normally.

    If you still want to define a custom mapping setup, but you don't want to create all your object manager instances passing a new explorer, you can alternatively change the TMappingExplorer.DefaultInstance. This way you can define a custom setup, and from that point, all TObjectManager objects to be created without an explicit TMappingExplorer parameter will use the new default instance. The following code illustrates how to change the default instance:

    uses
      Aurelius.Mapping.Setup, 
      Aurelius.Mapping.Explorer, 
      Aurelius.Engine.ObjectManager;
    
    {...}
    
    var
      MapSetup: TMappingSetup;
    begin
      MapSetup := TMappingSetup.Create;
      try
        // Configure the mapping setup 
    
        // Replace default instance of TMappingExplorer
        // MAKE SURE that no TObjectManager instances are alive using the old DefaultInstance
        TMappingExplorer.ReplaceDefaultInstance(TMappingExplorer.Create(MapSetup));
      finally
        MapSetup.Free;
      end;
    
      FManager := TObjectManager.Create(MyConnection);
      try
        // manipulate objects using the manager
      finally
        FManager.Free;
      end; 
    
      // No need to destroy the old or new default instances. Aurelius will manage them.
    end;
    

    Please attention to the comment in the code above. Make sure you have no existing TObjectManager instances that uses the old TMappingExplorer instance being replaced. This is because when calling ReplaceDefaultInstance method, the old default instance of TMappingExplorer is destroyed, and if there are any TObjectManager instances referencing the destroyed explorer, unexpected behavior might occur.

    Nevertheless, you would usually execute such example code above in the beginning of your application.

    Mapped Classes

    By default, TMS Aurelius maps all classes in the application marked with Entity attribute, for the default model. Alternatively, you can manually define which class will be mapped in each mapping setup. This allows you to have a differents set of classes for each database connection in the same application. For example, you can have classes A, B and C mapped to a SQL Server connection, and classes D and E mapped to a local SQLite connection.

    Defining mapped classes

    Mapped classes are defined using TMappingSetup.MappedClasses property. This provides you a TMappedClasses class which several methods and properties to define the classes to be mapped.

    uses
      Aurelius.Mapping.Setup, 
      Aurelius.Mapping.Explorer, 
      Aurelius.Mapping.MappedClasses,
      Aurelius.Engine.ObjectManager;
    
    {...}
    
    var
      MapSetup1: TMappingSetup;
      MapSetup2: TMappingSetup;
    begin
      MapSetup1 := TMappingSetup.Create;
      MapSetup2 := TMappingSetup.Create;
      try
        MapSetup1.MappedClasses.RegisterClass(TCustomer);
        MapSetup1.MappedClasses.RegisterClass(TCountry);
        MapSetup2.MappedClasses.RegisterClass(TInvoice);
        FMappingExplorer1 := TMappingExplorer.Create(MapSetup1);
        FMappingExplorer2 := TMappingExplorer.Create(MapSetup2);
      finally
        MapSetup.Free;
      end;
    
      // FManager1 will connect to SQL Server and will only deal
      // with entity classes TCustomer and TCountry
      FManager1 := TObjectManager.Create(MySQLServerConnection, FMappingExplorer1);
      // FManager2 will connect to SQLite and will only deal with entity class TInvoice
      FManager2 := TObjectManager.Create(MySQLiteConnection, FMappingExplorer2);
    
      // Don't forget to destroy FMappingExplorer1 and FMappingExplorer2 at the end of application
    end;
    

    Default behavior

    You don't need to manually register classes in MappedClasses property. If it is empty, Aurelius will automatically register all classes in the application marked with the Entity attribute, for the default model - the classes without a explicit Model attribute.

    If you want the mapping setup to automatically load entities from another model, just set the ModelName property:

      MapSetup.ModelName := 'MyModel';
    

    Methods and properties

    The following methods and properties are available in TMappedClasses class.

    procedure RegisterClass(Clazz: TClass);
    

    Registers a class in the mapping setup.

    procedure RegisterClasses(AClasses: TEnumerable<TClass>);
    

    Register a set of classes in the mapping setup (you can pass a TList<TClass> or any other class descending from TEnumerable<TClass>).

    procedure Clear;
    

    Unregister all mapped classes. This returns to the default state, where all classes marked with Entity attribute will be registered.

    function IsEmpty: boolean;
    

    Indicates if there is any class registered as a mapped class. When IsEmpty returns true, it means that the default classes will be used (all classes marked with Entity attribute).

    property Classes: TEnumerable<TClass> read GetClasses;
    

    Lists all classes currently registered as mapped classes.

    procedure UnregisterClass(Clazz: TClass);
    

    Unregister a specified class. This method is useful when combined with GetEntityClasses. As an example, the following will register all classes marked with Entity attribute (the default classes), except TInternalConfig:

    MapSetup.MappedClasses.RegisterClasses(TMappedClasses.GetEntityClasses);
    MapSetup.MappedClasses.UnregisterClass(TInternalConfig);
    
    class function GetEntityClasses: TEnumerable<TClass>;
    class function GetDefaultClasses: TEnumerable<TClass>;
    class function GetModelClasses(const ModelName: string): TEnumerable<TClass>;
    

    Helper functions that return classes in the application marked with Entity attribute.
    You can call GetModelClasses to retrieve entity classes belonging to the model specified by ModelName.
    You can call GetDefaultClasses to retrieve entity classes belonging to the default model (either classes with no Model attribute or belonging to model "Default").
    Or you can use GetEntityClasses to retrieve all entity classes regardless of the model they belong to. This is not a list of the currently mapped classes (use Classes property for that). This property is just a helper property in case you want to register all classes marked with Entity attribute and then remove some classes. It's useful when used together with UnregisterClass method. Note that if ModelName is empty string when calling GetModelClasses, model will be ignored and all classes marked with Entity attribute, regardless of the model, will be retrieved.

    Calling GetModelClasses('') is equivalent to calling GetEntityClasses.

    Calling GetModelClasses(TMappedClasses.DefaultModelName) is equivalent to calling GetDefaultClasses.

    Dynamic Properties

    Dynamic properties are a way to define mapping to database columns at runtime. Regular mapping is done as following:

    [Column('MEDIA_NAME', [TColumnProp.Required], 100)]
    property MediaName: string read FMediaName write FMediaName;
    

    But what if you don't know at design-time if the MEDIA_NAME column will be available in the database? What if your application runs in many different customers and the database schema in each customer is slightly different and columns are not known at design-time? To solve this problem, you can use dynamic properties, which allows you to manipulate the property this way:

    MyAlbum.CustomProps['MediaName'] := 'My media name';
    

    The following steps describe how to use them.

    Preparing Class for Dynamic Properties

    To make your class ready for dynamic properties, you must add a new property that will be used as a container of all dynamic properties the object will have. This container must be managed (created and destroyed) by the class and is an object of type TDynamicProperties:

    uses
      Aurelius.Mapping.Attributes,
      Aurelius.Types.DynamicProperties,
    
    type
      [Entity]
      [Automapping]
      TPerson = class
      private
        FId: integer;
        FName: string;
        FProps: TDynamicProperties;
      public
        constructor Create;
        destructor Destroy; override;
        property Id: integer read FId write FId;
        property Name: string read FName write FName;
        property Props: TDynamicProperties read FProps;
      end;
    
    constructor TPerson.Create;
    begin
      FProps := TDynamicProperties.Create;
    end;
    
    destructor TPerson.Destroy;
    begin
      FProps.Free;
      inherited;
    end;
    

    The Automapping attribute is being used in the example, but it's not required to use dynamic properties. You just need to declare the TDynamicProperties property, with no attributes associated to it.

    Registering Dynamic Properties

    Dynamic properties must be registered at run-time. To do that, you need to use a custom mapping setup. You need to create a TMappingSetup object, register the dynamic properties using DynamicProps property, and then create a TMappingExplorer object from this setup to be used when creating TObjectManager instances, or just change the TMappingExplorer.DefaultInstance.

    The DynamicProps property is an indexed property which index is the class where the dynamic property will be registered. The property returns a TList<TDynamicProperty> which you can use to manipulate the registered dynamic properties. You don't need to create or destroy such list, it's managed by the TMappingSetup object. You just add TDynamicProperty instances to it, and you also don't need to manage such instances.

    The following code illustrates how to create some dynamic properties in the class TPerson we created in the topic "Preparing Class for Dynamic Properties".

    uses
       {...}, Aurelius.Mapping.Setup, Aurelius.Mapping.Attributes;
    
    procedure TDataModule1.CreateDynamicProps(ASetup: TMappingSetup);
    var
      PersonProps: TList<TDynamicProperty>;
      Prop: TDynamicProperty;
    begin
      PersonProps := ASetup.DynamicProps[TPerson];
    
      // Scalar properties
      PersonProps.Add(
        TDynamicProperty.Create('Props', 'HairStyle', TypeInfo(THairStyle),
          TDynamicColumn.Create('HAIR_STYLE')));
      PersonProps.Add(
        TDynamicProperty.Create('Props', 'Photo', TypeInfo(TBlob),
          TDynamicColumn.Create('PHOTO')));
      PersonProps.Add(
        TDynamicProperty.Create('Props', 'Extra', TypeInfo(string),
          TDynamicColumn.Create('COL_EXTRA', [], 30)));
    
      // Association
      Prop := TDynamicProperty.Create('Props', 'Customer', TypeInfo(TCustomer));
      PersonProps.Add(Prop);
      Prop.AddAssociation(Association.Create([TAssociationProp.Required], CascadeTypeAllButRemove));
      Prop.AddColumn(JoinColumn.Create('CUSTOMER_ID', []));
    
      // Many-valued association
      Prop := TDynamicProperty.Create('Props', 'Items', TypeInfo(TList<TInvoiceItem>));
      PersonProps.Add(Prop);
      Prop.AddAssociation(ManyValuedAssociation.Create([], CascadeTypeAllRemoveOrphan));
      Prop.AddColumn(ForeignJoinColumn.Create('PERSON_ID', [TColumnProp.Required]));
    end;
    
    procedure TDataModule1.DefineMappingSetup;
    var
      MapSetup: TMappingSetup;
    begin
      MapSetup := TMappingSetup.Create;
      try
        CreateDynamicProps(MapSetup);
        TMappingExplorer.ReplaceDefaultInstance(TMappingExplorer.Create(MapSetup));
      finally
        MapSetup.Free;
      end;
    end;
    

    In the previous example, we have registered three scalar properties in class TPerson:

    • HairStyle, which is a property of type THairStyle (enumerated type) and will be saved in database column HAIR_STYLE;

    • Photo, a property of type TBlob, to be saved in column PHOTO;

    • Extra, a property of type string, to be saved in column COL_EXTRA, size 30.

    And also one single-valued association and one many-valued association:

    • Customer which is a property of type TCustomer. The column holding the id of the associated customer is named CUSTOMER_ID and is a foreign-key to the customers table.

    • Items, a many-valued association property of time TList<TInvoiceItem>. The column PERSON_ID should be present in the table holding the invoice items, will refer to the id of the table person.

    Note that the type of dynamic property must be informed. It should be the type of the property (not the type of database column) as if the property was a real property in the class.

    You can create dynamic properties of any type supported by Aurelius. Nullable types are also not supported, but because they are not needed. All dynamic properties are nullable because they are in essence TValue types and you can always set them to TValue.Empty values (representing a null value).

    The first parameter of TDynamicProperty.Create method must have the name of the TPerson property which will hold the dynamic property values (we have created a property Props of type TDynamicProperties in class TPerson).

    Declaration of TDynamicProperty object is as following:

      TDynamicProperty = class
      public
        constructor Create(const AContainerName, APropName: string; APropType: PTypeInfo); overload;
        constructor Create(const AContainerName, APropName: string; APropType: PTypeInfo; ColumnDef: AbstractColumn); overload;
        procedure AddColumn(Column: AbstractColumn);
        procedure AddAssociation(Assoc: Association);
        function Clone: TDynamicProperty;
        function GetAttributes: TArray<TCustomAttribute>;
        property ContainerName: string read FContainerName write FContainerName;
        property PropertyName: string read FPropertyName write FPropertyName;
        property PropertyType: PTypeInfo read FPropertyType write FPropertyType;
      end;
    

    The AbstractColumn and Association types are just the regular mapping attributes present in unit Aurelius.Mapping.Attributes, that you also use to map class members at compile-time. So the mapping and creating of such attributes following the exact behavior, you can use Column, JoinColumn or ForeignJoinColumn attributes when calling AddColumn, as well Association and ManyValuedAsssociation attributes to add associations with AddAssociation.

    Note

    The overloaded Create methods of TDynamicColumn are very similar to the ones used in Column attribute. The TDynamicColumn contains info about the physical table column in the database where the dynamic property will be mapped to, and its properties behave the same as the ones described in the documentation of Column attribute.

    Using Dynamic Properties

    Once you have prepared your class for dynamic properties, and registered the dynamic properties in the mapping setup, you can manipulate the properties as any other property of your object, using the TDynamicProperties container object. It's declared as following:

    TDynamicProperties = class
    public
      property Prop[const PropName: string]: TValue read GetItem write SetItem; default;
      property Props: TEnumerable<TPair<string, TValue>> read GetProps;
    end;
    

    This is how you would use it:

    Person := Manager.Find<TPerson>(PersonId);
    Person.Props['Extra'] := 'Some value';
    Manager.Flush;
    ExtraValue := Person.Props['Extra'];
    

    Note that in the example above, the dynamic property behave exactly as a regular property. The Flush method have detected that the "Extra" property was changed, and will update it in the database accordingly.

    Be aware that Props type is TValue, which is a generic container. Some implicit conversions are possible, as illustrated in the previous example using the dynamic property "Extra". However, in some cases (and to be safe you can use this approach whenever you are not sure about using it or not) you will need to force the TValue to hold the correct type of the property. The following example shows how to define a value for the dynamic property HairStyle, which was registered as the type THairStyle (enumerated type):

    Person := TPerson.Create;
    Person.Props['HairStyle'] := TValue.From<THairStyle>(THairStyle.Long);
    Manager.Save(Person);
    PersonHairStyle := Person.Props['HairStyle'].AsType<THairStyle>;
    

    The same applies to blob properties, which must be of type TBlob:

    var
      Blob: TBlob;
    begin
      // Saving a blob
      Blob.LoadFromStream(SomeStream);
      Person.Props['Photo'] := TValue.From<TBlob>(Blob);
      Manager.SaveOrUpdate(Person);
    
      // Reading a blob
      Blob := Person.Props['Photo'].AsType<TBlob>;
      Blob.SaveToStream(MyStream);
    

    Dynamic blob properties can also be lazy-loaded just as any regular blob property.

    For associations and many-valued associations, you should also cast to/from the object type when needed. Note that TDynamicProperties automatically destroys objects of type TList<T> in dynamic variables:

      // writing
      Customer := TCustomer.Create;
      Person.Props['Customer'] := Customer;
      Customer.Name := 'Dynamic';
      Manager.Save(Person);
    
      // reading
      Person := Manager.Find<TPerson>(Id1);
      Customer := Person.Props['Customer'].AsType<TTC_Customer>;
    

    Many-valued associations work in a similar way:

      // writing
      Items := TList<TDP_Item>.Create;
      Person.Props['Items'] := Items;
      Items.Add(TInvoiceItem.Create('Item1'));
      Items.Add(TInvoiceItem.Create('Item2'));
      Manager.Save(Person);
    
      Person := Manager.Find<TPerson>(Id1);
      Items := Person.Props['Items'].AsType<TList<TInvoiceItem>>;
      if Items <> nil then
        for Item in Items do 
          // iterate through items
    

    Dynamic Properties in Queries and Datasets

    When it comes to queries and datasets, dynamic properties behave exactly as regular properties. In queries, they are accessed by name as any other query. So for example the following query:

    Results := Manager.Find<TPerson>
      .Where(
        (Linq['HairStyle'] = THairStyle.Long) and
        Linq['Extra'].Like('%value%')
      )
      .AddOrder(TOrder.Asc('Extra'))
      .List;
    

    will list all people with HairStyle equals to "Long" and Extra containing "value", ordered by Extra. No special treatment is required, and the query doesn't care if HairStyle or Extra are dynamic or regular properties.

    The same applies to the TAureliusDataset. The dynamic properties are initialized in fielddefs as any other property, and can be accessed through dataset fields:

    // DS: TAureliusDataset;
    
    DS.Manager := Manager;
    Person := TPerson.Create;
    DS.SetSourceObject(Person);
    DS.Open;
    DS.Edit;
    DS.FieldByName('Name').AsString := 'Jack';
    DS.FieldByName('Extra').AsString := 'extra value';
    
    // Enumerated types are treated by its ordinal value in dataset
    DS.FieldByName('HairStyle').AsInteger := Ord(THairStyle.Short);
    
    // use BlobField as usual
    BlobField := DS.FieldByName('Photo') as TBlobField;
    
    In This Article
    • Multi-Model Step-By-Step
    • Using Model attribute
    • TMappingExplorer
      • Retrieving a TMappingExplorer instance
      • Creating a TMappingExplorer explicitly
    • Mapping Setup
      • Defining a Mapping Setup
      • Default Mapping Setup Behavior
      • Mapped Classes
        • Defining mapped classes
        • Default behavior
        • Methods and properties
      • Dynamic Properties
        • Preparing Class for Dynamic Properties
        • Registering Dynamic Properties
        • Using Dynamic Properties
        • Dynamic Properties in Queries and Datasets
    Back to top TMS Aurelius v5.21
    © 2002 - 2025 tmssoftware.com