Welcome To The Home Of The Visual FoxPro Experts  
home. signup. forum. archives. search. google. articles. downloads. faq. members. weblogs. file info. rss. print.
BUILDING AN N-TIER VERSION OF THE TASTRADE DEMO APPLICATION USING FOXDATAOBJECTS – PART 2

What is FoxDataObjects?
FoxDataObjects (http://www.foxdataobjects.com) is an object/relational persistence and query engine for Visual FoxPro. It lets us develop persistent classes following object-oriented idiom - including association, inheritance, composition, and collections, and allows us to express queries using a simple Object Query language, as well as in native SQL. Basically the product automates the Data-Access layer of an n-Tier application, but more important, it maps our business object model to a relational model, connects our Business layer to any supported database engine and provides a simple API for object persistence and querying. In this article, we will re-create parts of the Tasmanian Traders (Tastrade) sample application, but in an n-Tier architecture to show some basic features of FoxDataObjects.

Part 2
In the first article, 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. Now, 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. 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.

Working with objects references
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.
In this article, 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 may want to 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 instance 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:

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:
 
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 (it is classes and members expressions intead of tables and columns expressions). FoxDataObjects translates it to the corresponding Relational Model SQL WHERE.

Let us try it!

oProd=.NULL.
oCat=.NULL.

The following commands return the same object instance:

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.

We can navigate the retrieved Product instance, as it was never removed from memory.

? 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 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 && added this line 
	ProductName = ""
	EnglishName = ""
	Category = .Null.
	QuantityInUnits = 0
	UnitPrice = 0.0000
	UnitCost = 0.0000
	UnitsInStock = 0
	UnitsOnOrder = 0
	ReorderLevel = 0
	Discontinued = .F.
ENDDEFINE


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

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.
 
? oPrd.ProductName && "Guaraná Fantástica"
? oPrd.Code && 0

Let us assign a value to the Code property and save the instance:

oPrd.Code=1
? oSession.SaveObject(oPrd) && .T.
oPrd=.Null.

Now we should be able to retrieve our Product instance by its Code number like:

oPrd=oSession.GetObject("Product","Code=1")
? oPrd.ProductName && "Guaraná Fantástica"

In this second article, we learned some basic 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.

ABOUT THE AUTHOR: BERNARDO FOURCADE

Bernardo Fourcade RunAhead Technologies was founded in 1999 by the Visual FoxPro Senior Architect Bernardo Fourcade. Fourcade graduated with honors in Information Systems Engineering and Information Systems Analysis at Universidad Tecnológica Nacional. He started developing software early in 1986 with FoxBASE+ and closely followed the language evolution, working with every single version to the latest Visual FoxPro version. Beyond his deep commitment with the FoxPro platform, he also expanded to Visual C++, Visual C#, Visual Basic .Net, ASP3 and ASP.Net technologies. A solid experience on Project Management and the whole software development process, from designing, coding, testing, to deployment and re-engineering, lead him to work for some of the most important software companies. Prior to RunAhead foundation, he performed for more than 10 years as Chief Technology Officer at an important consulting company, mainly focused on the Health Care and Television Broadcast markets, where he lead and managed the most advanced and challenging software development projects. Now, at RunAhead Technologies, Fourcade is leading a strong development and supporting team, committed with the high standards of the industry. You can contact him at info@runahead-tech.com and you can find additional information at http://www.foxdataobjects.com/About.htm.

FEEDBACK


Your Name: 
Your Feedback: 

Spam Protection:
Enter the code shown: