Sample Object Definition and Code 

{ Four classes are defined (and generally always generated)

  1. Record (TdmoProject)

  2. Record Component for usage within Delhi IDE (TdmoProjectcmp)

  3. DataSet is list of Records (TdmoProjectDataSet)

  4. DataSet Component for usage within Delphi IDE (TdmoProjectDataSetcmp) }

 

TdmoProject = class(TosDbRecord// Base class is the TosDbRecord
private
  FTasks : TdmoTaskDataSet;       // FTasks is a child class of many tasks
protected
  procedure PostUpdate; override;
  procedure PostInsert; override;
  function GetTasks : TdmoTaskDataSet; // Get Property to load Tasks

  // All record classes need the TableClass function to specify 

  // the Database Tables Definition Class...
  function TableClass : TtscTableDefListClass; override;   

  // GetFieldName is always required to convert the Index provided with

  // the property setting below to a fieldname string value for lookup in the fieldlist.
  function GetFieldName(ix : integer) : string; override;

  // Get and Set methods...

  function GetProjectStatusCd : String;

  procedure SetProjectStatusCd(aValue : String); 

public

  // Constructor and Destructor methods are automatically generated for easy adjustment later 
  constructor Create(aOwner : Objects); override
  destructor Destroy ; override;

 

  // FreeChildren is always required IF child classes are defined - it Nils the

  // childclass pointers since Delphi does not when we free them in garbage collection.
  procedure FreeChildren; override;

  procedure Save; override// Custom Save is required because we are saving tasks with project

 

  // Tasks Child Class methods and property that are generated with child datasets...

  function RemoveTask(aTask : TdmoTask) : Boolean; virtual;
  procedure RefreshTasks; virtual;
  function AddTask(aTaskName : String) : TdmoTask; virtual;

  property Tasks : TdmoTaskDataSet read GetTasks;

 

  // Generation creates a property for each database field and uses an Index to

  // reference the field properly. See dd_dmoProject for the named constants.

  // Note that ProjectId is read only and ProjectStatusCd has custom Get/Set methods...

  property ProjectId : Integer Index ncprojProjectIdIx read GetIntegerField;
  property ProjectNumber : String Index ncprojProjectNumberIx read GetStringField write SetStringField;
  property ProjectName : String Index ncprojProjectNameIx read GetStringField write SetStringField;
  property ManagedByUserId : Integer Index ncprojManagedByUserIdIx read GetIntegerField write SetIntegerField;
  property ParentProjectId : Integer Index ncprojParentProjectIdIx read GetIntegerField write SetIntegerField;
  property ProjectTypeCd : String Index ncprojProjectTypeCdIx read GetStringField write SetStringField;
  property ProjectStatusCd : String read GetProjectStatusCd write SetProjectStatusCd;
  property TimeUnitCd : String Index ncprojTimeUnitCdIx read GetStringField write SetStringField;
  property EstimatedEffortUnits : Integer Index ncprojEstimatedEffortUnitsIx read GetIntegerField write SetIntegerField;
  property ActualEffortUnits : Integer Index ncprojActualEffortUnitsIx read GetIntegerField write SetIntegerField;
  property EstimatedStartDate : TDatetime Index ncprojEstimatedStartDateIx read GetDatetimeField write SetDatetimeField;
  property ActualStartDate : TDatetime Index ncprojActualStartDateIx read GetDatetimeField write SetDatetimeField; 
  property EstimatedEndDate : TDatetime Index ncprojEstimatedEndDateIx read GetDatetimeField write SetDatetimeField;
  property ActualEndDate : TDatetime Index ncprojActualEndDateIx read GetDatetimeField write SetDatetimeField;
  property EstimatedCost : Double Index ncprojEstimatedCostIx read GetFloatField write SetFloatField;
  property ActualCost : Double Index ncprojActualCostIx read GetFloatField write SetFloatField;
end;

// The wrapper record component is generally very small and is only

// necessary because TosDbRecordCmp descends from TComponent whereas

// TosDbRecord descends from TObject. TComponent allows you to add the Record

// Component to the Delphi IDE palette.

TdmoProjectcmp = class(TosDbRecordCmp)
private
  function RecordClass : TtscDbRecordClass; override // Will return TdmoProject
  function GetTasks : TdmoTaskDataSet;   // Added for easy reference within application code
public
  constructor Create(aOwner : TComponent); override;
  destructor Destroy ; override;


  property Tasks : TdmoTaskDataSet read GetTasks;
  function Data : TdmoProject; virtual// Always generated to access the real object of interest
end;

 

// The project dataset is a list of many project records...
TdmoProjectDataSet = class(TosDataSet)
private
protected
  function RecordClass : TtscDbRecordClass; override;     // Will return TdmoProject

 

  // contains all of the db tables and corresponding field definitions.
  function TableClass : TtscTableDefListClass; override;  
public
  constructor Create(aOwner : TObject); override;
  destructor Destroy ; override;

 
  function Current : TdmoProject;   // Returns the current Project within the list
  function New : TdmoProject;       // Adds a new empty project to the list
end;

 

// The wrapper dataset component for TdmoProjectDataSet
TdmoProjectDataSetcmp = class(TosDataSetCmp)
private
  function ListClass : TtscDataSetClass; override// Returns TdmoProjectDataSet
public
  constructor Create(aOwner : TComponent); override;
  destructor Destroy; override;

 

{-------------------------------------------------}

Code Section

{ TdmoProject Object }
{-------------------------------------------------}

constructor TdmoProject.Create(aOwner : TObject);
begin
  inherited Create(aOwner);
end;

procedure TdmoProject.FreeChildren;
begin

  // The FreeChildren method will free all child classes of TdmoProject

  // but we have to actually set the private variable back to Nil!
  inherited FreeChildren;
  FTasks := Nil;  
end;

destructor TdmoProject.Destroy;
begin
  inherited Destroy;
end;

procedure TdmoProject.Save;
var iTxnId : Integer;
begin

  // We need to manage the db transaction ourselves, start the transaction

  // and get the iTxnId representing this Transaction (only the dbCommit with 

  // the correct transaction id will actually perform the db COMMIT).
  Database.dbStart(iTxnId);
  try
    inherited Save;  // Perform the save on the project

    // We reference FTasks to save them (not Tasks to make sure we do not invoke GetTasks)
    if FTasks <> Nil then // Ensure the linkage value is seeded into the child class...
    begin

      // All Tasks need to be linked back to this Project
      FTasks.SetFieldValue(nctaskProjectId, IntToStr(ProjectId));  
      FTasks.Save;  // Saves all Modified tasks
    end;

    Database.dbCommit(iTxnId);  // If all is well so far, we perform a DB COMMIT
  except on E:Exception do
    begin
      Database.dbRollback;  // Anything goes wrong we perform a rollback
      raise EShowError.Create('Error saving TdmoProject...'#10#13 + E.Message);
    end;
  end;

  // At the end of the Save method, the RecordStates of all project and task

  // records will have been set back to rsFetched from either rsInsert, rsUpdate

  // or rsDelete.

  // If a task was physically deleted, the FTasks dataset will be less one entry.

  // The Database.dbCommit calls the inherited Reset method of all saved records

  // to have them reset their RecordStates OR destroy themselves if deleted.
end;


function TdmoProject.RemoveTask(aTask : TdmoTask) : Boolean;
begin
  Result := False;
  if (Tasks.FindRecord(aTask)) then  // Look for this task in the list
  begin
    Tasks.MarkForDelete(Tasks.Current);  // Mark it for deletion on the next Save Op!
    Result := True;
  end;
end;

procedure TdmoProject.RefreshTasks;
begin
  FTasks.Free;
  FTasks := Nil;
end;

function TdmoProject.AddTask(aTaskName : string) : TdmoTask;
begin

  // This method is stating that task names must be unique

  // We return the task if found or if marked for deletion,

  // otherwise, we create a new task record and specify some

  // initial values! 
  if (Tasks.FindByString(nctaskTaskName, aTaskName)) then
     Result := Tasks.Current
  else if (Tasks.PendingDelete(nctaskTaskName, aTaskName)) then
  begin
    Result := Tasks.Current;
    Result.Cancel;
  end
  else
  begin

    Result := Tasks.New;
    Result.ProjectId := Self.ProjectId;

    Result.TaskName := aTaskName;

  end;

end;

 

function TdmoProject.GetTasks : TdmoTaskDataSet;

begin

  // Create the FTasks dataset if not already done so...

  if FTasks = Nil then

     FTasks := TdmoTaskDataSet.Create(Self);

 

  // Only perform the database query to load the tasks if we have a project id

  // and if the tasks are not already cached (i.e. loaded)
  if (ProjectId > 0) and
     (not FTasks.Cached) then
  begin
    FTasks.Filters.Clear;
    FTasks.AddIntFilter(loAnd, nctaskProjectId, opEq, ProjectId, True);  // WHERE task.project_id=NNN
    FTasks.Load;  // performs the SQL Select and creates one entry per task record
  end;
  Result := FTasks;  // That SIMPLE!!!
end;


function TdmoProject.TableClass : TtscTableDefListClass;
begin

  // TableClass is invoked within DataSet and Record Constructors

  // It is what specifies the database tables for this object!
  Result := TdmoProjectTableDefs// IMPORTANT : References the appropriate Project tables and field definitions 
end;

function TdmoProject.GetFieldName(ix : integer) : string;
begin

  // Maybe TdmoProject was derived from some other Project class
  Result := inherited GetFieldName(ix);

  // It wasn't, so the following case will convert the property Index to a fieldname constant

 

function TdmoProject.GetProjectStatusCd : String;

begin

  Result := FieldByName(ncprojProjectStatusCd).AsString;

end;

 

procedure TdmoProject.SetProjectStatusCd(aValue : String); 

begin

  if FieldByName(ncprojProjectStatusCd).AsString <> aValue then

  begin

    // Lookup the field for the ProjectStatusCd and set its value!

    // This will set the Modified flag for the record and change 

    // the RecordState from rsFetched to rsUpdate!

    FieldByName(ncprojProjectStatusCd).AsString := aValue;

    // The reason we made this method was to add some intelligence...

    if (aValue = 'CLOS') then  // Project is being CLOSED ?

       ActualEndDate := Now;

  end;

end;

{ TdmoProjectcmp Component }
{----------------------------------------------------------}

constructor TdmoProjectcmp.Create(aOwner : TComponent);
begin
  inherited Create(aOwner);
end;

destructor TdmoProjectcmp.Destroy;
begin
  inherited Destroy;
end;

// GetTasks redirects down to the Data or Project Record
function TdmoProjectcmp.GetTasks : TdmoTaskDataSet;
begin
  Result := Data.GetTasks;
end;

function TdmoProjectcmp.RecordClass : TtscDbRecordClass;
begin
  Result := TdmoProject;  // What does this component wrapper create for records
end;

function TdmoProjectcmp.Data : TdmoProject;
begin
  // Caste the DataRec to the TdmoProject so application code does not need to.

 

 

// Wrapper for the ProjectDataSet object is just a convenience to have components

// within the Delphi IDE that reference our objects!
{ TdmoProjectDataSetcmp Component }
{--------------------------------------------------}

constructor TdmoProjectDataSetcmp.Create(aOwner : TComponent);
begin
  inherited Create(aOwner);
end;

destructor TdmoProjectDataSetcmp.Destroy;
begin
  inherited Destroy;
end;

function TdmoProjectDataSetcmp.ListClass : TtscDataSetClass;
begin
  Result := TdmoProjectDataSet;
end;

function TdmoProjectDataSetcmp.Current : TdmoProject;
begin
  Result := TdmoProject(GetCurrentRecord);
end;

function TdmoProjectDataSetcmp.New : TdmoProject;
begin
  Result := TdmoProject(GetNewRecord);
end;