On the first tutorial, we learned how to begin working with FoxDataObjects, created our class library and an empty mapping file, parsed our classes and generated the first mappings by default.
On this tutorial, we will learn how to customize mapping options and begin working with objects relationships.
·
Open the Schema Manager GUI tool.
· On the Schema Manager click on the Open button in the toolbar or select File / Open from the menu.
· Select your Tastrade.FDO schema file.
Your schema file is opened and the Schema Manager shows all the items contained in the mapping file.
Let us explore some Schema Manager options. As you already observed, the schema manager shows the mapping information on two main panels: The Left side panel containing a Tree representation of the mapping schema, and the Right panel showing properties and options available for the current selected node in the left tree.
The left tree contains a parent node representing the whole schema file (Main Schema). By now, we will work with a single schema file, but it is possible to have more than one schema file displayed on the left tree as showed in other tutorials. This node shows summary information about the schema file and allows editing the mapping options used by default when FoxDataObjects parses and map a new or modified class definition.
In the Mapping Defaults page, you can specify tables’ prefixes and suffixes for new tables definitions created by FDO, default concurrency-control values, etc. Regarding the default Inheritance Strategy option, briefly, it allows you to specify how a new child class (a class that inherits from another business class) is mapped into the relational model by default. With a Vertical strategy, the child class is mapped into its own table, while with a Horizontal or Flat strategy the child class members are mapped to the same table its parent class’s does. We will see that on Tutorial 4.
The DataSources tab allows you to create DataSources definitions and store them right into the mapping file. When a Session object needs to connect to the data storage, if no other information is supplied, the persistence engine looks into the loaded schema file for a DataSource definition marked as Default. In the first tutorial, we created a DataSource definition and stored it in the schema file. The Documentation Manual and the API Reference contain detailed information on DataSource objects and definitions.
As stated before, the Relational Model node within a Mapping Schema, groups all the classes and members found in your parsed classes libraries. Classes’ nodes are located in accordance with its inheritance hierarchy. In addition, each class node shows a child node for each class’ member.
Please check the Schema Manager help, which contains detailed descriptions on each form and control purposes.
Clicking on a class node, the right panel shows the class properties form, where you can document your class and indicate whether the class is Persistent or Not Persistent. A persistent class is a class whose objects instances can be stored on the database (as business objects classes). A not persistent class, is a class whose objects instances will not be directly stored on the database.
For persistent classes, the Mapping tab allows you to tell FDO how to map your class into the relational model. Here you can specify the inheritance strategy for an inherited class, the name of the table where the class’ members will be mapped, etc. Changing the table name here, renames the current table definition on the Relational Model, so it is easy to make mappings adjustments right from the class form. FDO will do the job for you on keeping your Relational Model definitions in sync and will even recreate all of your changes when you connect to a database.
Clicking on a persistent class member’s node, the right panel shows the member’s properties form where you can document your class member and indicate whether the member is Persistent or Not Persistent.
Any Visual FoxPro class whose state is entirely represented by the state of its properties could be persistent. Therefore, we will focus on class’ Properties for object persistence (contained objects are just objects references and will be analyzed later).
From an ORM viewpoint class members can be:
· A value Property (including arrays)
· A reference to another object
· A collection of references or values
Please check the Mapping Basics section on the documentation for a complete description on this topic.
In the first tutorial we saw how properties where mapped to table columns. This way a String property is mapped to a String column type (Char, Varchar, Text, etc).
By default, FoxDataObjects maps all the string properties into a VarChar column type for maximum flexibility. You can make all sorts of changes in the way a property is mapped into a column table.
Clicking on the Mapping tab for a Property member you can set the Column name, Column Type, Size, Decimals places, Default value, and even indicate you want an Index created on that column. Changes you make here alter the Relational Model definitions immediately, and the database you are working with is updated as soon as you re-connect.
· Expand the Object Model node and the Category class node.
· Click on the PictureBitMap node.
· Click on the Mapping tab in the right panel’s Category.PictureBitMap member form.
· Change the Column Name field to picbitmap
· On the DataType dropdown list, select BLOB (Binary Large Object)
· Expand the Relational Model node and click on the Category table node. The right panel shows the Category table properties form where you can see your changes reflected.
You can make all sort of changes into the table definition using the Columns, Indexes and Relations pages.
In Object-Oriented designs, entities may reference to other entities, generating relationships between objects instances. There are to-One and to-Many relationships.
In Visual FoxPro classes, you can reference to another object (a To-One relationship) either by using a property holding an object pointer value or by using a contained object; and you can reference to a group of objects (a To-Many relationship) by using a Collection.
On this tutorial, we will start using single references to learn how FoxDataObjects works with To-One relationships.
In the Classes.PRG class library, on the Product class definition, we declared a property named Category with a Null pointer as default value.
· Expand the Object Model node and the Product class node. It will expand and show all the members the Product class contain. You can see the Category member drawn with a single reference icon and a “To any object” description. It means the member will be treated as a reference to any object.
· Click on the Category member node
On the Member’s properties form, you can see the Member Type field is set to Reference, the FoxPro data type field indicates that the member is implemented as an Object Pointer, and you can see the Relationship page is enabled.
When FoxDataObjects parsed the class definition and detected the NULL value as default value for the property, it assumed the property would be used to hold an object pointer, it is, a reference to another object.
· Click on the Relationship tab
The next figure shows the content for the Relationship tab:
FoxDataObjects is able to work with heterogeneous relationships. It means that by default, the Product.Category property for a given object instance can hold a pointer to any type of object, and when you save your Product instance, FoxDataObjects will also persist the referenced object (if the referenced object is an instance of a Persistent class).
This is a powerful feature and reflects the fact that in the Visual FoxPro object world, a property may point to any kind of object.
However, you can restrict that (as best practices suggest), and instruct FoxDataObjects about the class to be used as destination for the relationship, so the persistence engine can check it at save time.
In our case, we want our Product.Category property to be used to hold a reference to a Category instance, so:
· Select Category from the Referenced Class dropdown list.
The Ordinality option can be used to specify if the relationship is Optional (0-To-One) or Mandatory (One-To-One)
The Ownership option is very important because it tells FoxDataObjects whether the object holding the reference (in this case the Product instance) is the owner of the referenced object (category instance). If this attribute is set (checked), when you delete a Product instance, the referenced Category instance is also removed (Cascade delete). However, in our case, we do not want this, so leave this option unmarked. We will see Ownership examples later.
The Lazy loading option is one of the coolest features from FoxDataObjects. When a relationship is flagged with the Lazy Loading option and you retrieve an object instance from the database, the referenced objects instances are not retrieved until needed. When you access a property or method in the referenced object, FoxDataObjects will retrieve it from the database. All of this happens behind the scenes, and you do not need to implement any special functionality or inherit from any special class to get it working. It works transparently with all of your objects references by default.
· So, ensure the Lazy Loading option is checked for the Product.Category property.
Now we will see all of these concepts in action.
· Close the Schema Manager and ensure your changes to the mapping schema file are saved.
As we did on the first tutorial, we will use the Persistence Services from the command window just to test. Type the next commands:
|
Command |
Result |
|
SET PROCEDURE TO FDO, Classes ADDITIVE
|
|
|
oServer=CREATEOBJECT(“fdoServer”)
|
|
|
oSession=oServer.NewSession(“Tastrade.FDO”)
|
|
|
? oSession.IsConnected
|
.T. |
At this point, we got a session object connected. Upon connection, by default, the session object checked the database schema and upgraded it if necessary.
We will create one Product instance, one Category instance, and we will relate them:
|
Command |
Result |
|
oProd=CREATEOBJECT("Product")
|
|
|
oProd.ProductName="Guaraná Fantástica"
|
|
|
oProd.EnglishName="Guaraná Fantástica Soft Drink"
|
|
|
oProd.UnitPrice=4.5
|
|
|
oProd.UnitCost=3.15
|
|
|
oCat=CREATEOBJECT("Category")
|
|
|
oCat.CategoryName="Beverages"
|
|
|
oCat.Description="Soft drinks, coffees, teas, beer, and ale"
|
|
|
oCat.PictureBitMap=FILETOSTR("bitmaps\beverage.bmp")
|
|
Now we can associate the Product instance with the Category instance:
|
oProd.Category=oCat
|
|
and save the Product instance:
|
? oSession.SaveObject(oProd)
|
.T. |
At this point, FoxDataObjects saved the Product instance along with the Category instance. It is called Persistence-by-Reachability. When FoxDataObjects saved the Product instance, it found that the referenced Category object (even though it has been marked as not owned) does not exist on the database (it has not been persisted yet) so it saved the referenced object too in the same transaction.
When saving the objects, FoxDataObjects assigned an Object_ID value to each instance, and related them using those values.
|
? oSession.GetObjectId(oProd)
|
L0_1OU15RLVY |
|
? oSession.GetObjectId(oCat)
|
Aj_1OU15RLX2 |
To retrieve an object instance from the database we can use the Session.GetObject method or an Object Query.
We can use Session.GetObject to retrieve a single object (an object graph). We can pass the instance identifier (Object_ID) value or the class name and a conditional expression (or search expression). The search expression is expressed in Object Model terms. FoxDataObjects translates it to the corresponding Relational Model SQL WHERE.
Let us try it!
|
Command |
Result |
|
oProd=.NULL.
|
|
|
oCat=.NULL.
|
|
The following commands return the same object instance:
|
Command |
Result |
|
oPrd=oSession.GetObject(“Product”,”ProductName like ’Gua%’ and Product.Category.Description like ‘%drink%’”)
|
|
|
oPrd=oSession.GetObject(“L0_1OU15RLVY”)
|
|
We should included a “Business” unique identifier to our Product instances like a mnemonic string identifier or a numeric code to get the sample closer to our “relational-way”, but we will add it soon.
In the first command we used GetObject() with two arguments, note that the second argument uses Object-Model expressions, including members of referenced objects. This is translated by FDO into the SQL SELECT statement required to perform a fully optimized query.
In the second example, we passed just the instance identifier. Note that FDO knows the class (tables) where to look for a given instance identifier. It helps you focus on your object model without the need to know about tables’ names or column’s names.
See the API reference for a detailed description on this method.
We can navigate the retrieved Product instance, as it was never removed from memory.
|
Command |
Result |
|
? oPrd.ProductName
|
Guaraná Fantástica |
|
? oPrd.UnitCost
|
3.15 |
|
? oPrd.Category.CategoryName
|
Beverages |
|
? oPrd.Category.Description
|
Soft drinks, coffees, teas, beer, and ale |
Note than even referenced objects are retrieved, like the Category instance. Behind the scenes, Session.GetObject() retrieved only the Product instance because of the Lazy Loading flag enabled for the Product.Category reference. When we accessed a property or method in Product.Category, FoxDataObjects retrieved the Category instance transparently.
It brings a huge difference in performance to your applications. If you make changes to the Product instance, when you save it to the database using Session.SaveObject, FoxDataObjects ignore not retrieved lazy-loading references.
As stated, let us add a Business identifier to our Product instances.
· Clear your objects by issuing a:
CLEAR ALL
· Open your class library file
MODIFY COMMAND CLASSES
· Modify the Product class definition adding a Code property. The Product class definition should end with something like:
DEFINE CLASS Product AS Custom
Code = 0
ProductName = ""
EnglishName = ""
Category = .Null.
QuantityInUnits = 0
UnitPrice = 0.0000
UnitCost = 0.0000
UnitsInStock = 0
UnitsOnOrder = 0
ReorderLevel = 0
Discontinued = .F.
ENDDEFINE
(Note in red the added line).
Now let us retrieve our product object to assign it a code value:
· Close your Classes.PRG saving your changes
And try to retrieve the Product instance we stored before
|
Command |
Result |
|
SET PROCEDURE TO FDO, Classes ADDITIVE
|
|
|
oServer=CREATEOBJECT(“fdoServer”)
|
|
|
oSession=oServer.NewSession(“Tastrade.FDO”)
|
|
|
oPrd=oSession.GetObject(“Product”,”1=1”)
|
|
The last command it is just a trick to retrieve the first Product instance in the repository.
What happened?
FoxDataObjects detected that your object model has changed, so it parsed your class definition, documented and mapped the changes, and opened the Schema Manager for you to confirm the new mappings.
Note that modified items appear in Blue and new items appear in red.
In this case, the Product class has changed. It has a new Code member.
· Click on the Code member node and click on the Mapping tab in the right panel.
FoxDataObjects mapped the new property to a new column named Code and assigned a default data type based on the FoxPro data type found in the property value.
· Set the column data type to Integer.
· You can set a unique index (or constraint) for this column right from this form by clicking to set the Indexed and Unique check boxes.
Expand the Relational Model node and check the Product table. You will find the new table column with the proper data type and the new index definition.
Once your mappings are done, you are ready to save them and continue working:
· Click on the green “Go” button located at the upper-right side on the toolbar.
Doing so, the Schema Manager saves your changes to the mapping file and closes. FoxDataObjects replicates the relational model changes into the current connected database and your application (in this case a command issued from the Command window) continues its normal execution.
This way you simply edited your class code, made changes on your object model and FoxDataObjects detected and mapped them while you where testing your new code.
The previous command should be completed and the Product instance should be retrieved.
|
Command |
Result |
|
? oPrd.ProductName
|
Guaraná Fantástica |
|
? oPrd.Code
|
0 |
Let us assign a value to the Code property and save the instance:
|
Command |
Result |
|
oPrd.Code=1
|
|
|
? oSession.SaveObject(oPrd)
|
.T. |
|
oPrd=.Null.
|
|
Now we should be able to retrieve our Product instance by its Code number like:
|
Command |
Result |
|
oPrd=oSession.GetObject(“Product”,”Code=1”)
|
|
|
? oPrd.ProductName
|
Guaraná Fantástica |
In this second tutorial, we learned some basics Schema Manager options, and saw a simple introduction to Objects References. We also learned how our entire development process is simplified, just editing and testing our class code and how FoxDataObjects takes care of relational definitions and objects persistence.
| Home |
Send feedback on this topic to RunAhead Technologies
For Technical support and product issues please contact us at support@foxdataobjects.com or visit http://www.foxdataobjects.com
Copyright (c) 2000-2005 RunAhead Technologies