menu
  Home  ==>  papers  ==>  db  ==>  eco_tutorial   

ECO Tutorial - Felix John COLIBRI.


1 - ECO modeling and coding

ECO is a Delphi modeling tool: the developer draws the model of his application, and the ECO framework builds an in-memory structure of this model using objects. This representation is used to implement the model in order to add a visual interface to our application (Forms, Buttons etc) and transfer the data to disc (.XML files or Sql Database).

The overall objective is to let the developer work at a high model level, letting the ECO machinery take care of many of the low-level coding tasks (writing Sql requests, synchronizing the visual GUI with the Tables, handling master / detail navigation etc).

This tutorial will present how to write a simple, application with ECO.


2 - An Invoicing example

2.1 - The Goal

We will build a tiny invoicing application with INVOICEs and ITEMs. Each ITEM corresponds to a PART, which can be either manufactured by our company, or purchased outside.

This corresponds to the following diagram:

image



In this paper, we are only going to use the invoice / item CLASSEs:

image



2.2 - The ECO architecture

Building and ECO application involves 2 basic steps
  • create the UML model for this application
  • generate the Delphi code corresponding to this model, and write some code


This can be represented by the following figure:

image

and

  • we draw the UML Class diagram using any kind of UML editor (Rational Rose, Model Maker, Together)
  • this "picture" is converted into a code structure. This is the EcoSpace which contains:
    • the classes from the UML Class diagram (in the UMLRt, which is the Run Time representation of the diagram)
    • the objects which are instances of those UML Classes which will contain the values of our Database
  • we can access those EcoSpace objects (for creation, reading, writing) using EcoHandles. Those handles are used for displaying the values in GUI controls


2.3 - Create the UML model

To create the ECO model, we will start an ECO project. This can be done using the Delphi Wizard. So let's start a new Windows Forms ECO application:
   load Delphi 2006 (or Delphi 2006, Windows Forms personality, or Turbo Delphi for .Net)
   select "File | New | 0ther"
   Delphi displays the "other" files:

image

Note that the menu used to reach this dialog, the specific menu treeview item (the root "Delphi for .Net Project" in our case) and the content of this dialog may be different, depending on the Delphi version). We simply must locate the "ECO WinForms Application" wizard somewhere.

   select "Eco WinForms Application"

   Delphi presents the path and project dialog:

image

   type your path and project name, and click "Ok"

   Delphi create the new "ECO Windows Forms Application"
   rename the main unit WINFORMS.PAS by selecting the Project Manager tab from the top-right notebook, then select "WINFORM.PAS | right click | Rename" and type U_01_INVOICING_FORM.PAS

   the Project Manager displays the following files:

image

where:

  • PACKAGE_1UNIT.PAS will contain the CLASSes corresponding to our model (a c_invoice CLASS, a c_item CLASS etc). At this stage, there is only an empty Package_1 CLASS (an "UML package" meaning a set of UML things, not the "Delphi .DLL / package")

  • P_01_INVOICINGECOSPACE.PAS contains the Tp_01_invoicingEcoSpace CLASS which represents the ECO engine and all its properties and methods.

    This CLASS will represent the model as objects in memory, and allow communication with the visual GUI components, as well as the disc data. It will have access to our custom application-dependent CLASSes defined in the PACKAGE_1UNIT.PAS UNIT

  • U_01_INVOICING_FORM.PAS is the usual Windows Form. The tWinForm CLASS contains an EcoSpace PROPERTY of type Tp_01_invoiceEcoSpace which is a reference to the eco space

    By selecting the "Design" tab at the bottom of the central notebook, the tWinForm's design surface with the component tray is displayed:

    image

    You can also remove all the components (EcoGlobalActions, EcoAutoForms, EcoListActions and EcoDragDrop) other than the rhRoot, since they are not required and we will not use them in this tutorial. But do keep the rhRoot component.



2.4 - Building the Model

Let's create the basic UML model with the 3 CLASSes.

The diagrams are created on the modeling surface, which must be displayed:
   select the "model view" tab in the top-right notebook, and in the treeview, click on the grey "PACKAGE_1" item (called "CORE" in some old Delphi versions)
   an empty modeling surface is displayed:

image



To create the diagram for our INVOICE CLASS
   unfold the Tools Palette (by clicking on the "+"), and select the "ECO Class" element:

image

and drop it on the modeling surface

   the Class_1 is displayed

image

   type the name of the CLASS, for instance c_invoice
   the CLASS name is updated. Also notice that the diagram attributes are displayed in the Object Inspector:

image

Those properties are graphic properties (background color, border size etc)

   to add the customer field, select the c_invoice CLASS, right click on the "Attributes" element, to display the contextual menu, and select the "Add | Attribute" menu item:

image

   a new attribute is created with a default "Attribute_1: Integer" definition

   type the name and type of your attribute. In our case "m_customer: String"

image

   add the other attribute "m_invoice_number: Integer"

   this is our c_invoice CLASS diagram so far:

image



A this stage, compile the project, in order to synchronize the model (the UML drawings) with the EcoSpace (the object representation of those drawings). So:
   compile the project
This compilation is required, since the Delphi IDE will use the in-memory representation of the UML drawing. So compiling is often required, as well as clicking on the "Regenerate Eco Source Code" - like icons a the top of the model view notebook.



You may look at the PACKAGE_1UNIT.PAS, which now contains 2 more definitions

  • the c_invoice CLASS
  • a Ic_invoiceList INTERFACE which is used to represent the list of invoices (the in-memory representation of our future disc data content)


Note that
  • compiling from time to time seems to be a good idea. The p_01_invoicingEcoSpace design surface even reminds you to compile each time you load the project in the Delphi IDE.


2.5 - Adding Invoices

To add invoices, we have to call a c_invoice CONSTRUCTOR and initialize the attributes:
  • one of the c_invoice CONSTRUCTORs uses the tWinForm.EcoSpace as its parameter. This allows the object to be added to the invoice list contained in the EcoSpace
  • the c_invoice CLASS contains the m_customer and m_invoice_number members which can be used to initialize the object
Therefore
   select the main Windows Forms designer by selecting the "Project Manager" tab from the top-right notebook, and the U_01_INVOICING_FORM.PAS item (caution: there is also a U_01_INVOICING_FORM element nested inside the "ModelSupport_p_01_invoice" summary element, but it is not a .PAS element. Our treeview item is, usually, at the bottom of the treeview).
   the traditional Delphi grey dotted designer surface is displayed
   from the Tools Palette, select a Button an drop it on the Form, rename it "create_invoices_". Create its Click event, and type the code which will add some c_invoice objects:

procedure TWinForm.create_invoices__Click(senderSystem.Object;
    eSystem.EventArgs);

  procedure add_invoice(p_customerStringp_invoice_numberInteger);
    begin
      with c_invoice.Create(EcoSpacedo
      begin
        m_customer:= p_customer;
        m_invoice_number:= p_invoice_number;
      end// with c_customer
    end// add_invoice

  begin // create_invoices_
    add_invoice('Miller', 12345);
    add_invoice('Smith', 5234);
    add_invoice('Adams', 5234);
  end// create_invoices_

   compile the project


2.6 - Saving the Result in an .XML file

To save the objects to disc, we can place them in .XML files or into some Sql Database. To avoid mixing ADO .Net components in our tutorial, we chose to use the more simple .XML files.

Persisting the data in an .XML file only requires a PersistencMapperXML component which takes all data objects contained in the EcoSpace and saves it to (or reads it from) an .XML file.

That's quite easy:
   from the top-right notebook, select the "project manager" tab, and select P_01_INVOICINGECOSPACE.PAS

image

and in the central notebook, select the "Design" tab

   an empty EcoSpace designer is displayed

image

   from the Tool Palette, select a PersistencMapperXML:

image

and drop it on the EcoSpace designer

   in the Object Inspector, set the FileName property, for instance

     save_invoicing.xml

   select the main Form, and add a Button, rename it "save_xml_", create its Click event, and add the updating code:

     EcoSpace.UpdateDatabase;

   compile and run
Click "create_invoices_" and then "save_xml_"
   the result is a 4K .XML file (in the .EXE folder). The format is SOAP envelope, and it contains
  • informations about the CLASSes
  • information about the attributes (2 auto generated keys and our two attributes)
  • information about the invoices list
  • the values of our objects
Here is an partial dump of this file:

 
<SOAP-ENV:Envelope xmlns="http://www.w3.org/" ...ooo...>
  <SOAP-ENV:Body>
 
    <a1:Datablock id="ref-1" xmlns=" ...ooo... Eco.Interfaces">
      ...ooo...
    <a2:Hashtable id="ref-3" xmlns=" ...ooo... System.Collections">
      ...ooo...
 
    <SOAP-ENC:Array id="ref-4" SOAP="xsd:anyType[3]">
      <item href="#ref-6"/>
      <item href="#ref-7"/>
      <item href="#ref-8"/>
    </SOAP-ENC:Array>
    <SOAP-ENC:Array id="ref-5" SOAP="xsd:anyType[3]">
      <item href="#ref-9"/>
      <item href="#ref-10"/>
      <item href="#ref-11"/>
    </SOAP-ENC:Array>
 
    <a1:DefaultId id="ref-6" xmlns=" ...ooo... Eco.Interfaces">
      <ObjectId_x002B_key xsi="xsd:int">
       308242888
      </ObjectId_x002B_key>
      <ObjectId_x002B_classId>
       0
      </ObjectId_x002B_classId>
    </a1:DefaultId>
    ...ooo...
 
    <a1:ObjectContents id="ref-9" xmlns=" ...ooo... Eco.Interfaces">
      <objectId href="#ref-6"/>
      <existenceState>
       New
      </existenceState>
      <members href="#ref-12"/>
      <isReadOnly>
       false
      </isReadOnly>
      <timestamp>
       -1
      </timestamp>
    </a1:ObjectContents>
    ...ooo...
 
    <a2:ArrayList id="ref-12" xmlns="...ooo... System.Collections">
      <_items href="#ref-15"/>
      <_size>
       2
      </_size>
      <_version>
       4
      </_version>
    </a2:ArrayList>
    ...ooo...
 
    <SOAP-ENC:Array id="ref-15" SOAP="xsd:anyType[16]">
      <item id="ref-18" xsi="SOAP-ENC:string">
       Miller
      </item>
      <item xsi="xsd:int">
       12345
      </item>
    </SOAP-ENC:Array>
    <SOAP-ENC:Array id="ref-16" SOAP="xsd:anyType[16]">
      <item id="ref-19" xsi="SOAP-ENC:string">
       Smith
      </item>
      <item xsi="xsd:int">
       5234
      </item>
    </SOAP-ENC:Array>
    <SOAP-ENC:Array id="ref-17" SOAP="xsd:anyType[16]">
      <item id="ref-20" xsi="SOAP-ENC:string">
       Adams
      </item>
      <item xsi="xsd:int">
       5234
      </item>
    </SOAP-ENC:Array>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>



Once the .XML file has been created, the data will automatically be loaded in the EcoSpace at each run. This is similar to the tClientDataSet briefcase saving. Of course, if you remove the file name from the PersistentMapper, this will not happen.



From an architectural point of view, we have the following (partial) diagram:

image

and:

  • our tWinForm contains an EcoSpace attribute
  • the EcoSpace CLASS contains a PersistenceMapper attribute
  • there is a whole hierarchy of persistence mappers. We used the .XML mapper, but we could have chosen a PersistencMapperBDP, or PersistenceMapperSqlServer


At this stage, I would suggest that you start looking into the Help. Clicking F1 and navigating to the ECO Framework, you will find all the Assemblies involved (partial view):

image

The main Assemblies are:

  • Borland.ECO.UmlRt where all the CLASSES and INTERFACES for the in-memory object representation of the UML graphic are located
  • Borland.ECO.ObjectRepresentation which contains all the objects containing our data (the values of our invoices)
  • Borland.ECO.Services, for all the ECO services. We will not present them in detail, but they are at the heart of the ECO machinery
  • Borland.ECO.Handles, which contains our EcoSpace, as well as the ReferenceHandle and ExpressionHandle that we will use shortly to display the objects in a DataGrid
  • Borland.ECO.Persistence for all disc handling


2.7 - Displaying the invoices

We will now display the values of our invoices in a TextBox.

To list the in-memory invoices, we need to use an ExpressionHandle. The handles are a way to read or write the EcoSpace elements. Handles can return values, objects or object lists. What will be returned depends on the ExpressionHandle.Expression, and this Expression is written in some special language called OCL (Object Constraint Language). In our case, we want all the invoice objects, and the OCL expression is

 
    c_object.AllInstances
 

ExpressionHandles can be chained: we can reach a list of objects using a first expression, and add a chain another ExpressionHandle to do some filtering, for instance.

The ExpressionHandle chains must have a starting point, which is specified in the ExpressionHandle.RootHandle property.

In our case we will use the rhRoot Handle. And we must initialize the rhRoot.EcoSpaceType property with the Tp_01_invoicingEcoSpace.



So :
   select the main form designer
   select the rhRoot object that was created by the Wizard
In the Object Inspector, select the EcoSpaceType property and set its property to

    p_01_invoicingEcoSpace.Tp_01_invoicingEcoSpace

   in the Tools Palette, select an ExpressionHandle:

image

Drop it ON THE FORM

In the Object Inspector

  • change its (Name) property to "invoice_list_expression_handle"
  • select the RootHandle and select rhRoot
  • select the Expression property and type

        c_object.AllInstances

   from the Tools Palette, select a TextBox, drop in on the Form, and set its MultiLine property to True

   from the Tools Palette, select a Button, drop it on the Form, rename it "list_invoices", create its Click event and type the code which displays the values of the invoices in the TextBox1

procedure TWinForm.list_invoices__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_invoice_listBorland.Eco.ObjectRepresentation.iObjectList;
      l_invoice_indexInteger;
      l_c_invoicec_invoice;
  begin
    l_c_invoice_list:= invoice_list_expression_handle.Element
        as Borland.Eco.ObjectRepresentation.iObjectList;

    for l_invoice_index:= 0 to l_c_invoice_list.Count- 1 do
    begin
      l_c_invoice:= l_c_invoice_list[l_invoice_index].AsObject as c_invoice;
      with l_c_invoice do
        display(m_customer' 'm_invoice_number.ToString);
    end// for l_invoice_index
  end// list_invoices__Click

where display is our standard procedure for adding text to the TextBox1.Text String.

   compile, execute and click "list_invoices"
   here is the display of our invoices

image



Note that

  • OCL might be case sensitive (it seems Bold was, maybe ECO I, ECO II and ECO III are not)


To put those handles into perspective, here is a (partial) UML Class diagram:

image

and:

  • we start the access chain with a ReferenceHandle. This handle is used to reach into the EcoSpace. Usually we use the rhRoot handle which was generated by the Wizard. We simply must check that this rhRoot has been initialized with some EcoSpace, and that has been done after the first project compilation by setting the rhRoot.EcoSpaceType to p_01_invoicingEcoSpace.Tp_01_invoicingEcoSpacepoint

  • we use an ExpressionHandle linked to this ReferenceHandle

  • now the ExpressionHandle has two basic functionalities :
    • it allows us to write an OCL Expression, which will yield in the ExpressionHandle.Element some iElement descendent. In our case an iObjectList.

      We cannot directly display an iElement. It lives in the outer EcoSpace there, some kind of Walhalla filled with thunder and lightning. So we first have to cast it using iElement.AsObject. More seriously, it seems that the EcoSpace has a fragmented representation of our c_invoice CLASS, and AsObject brings all the parts back together, yielding a nice tObject with all the attributes and methods. And since we know that this object is a c_invoice, we cast it into a c_invoice and can then use the c_invoice.m_customer members.

    • the ExpressionHandles are mainly used as visual control DataSource, since ExpressionHandles implement the iBindingList INTERFACE. This will be presented below


2.8 - Implementing ToString

If we want a general display of our classes, we can implement the VIRTUAL ToString FUNCTION in our CLASS.

To do so
   select the model view, select the c_invoice diagram, and select the "Operations" compartment. Right click to find "Add Operation" and type the function name

   here is the diagram:

image

   on the ToString operation, right click and select "Go to Definition" (or somehow direclty display the PACKAG1_1UNIT.PAS text in the central editor)
   this brings you into the PACKAG1_1UNIT.PAS UNIT, at the position of this definition:

image

Notice that despite the Result Type, this was generated as a PROCEDURE and not as a FUNCTION

   change the PROCEDURE into FUNCTION, add OVERRIDE

function ToStringStringOverride;

and go to the IMPLEMENTATION to write the body:

function c_invoice.ToStringString;
  begin
    Result:= m_customerm_invoice_number.ToString;
  end// ToString

   drop a Button, rename it "to_string_", create its Click event, and display the invoices:

procedure TWinForm.to_string__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_invoice_listBorland.Eco.ObjectRepresentation.iObjectList;
      l_invoice_indexInteger;
  begin
    l_c_invoice_list:= invoice_list_expression_handle.Element
        as Borland.Eco.ObjectRepresentation.iObjectList;

    with l_c_invoice_list do
      for l_invoice_index:= 0 to Count- 1 do
        display(Item[l_invoice_index].AsObject.ToString);
  end// to_string__Click

   compile and run


2.9 - Deleting an invoice

To remove an invoice from the invoice list, we mark it as "deleted". The next saving to the persistent media will physically remove this invoice when UpdateDataBase is called (by removing it in the new .XML file version, or by generating an DELETE Sql request).

Here is an example which deletes the second invoice (index 1):
   add a Button to the Form, rename it "delete_1_" and create its Click event, and type the following code:

procedure TWinForm.delete_1__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_invoice_listBorland.Eco.ObjectRepresentation.iObjectList;
      l_c_invoicec_invoice;
  begin
    l_c_invoice_list:= invoice_list_expression_handle.Element
        as Borland.Eco.ObjectRepresentation.iObjectList;

    if l_c_invoice_list.Count< 1
      then display('no_[1]_invoice')
      else begin
          l_c_invoice:= l_c_invoice_list[1].AsObject as c_invoice;
          l_c_invoice.AsIObject.Delete;
        end;
  end// delete_1__Click

   compile, execute and click "list_invoices_", "delete_1_", "list_invoices_"
   here is the display of our invoices

image



Note that

  • you should NOT remove the object with Free or Destroy, since the in-memory list will no longer contain any information about this object, and it will not be deleted from the disc files (.XML or Sql Database)
  • the Delete method is part of the iObject (see the UML diagram above). To call this method, we first must cast the c_invoice object into an iObject object, and this casting is available in c_invoice (as represented in the UML diagram above)


2.10 - Display in a DataGrid

Instead of manually dumping the invoices values in a TextBox, we can display them in visual controls, using the Windows Forms binding mechanism (see the Ado .Net tutorial for more details about binding).

Here is how to display the invoices in a DataGrid:
   from the Tools Palette, select the "Data Controls" tab, and then the DataGrid Control:

image

and drop it on the Form

In the Object Inspector, set the following properties

  • (Name) to invoice_datagrid
  • DataSource property to invoice_list_expression_handle
   the attributes of the c_invoice CLASS are displayed in the DataGrid

image

   compile and run
   the invoice_datagrid is displayed with the invoices values

image



Note that

  • in usual ECO applications, you will go directly from the UML diagram to the DataGrid, without the creation and listing steps. All that is required is
    • some kind of PersistentMapper to get the data
    • an initialized rhRoot
    • an ExpressionHandle with the relevant OCL Expression
    • and, of course, a DataGrid


You can change the values in the DataGrid, but cannot insert new invoices. To do so, you must first create the c_invoice object, and you can then set the values in the DataGrid. Here is an example:
   add a Button to your Form, rename it "create_invoice_", and in its Click event create a new c_invoice object:

procedure TWinForm.create_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    c_invoice.Create(EcoSpace);
  end// create_invoice__Click

   compile and run. Click on "create_invoice_"
   the DataGrid displays a NULL initialized new invoice:

image



The DataGrid can be used to navigate the invoice list. If we want to perform some action on the current invoice, we must find a way to find which invoice is the selected invoice in the DataGrid.

This can be achieved using a Windows Forms CurrencyManager-like component (see the Ado .Net tutorial for more details about the CurrencyManager).

Therefore:
   select the main designer
   from the Tools Palette, select the ECO tab and then the CurrencyManagerHandle:

image

and drop it on the Form and from the Object Inspector, set the following properties:

  • (Name) to invoice_currency_manager_handle
  • RootHandle to invoice_list_expression_handle (not to rhRoot)
  • BindingCOntext to invoice_datagrid
   drop a Button on the Form, rename it "delete_current_" and in its Click event, type the code which deletes the invoice selected in the invoice_datagrid:

procedure TWinForm.delete_current__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_selected_invoicec_invoice;
  begin
    if invoice_currency_manager_handle.Element.AsObject is c_invoice
      then begin
         l_c_selected_invoice:=
           c_invoice(invoice_currency_manager_handle.Element.AsObject);
         l_c_selected_invoice.AsIObject.Delete;
       end;
  end// delete_current__Click

   compile, run, in the invoice_datagrid navigate to the second row and click "delete_current_"
   the second row object is deleted (and the DataGrid updated):

image

If you save the result (EcoSpace.UpdateDataBase) the disc data will no longer contain this second row




3 - Handling Related Classes

3.1 - Master Detail relation

We want to add invoice items: each invoice contains several items with price and quantity.

This is done using the following steps

  • we add a new c_item class to the UML class diagram
  • we add an association between the c_invoice and the c_item classes. We must specify on both ends of this association
    • the multiplicity (1 on the c_invoice side, 1..* on the c_item side, meaning that any item MUST belong to a single invoice, and any invoice has at least 1 item, possibly many more)
    • the name of the association end.
      • on the c_item end, we chose has_item, which can be read from left to right "a invoice has_item the items'
      • on the c_invoice end we chose from_invoice, which means "an item comes from_invoice an invoice"
    The class diagram looks like this:

    image

  • now the important part: we want to add an invoice with 2 items
    • we create the my_c_invoice object, as explained above

      my_c_invoice:= c_invoice.Create(EcoSpace);
      my_c_invoice.m_customer:= 'Miller';
      my_c_invoice.m_invoice_number:= 1234;

    • for each item
      • we create the item

        my_c_item:= c_item.Create(EcoSpace);
        my_c_item.m_price:= 765.33;
        my_c_item.m_quantity:= 18;

      • we add this item to the invoice's item list:

            my_c_invoice.has_item.Add(m_c_item);

        This is the crux: we use the has_item relation-end name as a COLLECTION of c_item objects

        We could also have worked the other way around, by specifying to which c_invoice the newly created c_item belongs:

            m_c_item.from_invoice:= my_c_invoice;



Note that
  • this use of the relation end names is called OCL navigation
  • we did not include any key in the c_invoice table. There is an underlying key, but is is automatically managed by the ECO framework
  • using the my_c_invoice.has_item collection is similar to a tObjectList nested inside each c_invoice. But OCL navigation is more general then simple containerpart relations


3.2 - Add the invoice_items

So let's implement this relation in our code.

You may use the previous project, but for this presentation, we will start a new project. Here are the steps to create the invoices part (everything here has been explained above):
   create a new ECO Windows Forms project, rename it p_02_invoicing_relation / u_02_invoice_relation
   compile to generate the UML runtime model, and close the running project
   from the project manager, select the u_02_invoice_relation.PAS, select the center "design" tab, and remove all components but rhRoot.
Select rhRoot and in the Object Inspector set its EcoSpaceType to p_02_invoicing_relationEcoSpace.Tp_02_invoicing_EcoSpace

   create the c_invoice class diagram, as explained above (select the "Model View", click on the grey "Package_1" icon, drop an "Eco Class" and fill it)

   add a Button to populate the c_invoice list and type the code which adds a couple of invoices
   on the p_02_invoicing_relationEcoSpace designer surface drop an PersistenceManagerXML, initialize its file name, then switch back to the main form designer, add a "save_xml" button which updates the EcoSpace
   compile and run


We will now add the c_item CLASS:
   select the "Model View", click on the greay "Package_1" icon, drop an "Eco Class", rename it c_item class diagram, and fill it with an m_price and an m_quantity attribute.
   from the Tools Palette, select the "Association" shape, click on the c_item class diagram and drag to the c_invoice class diagram

In the Object Inspector

  • select the End1 property, expand it, and
    • set its Multiplicity to "1"
    • and set its Name to from_invoice. This name IS IMPORTANT since it will be used for navigation
  • select the End2 property, expand it, and set its Multiplicity to "1..*" and its Name to has_item
   The UML class diagram should look like this:

image

   compile the project


If you look at the c_invoice CLASS (in PACKAGE_1UNIT.PAS) you will notice that the CLASS now contains a has_item PROPERTY (the code has been heavily changed):

unit Package_1Unit;
  interface

    type
      Ic_invoiceList=
        interface(ICollection)
          function Add(valuec_invoice): Integer;
          // -- ..ooo...
        end// ic_invoiceList

      Ic_itemList=
        interface(ICollection)
          // -- ..ooo...
        end// ic_itemList

      c_invoice=
        class(System.ObjectILoopBack)
          // -- ...ooo...
          public
            function AsIObjectIObjectInstance;
            property m_customerstring read get_m_customer
                write set_m_customer;
            property m_invoice_numberInteger read get_m_invoice_number
                write set_m_invoice_number;
            property has_itemIc_itemList read get_has_item;
            constructor Create(serviceProviderIEcoServiceProvider); overload;
        end// c_invoice



Here is how to add a couple of items to our first invoice:
   select the main form designer
   drop a Button on the Form, create its Click event and type the following code which creates three c_items and links them to the first c_invoice:

procedure TWinForm.add_first_invoice_items__Click(senderSystem.Object;
    eSystem.EventArgs);

  procedure add_item_to_invoice(p_priceDoublep_quantityInteger;
      p_c_invoicec_invoice);
    begin
      with c_item.Create(EcoSpacedo
      begin
        m_price:= p_price;
        m_quantity:= p_quantity;

        from_invoice:= p_c_invoice;
      end// with c_invoice.Create
    end// add_item_to_invoice

  var l_c_invoice_listBorland.Eco.ObjectRepresentation.iObjectList;
      l_c_invoicec_invoice;

  begin // create_items__Click
    l_c_invoice_list:= invoice_list_expression_handle.Element
        as Borland.Eco.ObjectRepresentation.iObjectList;

    // -- use any c_invoice
    l_c_invoice:= l_c_invoice_list[0].AsObject as c_invoice;

    add_item_to_invoice(1245.45, 12, l_c_invoice);
    add_item_to_invoice(452.33, 7, l_c_invoice);
    add_item_to_invoice(5832.55, 19, l_c_invoice);
  end// add_first_invoice_items__Click




To check that the invoices and the items are correctly linked, we can display them in a TextBox
   drop a Button on the Form and in its Click event, enumerate the elements of the c_invoice list, and for each c_invoice, enumerate the c_items:

procedure TWinForm.list_all__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_invoice_listBorland.Eco.ObjectRepresentation.iObjectList;
      l_invoice_indexInteger;
      l_c_invoicec_invoice;
      l_item_indexInteger;
  begin
    display('> list_all');
    l_c_invoice_list:= invoice_list_expression_handle.Element
        as Borland.Eco.ObjectRepresentation.iObjectList;

    for l_invoice_index:= 0 to l_c_invoice_list.Count- 1 do
    begin
      l_c_invoice:= l_c_invoice_list[l_invoice_index].AsObject as c_invoice;
      display(l_c_invoice.m_customer);
      for l_item_index:= 0 to l_c_invoice.has_item.Count- 1 do
        display('  'l_c_invoice.has_item[l_item_index].m_price.ToString
            + ' 'l_c_invoice.has_item[l_item_index].m_quantity.ToString);
    end// for l_invoice_index
    display('< list_all');
  end// list_all__Click

   compile, run, click "add_first_invoice_items_" and then "list_all_"
   here is the snapshot of the application:

image



Using the same technique, we added a couple of items to the other invoices (the "create_items_" Button which is in the .ZIP sources).



To add the item DataGrid display, we follow those steps:
   first display the invoices:
  • add a ExpressionHandle, set
    • its (Name) to invoice_list_expression_handle
    • its RootHandle to rhRoot
    • its Expression to c_invoice.AllInstances
  • drop a DataGrid, set
    • its (Name) to invoice_datagrid
    • its DataSource to invoice_list_expression_handle
   the field names of the c_invoice class are displayed in the invoice_datagrid

   to know which invoice is selected, add a CurrencyManagerHandle:
  • from the Tools Palette, drop a CurrencyManagerHandle and set
    • its (Name) to invoice_currency_manager_handle
    • its RootHandle to invoice_list_expression_handle
    • its BindingContext to invoice_datagrid
  • drop an ExpressionHandle for the items and set
    • its (Name) to item_list_expression_handle
    • its RootHandle to invoice_currency_manager_handle
    • its Expression to self.has_item
  • drop a DataGrid and set
    • its (Name) to item_datagrid
    • its DataSource to item_list_expression_handle
   compile and run
   when we move in the invoice_datagrid, the items of this invoice are displayed in the item_datagrid

image



Note that

  • we could not impose the attribute order in the c_item UML class diagram. We first chose (m_pricem_quantity) and then tried to change it to (m_quantitym_price) but could'nt succeed
  • for the (m_pricem_quantity) first trial, the invoice_grid had a last "has_item" column in the invoice_datagrid. After trying to switch to (m_quantitym_price) we have a first "from_invoice" column.
    By the same token, we could not increase the size of the invoice_datagrid last column width. There must be a way to do those things, but since this is not the most important part, we


In a way similar to how we deleted an invoice, we can add some items to the current invoice using the CurrencyManagerHandle component
   drop a Button on the Form, a in it Click event, create an new empty item to the currently selected invoice:

   compile, run, click "add_item_to_invoice_"

procedure TWinForm.add_item_to_invoice__Click(senderSystem.Object;
    eSystem.EventArgs);
  var l_c_selected_invoicec_invoice;
      l_c_new_itemc_item;
  begin
    if invoice_currency_manager_handle.Element.AsObject is c_invoice
      then begin
          l_c_selected_invoice:=
              c_invoice(invoice_currency_manager_handle.Element.AsObject);

          l_c_new_item:= c_item.Create(EcoSpace);
          l_c_selected_invoice.has_item.Add(l_c_new_item);
          // -- or
          // l_c_new_item.from_invoice:= l_c_selected_invoice;
        end;
  end// add_item_to_invoice__Click

   here is a snapshot of this addition:

image




4 - OCL: the Object Constraint Language

4.1 - OCL for navigation and for modeling

OCL at the start was created to impose pre and post-conditions, similar to the pre and post-condition that the Eiffel language introduced to implement "Design by Contract". For a bank withdraw() method, you require that at the start of the method, just after BEGIN, the balance should be greater then the amount taken out, and after the withdrawal, just before the END of the method, the balance must be equal to the starting amount less the amount taken out. It basically is an expression language allowing to specify in a non procedural way how the methods of our CLASSes are changing the state of our system. In other words specifying the behaviour of the model. Since the methods of one CLASS often uses other CLASSes, OCL also formalized navigation, allowing for instance to specify that the total of the c_item amounts of a c_invoice should be greater than $100:

 
    Self.has_item.AllInstances->collect(m_price* m_quantity)->sum > 100
 



In our projects above, we already used OCL expressions to navigate the model. For instance, to be able to visualize the invoice collection in a DataGrid, we used:

    my_c_expression_handle.Expression:= 'c_invoice.AllInstances';

We now are going to investigate some other OCL expressions.



4.2 - Evaluating OCL expression by Code

So lets start with the expression that we already used, c_invoice.AllInstances. This expression computes the list of all c_invoice objects. The result of this computation is some kind of list. To be more accurate, it is an iObjectList.

To compute this list we call the EcoSpace.OclService.Evaluate function, which returns the iObjectList:

VAR my_c_ielementiElement;

my_c_ielement:= 
    EcoSpace.OclService.Evaluate(Nil'c_invoice.AllInstances'Nil);

This iObjectList has a Count and an Item property. Each Item[nnn] is an iElement. So to display it we have to cast it back into our Delphi object world, using AsObject:

with iObjectList(my_c_ielementdo
  for l_index:= 0 to Count- 1 do
    displayItem[l_index].AsObject.ToString );

Since our objects (c_invoice and c_item) implement the ToString FUNCTION, we will see the values displayed.



This is how we implemented this evaluation
   we started a new project, but you can of course use your current project (or, alternately, use the source code from the downloadable .ZIP below).

Our project contains simply the 2 classes with the association, the ToString implementation in each CLASS, and a Button for initializing the values

   drop a Button, call it "all_instances_", and in it Click event, call our iObjectList method:

procedure TWinForm.all_instances__Click(senderSystem.Object;
    eSystem.EventArgs);
  begin
    evaluate_iobjectlist('c_invoice.AllInstances');
  end// all_instances__Click

   in the tWinForm CLASS define the evaluation method:

unit u_03_invoicing_ocl;
  interface
  type TWinForm=
      class(System.Windows.Forms.Form)
          // -- ...ooo...
        public
          procedure evaluate_iobjectlist(p_ocl_expressionString);
      end// TWinForm

   type the code of this method:

procedure TWinForm.evaluate_iobjectlist(p_ocl_expressionString);
  var l_c_ielementiElement;
      l_indexInteger;
  begin
    l_c_ielement:= EcoSpace.OclService.Evaluate(Nilp_ocl_expressionNil);

    if l_c_ielement is iObjectList
      then begin
          with iObjectList(l_c_ielementdo
            for l_index:= 0 to Count- 1 do
              display('  'Item[l_index].AsObject.ToString);
        end
      else display('not_iObjectList ');
  end// evaluate_iobjectlist

   compile, run, create the values, click "all_instances_"
   here is the snapshot of our project

image



Here is an UML Class diagram of this evaluation:

image

and:

  • our tWinForm contains the EcoSpace attribute, this EcoSpace containing the OclService member
  • the OclService.Evaluate returns one of the iElement descendents


4.3 - Evaluating different iElement expressions

How do we know that the type of the AllInstances is iObjectList ? Well OCL expressions compute values in the EcoSpace world. The object kinds in this outer world are all descendents of iElement, and the iElement CLASS has a ContentType enumeration, which tells us which descendent of iElement should be used. So while spelunking in this EcoSpace world, we used the following FUNCTION which displays the iElement descendent that should be used

Here is the function (which is in the downloadable .ZIP):

function TWinForm.f_content_type_name(p_content_typeContentType): String;
  begin
    case p_content_type of
      ContentType.Primitive : Result:= 'Primitive';
      ContentType.Collection : Result:= 'Collection';
      ContentType.Tuple : Result:= 'Tuple';
      ContentType.Object : Result:= 'Object';
      ContentType.ObjectList : Result:= 'ObjectList';
      ContentType.ConstraintCheck : Result:= 'ConstraintCheck';
      else Result:= '???_unknown';
    end// case
  end// f_content_type_name

Note that

  • since ContentType is defined in the Borland.Eco.ObjectRepresentation Namespace, we also had to move this USES from the IMPLEMENTATION to the INTERFACE level


To be able to display other iElement descendent expression, we then implemented the other evaluation methods. Here are all our OCL expression evaluation and display methods:

procedure TWinForm.evaluate_iprimitive(p_ocl_expressionString);
  var l_c_ielementiElement;
  begin
    l_c_ielement:= EcoSpace.OclService.Evaluate(Nilp_ocl_expressionNil);

    if l_c_ielement is iPrimitive
      then display('  'iPrimitive(l_c_ielement).AsObject.ToString )
      else display('  not_iPrimitive_but '
          + f_content_type_name(l_c_ielement.ContentType));
  end// evaluate_iprimitive

procedure TWinForm.evaluate_iobject(p_ocl_expressionString);
  var l_c_ielementiElement;
  begin
    l_c_ielement:= EcoSpace.OclService.Evaluate(Nilp_ocl_expressionNil);

    if l_c_ielement is iObject
      then display('  'iObject(l_c_ielement).AsObject.ToString )
      else display('  not_iObject_but '
          + f_content_type_name(l_c_ielement.ContentType));
  end// evaluate_iobject

procedure TWinForm.evaluate_ielementcollection(p_ocl_expressionString);
  var l_c_ielementiElement;
      l_indexInteger;
  begin
    l_c_ielement:= EcoSpace.OclService.Evaluate(Nilp_ocl_expressionNil);

    if l_c_ielement is iElementCollection
      then
        with iElementCollection(l_c_ielementdo
          for l_index:= 0 to Count- 1 do
            display('  'Item[l_index].AsObject.ToString)
      else display('  not_iCollection_but '
          + f_content_type_name(l_c_ielement.ContentType));
  end// evaluate_ielementcollection

procedure TWinForm.evaluate_iobjectlist(p_ocl_expressionString);
  var l_c_ielementiElement;
      l_indexInteger;
  begin
    l_c_ielement:= EcoSpace.OclService.Evaluate(Nilp_ocl_expressionNil);

    if l_c_ielement is iObjectList
      then
        with iObjectList(l_c_ielementdo
          for l_index:= 0 to Count- 1 do
            display('  'Item[l_index].AsObject.ToString)
      else display('not_iObjectList_but '
          + f_content_type_name(l_c_ielement.ContentType));
  end// evaluate_iobjectlist

At the start of the method we also display the expression string (this is not shown here)



4.4 - OCL expression gallore

4.4.1 - Class member collection

We can request to evaluate the values of the members of a CLASS. For instance, to get the list of all customer names, we can use:

 
    c_invoice.AllInstances.m_customer
 

The result of this expression is a collection, with the iElementCollection TYPE. So we called:

evaluate_ielementcollection('c_invoice.AllInstances.m_customer');



4.4.2 - OCL operators

OCL contains some predefined functions, like Size. We can call this on any iObjectList or iElementCollection values. To avoid some conflict with any CLASS attribute which could be named "Size", OCL decided to use the "->" symbol to specify a FUNCTION call, like this:

 
    c_invoice.AllInstances->Size
 

The result of Size is an iPrimitive Integer value, so we called:

evaluate_iprimitive('c_invoice.AllInstances->Size');

Also not that those functions are called "operators" (like UML methods)



4.4.3 - ForAll

To check that some condition is true for all members of a list, we can call the ForAll function:

 
    c_invoice.AllInstances->forAll(my_invoice | my_invoice.m_invoice_number> 100
 

The result of Size is an iPrimitive Boolean value, so we called:

evaluate_iprimitive('c_invoice.AllInstances'
  + '->forAll(my_invoice | my_invoice.m_invoice_number> 100)');



4.4.4 - Select and Self

The Select FUNCTION allows us to extract a list of iElements from a list:

 
    c_invoice.AllInstances->select(m_invoice_number= 12345)
 

The result of Size is an iObjectList, so we called:

evaluate_iobjectlist('c_invoice.AllInstances'
    + '->select(m_invoice_number= 12345)');



We used this Select to specify a single c_invoice object. All OCL expressions are evaluated in some context. In our case, we used the global EcoSpace context. But if some expression is in the context of one object (say the invoice object of Mr Miller) we could use the Self OCL specifier. Like this:

 
    Self.m_invoice_number
 



4.4.5 - Navigating to a related object

To navigate to another object, we use the relation end names. For instance, to display the items of some invoice:

 
    c_invoice.AllInstances->select(m_invoice_number= 12345).has_item'
 

And to display the iObjectList values:

The result of Size is an iObjectList, so we called:

evaluate_iobjectlist('c_invoice.AllInstances'
    + '->select(m_invoice_number= 12345).has_item');



s
Checking the OCL expression

To make sure that our OCL expression is correct, we can use the OCL Editor. This is accessible from the ExpressionHandle.Expression property in the Object Inspector.

So:
   select the u_03_invoicing_ocl main form designer
   select rhRoot and make sure that its EcoSpaceType property is set to your EcoSpace type (p_03_invoicing_oclEcoSpace.Tp_03_invoicing_oclEcoSpace in our case)
   drop an ExpressionHandle on the Form
Set its RootHandle to rhRoot
   select Expression and click on the Ellipsis (or right click and select "Edit Expression", or use the link at the bottom of the Object Inspector
   the OCL Editor is displayed

image

   double click on c_invoice in the right treeview
   this element of the expression is displayed in the left memo, and the right treeview is updated to present what we can now add to our expression

image

   click on .AllInstances
   to see what happens if an expression is incorrect, type now directly in the memo "customer" (instead of "m_customer")
   the incorrect part is displayed in red:

image




5 - Comments on this ECO III with Delphi 2006 tutorial

5.1 - What's Next ?

Among the many things that were not presented, we can list
  • the use of Sql Database persistence. Since we already presented many papers about ADO .Net, the connection should be easy. However, ECO has also some specific parts for handling transactions and tweaking the generated Sql requests
  • the uses of ASP .Net Web pages, and Web Services. There are published papers in this area (see the references)
  • the creation of "derived attributes", which in Delphi language means calculated fields
  • the creation of "ECO Variables", which are the equivalent in OCL world of the parameters in parametrized Sql requests
  • the creation of custom OCL operations (adding new OCL function, in addition to Size etc)
  • using OCL constraints at the UML model level
  • the possibility to execute the UML state diagram, and in ECO III to use an Action Language (wich can change the state of the objects, whereas OCL only evaluates expressions, without side effects)


5.2 - OCL for modeling

My primary motivation to look at the ECO framework was to try the OCL language.

To be able to code at a more abstract level than Pascal code, UML (or any other modeling language) looks like a very good choice for structural modeling (Class diagram). On the behavioral side, things get more complicated.

First of all you have two schools:

  • one trying to work at the UML model level only, and the framework will blindly generate the Pascal code. The same jump from model to Pascal as from Pascal to Assembler. This is the MDA approach, with Action Languages, implemented by Kennedy Carter, PathFinder or xtUML and MELLOR at Bridgepoint. Quite heavy machinery, with many marketing enthusiatic presentations, and very few real life examples, if any. In addition these models generate the code from State Diagrams, since those frameworks are anchored in the telecom world (the phone is "on hook", "ringing", etc)

  • one trying to use the UML model as a starting point, this model being used to generate some stub code that the developer has to fine tune. This is the "round trip" approach.
    In this area you find both generation from State Diagram or from Activity Diagram (or Object Diagrams). The Activity Diagram approach focuses more on chunks of work, than state. This is obviously more in line with business thinking ("process the order", "manufacture the product" etc), and somehow gave birth to the Workflow models.
At the end of this ECO trial, we now know how to use some specification language at the UML model level, like OCL, to navigate the different Classes of the model, and this was our main target at this point. And ECO is the only implementation we have, with State Diagram execution and Action Language.




6 - Download the Sources

Here are the source code files:

The .ZIP file(s) contain:

  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters, samples, test data
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
  • create or select any folder of your choice
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.

The Pascal code uses the Alsacian notation, which prefixes identifier by program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse etc. This notation is presented in the Alsacian Notation paper.



As usual:

  • please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :
     

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.



7 - ECO documentation

We are not overhelmed with documentation, but here is what I found:
  • the basic tutorials are those from Anthony RICHARDSON
  • there are a couple of slides (use Google to find the links)
    • Jason VOKES, Gerard VAN DER POL (marketing slides)
    • Peter MORRIS, did some presentations, and also has a blog (see below)
    • Tim JARVIS
  • some blogs will keep you up to date
  • there is a book:
    • Alois SCHMID who wrote a book in german, which seems to have been translated in English
  • Peter MORRIS has several interesting places:
  • there is a Eco Community page on the CodeGear site
  • also there is a Wiki page at CogeGear with very nice tutorials on some detailed points of ECO II or ECO III. Exactly what a Borland tutorial should have looked like in the first place (but for the whole framework, not for some specific points only)
  • ECO Wikia a wiki about ECO. And there are other Wiki pages about ECO
  • projects
    • Delphi_Unleashing_enterprise_models_with_Delphi_8_Architect_for_.NET.pdf" - by Christoph FLOURY - A Whitepaper with a Project Mangement example, and the most complete Class diagram about the ECO Framework
    • BLOGGING WITH DELPHI is a 4 part paper from Bob SWART explaining how to build a blog application with ECO and ASP.Net
    • Real life complexity with ECOII by Hans KARLSEN, is a time keeping application
  • using Tamaracka to search the Eco newsgroup, you will find many ECO questions, and, what is very nice, is that the ECO developer often directly answer to the questions


Overall, I feel a little bit disapointed that ECO changed so often, and that the official documentation (manuals or Help) are so thin. But all those resources should get you started in this brave new ECO world.




8 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: may-07. Last updated: dec-15 - 99 articles, 220 .ZIP sources, 1068 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
      + interbase
      – firebird_trans_simulator
      + sql_server
      + bdp
      – db_refactoring
      – sql_parser
      – sql_to_html
      – sniffing_interbase
      – eco_tutorial
      – dbx4_programming
      – blackfishsql
      – rave_pdf_intraweb
      – rave_reports_tutorial
      – rave_reports_video
      – embarcadero_er/studio
      + firedac
      – bde_unidac_migration
    + web_internet_sockets
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog