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 3

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 3
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. In part 2, we will learned how to customize mapping options and begin working with objects relationships.

Adding Behaviour and Using Events
Until now, we have used our classes’ definitions just to hold data, but a complete object model not only has data but behaviour. You can enrich your object model by adding Methods and Events to your classes. You are free to add all the methods your classes need, because FoxDataObjects does not imposes any restriction to your business classes definitions, and you do not need to inherit your business classes from over-charged and over-engineered classes as other frameworks impose.


As a basic example, in the Category class we have a PictureBitMap property that holds a picture for the category instance in binary string format. We can add a couple of methods to the Category class that allows loading and saving such picture bitmap from/to a disk file. This way, a Category instance can work with BMP files, no matter if it is instantiated by a Visual FoxPro program, an ASP web page, a VB or .Net program, etc. Let us add the LoadPic and SavePic methods to the Category class.
- Clear your objects instances by typing this in the Command window:

CLEAR ALL

- Open your Classes.PRG file to edit

MODIFY COMMAND classes

- Edit the Category class definition. It should end in something like:

DEFINE CLASS Category AS Custom
    CategoryName = ""
    Description = ""
    PictureBitMap = ""

    PROCEDURE LoadPic (tcFileName as String)
        IF VARTYPE(tcFileName)="C" AND FILE(tcFileName)
            This.PictureBitMap = FILETOSTR(tcFileName)
        ENDIF
    ENDPROC

    PROCEDURE SavePic (tcFileName as String)
        IF VARTYPE(tcFileName)="C"
            STRTOFILE(This.PictureBitMap,tcFileName)
        ENDIF
    ENDPROC
ENDDEFINE


This is just basic code. Best practices suggest that we should control and protect file operations within a Try/Catch block and return a logical value indicating whether the file was created or read successfully. Your business objects will be plenty of methods encapsulating all the behaviour your real life entities expose. As with any other VFP class, you can expose those methods by leaving them PUBLIC (as the sample) or, if they will be used only within your class or inherited class methods you can protect or hide them by adding the HIDDEN or PROTECTED keyword to the Method declaration (see DEFINE CLASS in the Visual FoxPro language reference).

You can freely use Assign_ and Access_ methods for your object instances to catch and react to changes on special properties, map stored values to a different subset or domain, etc.

Using Persistence Events
When our applications use Persistent Services to save, retrieve or remove objects instances from the database, FoxDataObjects generates a rich set of events that can fire methods on our classes’ definitions. It opens a new world of possibilities to our business objects. Persistence Events give us the perfect space to implement Business Rules, whenever your objects are retrieved, removed or saved to the database.

In order for your objects instances to react to a persistence event, you just need to define a method in the class definition for the event you want to implement. As an example, when an object instance is going to be saved to the database, FoxDataObjects generates an OnSave event. If your class has a method named OnSave, it is fired automatically by FoxDataObjects.

You could use the OnSave event on your class definition to execute validation code to enforce Business Rules, prepare or change data to be saved, update, remove other objects, send/retrieve data directly from the data store, etc.

When FoxDataObjects executes a Method in your class for a given Persistence Event, it passes one or more parameters to the Class method depending on the event fired. The first parameter passed is a pointer to the Session object involved in the persistence event. This way you can access all the persistence services within your Method code.

Most events are conditional; it means that if the method implementing the event returns a False value, the whole operation is cancelled and the active transaction is rolled back.

Let us try some basic usage for Persistence Events. We will use the OnSave method on the Product class to check some conditions and impose a minimal set of business rules.

- Edit the Product class definition. It should end in 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.

    PROCEDURE OnSave(oSession as Object)
        IF EMPTY(This.Code)
            oSession.Errors.Set("Product Code cannot be empty.")
            RETURN .F.
        ENDIF 
        IF This.UnitsInStock < This.ReorderLevel
            This.AddToReorderList()
        ENDIF 
    ENDPROC

    HIDDEN PROCEDURE AddToReorderList
    ENDPROC 

ENDDEFINE


This is just basic code. We have added a method named OnSave that is fired by the Persistence Engine when the instance is going to be saved (Inserted/Updated) into the database. As any other persistence event, the method receives as a parameter a pointer to the current Session object, that we can use to access the whole set of functions and services exposed by this object. In the example, we check for a not empty value present in the Code property. Otherwise, we use the session’s Errors structure to log a message and then return a False value. If the OnSave event returns a False value, the complete Session.SaveObject persistence service is cancelled and the underlying database transaction is rolled back. We also test to know if the Product stock has dropped below the reorder value. In such case, we fire an internal method (hidden) that adds the product to a list of products to be re-ordered (we do not include that code her just to keep this basic sample clear).


Let us test our classes!

- Close your Classes.PRG saving your changes. And try to save an empty Product instance:
SET PROCEDURE TO FDO, Classes ADDITIVE
oServer=CREATEOBJECT("fdoServer")
oSession=oServer.NewSession("Tastrade.FDO")
oPrd=CREATEOBJECT("product")
oPrd.ProductName="Tropical Soda"
? oSession.SaveObject(oPrd) && .F.
? oSession.Errors.ToString() && Product Code cannot be empty.
oPrd.Code=2
? oSession.SaveObject(oPrd) && .T.

When we tried to save the Product instance, FoxDataObjects fired the OnSave method on the product class and got a false value as return value. Therefore, it cancelled the operation and rolled back the transaction.

Working with class inheritance
Following with the Tastrade sample, and to show how a complete object orientation help us with the application design, we will create a Person class with an Employee subclass and Customer and Supplier classes will reference to a Person instance as a contact.

- Clear your objects instance by typing this in the Command window:

CLEAR ALL

- Open your Classes.PRG file to edit:

MODIFY COMMAND classes

- Add the following classes definitions:

DEFINE CLASS Person AS Container
    FirstName = ""
    LastName = ""
    Title = ""
    Gender = 0
    BirthDate = {}
ENDDEFINE

DEFINE CLASS Employee as Person
    EmployeeID = 0
    Title = ""
    HireDate = {}
    ADD OBJECT Address AS Address 
    Group = 0
    SalesRegion = ""
    Photo = ""
    Notes = ""
ENDDEFINE 

DEFINE CLASS Supplier AS Container
    SupplierID = 0
    CompanyName = ""
    ADD OBJECT Contact AS Person
    ADD OBJECT Address AS Address 
ENDDEFINE

DEFINE CLASS Customer AS Container
    CustomerID = 0
    CompanyName = ""
    ADD OBJECT Contact AS Person
    ADD OBJECT Address AS Address 
    MaxOrderAmt = 0.0000
    MinOrderAmt = 0.0000
    Discount = 0.00
    SalesRegion = ""
ENDDEFINE

- And Add the Phone and Fax properties to the existing Address class definition:

DEFINE CLASS Address AS Custom
    Street = ""
    City = ""
    Region = ""
    PostalCode = ""
    Country = ""
    Phone = ""
    Fax = ""
ENDDEFINE

- Close your Classes.PRG file saving the changes.
- Open the Schema Manager tool
- Open the Tastrade.FDO mapping file
- Now select Parse Class Library… from the Tools menu and select your Classes.PRG file.

Your changes are documented and the Schema Manager shows new and modified object-model items in Red and Blue respectively.



- Click to expand the Person class node.

You can see how the Employee class is drawn as a child node because of the inheritance relationship. The Person class has been mapped into a Person table and, by default, the Employee class has been mapped into the same Person table because the default option for Class Inheritance Strategy was set to Flat (Horizontal) when we parsed the library file.

- Click on the Employee class node and then click on the Mapping tab on the right panel.

You will see that the Inheritance Strategy option is set to Flat Mapping (remember you can change the default option used by FDO at the Main Schema node options). With a Flat Mapping Inheritance Strategy, all of the properties in the Employee class are mapped as columns added to the Person table. Normally it is the most used option because keeps all data on the same table, thus to retrieve an Employee instance, FDO needs to select just one record from the database. However, in this case, the Person class will be extensively used by Customer and Supplier entities. In practice, the Person table will contain a lot of records that belong to Person instances and a few records that belong to Employee instances. It is just right but if you are concerned about wasted space in table columns used just by a few records, you can instruct FDO to use a Vertical Inheritance Strategy for the Employee class. Doing so, the Employee subclass will be mapped to its own Employee table with all of its properties as table columns. This way, the person table will contain columns for the Person class attributes only. We optimized database space (normalization) but now an Employee instance uses two records in the database. Anyway, all this work is done by FoxDataObjects at C++ speed so: do not worry. Before doing any change, look at the Relational Model nodes and you will see there is a Person table but not an Employee table.

- Now, change the Inheritance Strategy value to Vertical in the Mapping tab on the Employee class options form.

The Relational Model is refreshed. You will find an Employee table as shown in the next figure:



As you can see in the Object Model, FoxDataObjects mapped all the members automatically.

- Navigate to the Address member in the Customer class and click to select the node.
- Click on the Mappings tab in the right side panel.

FoxDataObjects mapped the contained object to a column in the Customer table that wild hold the Object_ID for the address instance contained in the Customer instance; you can rename the column but not the data type because it is entirely managed by FDO.

- Click on the Relationship tab in the right side panel.

FoxDataObjects set the Reference Class as Address, also note the Ordinality as been set as Mandatory, the Ownership flag has been set, and both options has been disabled. It is because single references implemented as contained objects always represent an owned relationship that cannot be null and when the container object is removed, all of its contained objects are deleted. Since FoxDataObjects deduced all the mappings, there are just a few things to tweak. We will change the column data type used to store an Employee picture from Varchar to BLOB.

- Click to expand the Object Model node
- Click to expand the Person class node
- Click to expand the Employee class node
- Click to select the Photo member node
- Select the Mapping tab in the right panel
- Select BLOB from the column Data Type dropdown list

We are just done with the mappings.

- Close the Schema Manager and ensure the changes are saved.

Let us test our classes!

SET PROCEDURE TO FDO, Classes ADDITIVE
oServer=CREATEOBJECT("fdoServer")
oSession=oServer.NewSession("Tastrade.FDO")
oEmp.FirstName="Steven"
oEmp.LastName="Buchanan"
oEmp.Title="Sales Manager"
oEmp.BirthDate={03/04/1955}
oEmp.HireDate={02/04/2002}
oEmp.Address.Street="14 Garrett Hill"
oEmp.Address.City="London"
oEmp.Address.PostalCode="SW1 8JR"
oEmp.Address.Country="UK"
oEmp.Address.Phone="(71) 555-4848 "
?oSession.SaveObject(oEmp) && .T.


We created an Employee instance and filled some of its properties. We also filled some of the Address object contained in the Employee instance. When FoxDataObjects saved the Employee instance, it inserted three records: one into the Person table, one into the Employee table and one into the Address table. Browse your tables and you will see those records.

Do you envisage how powerful it is becoming? You work into your object model and the relational persistence problem is managed by FoxDataObjects. Anyway, you can still access your database records as always, but we will see that in future articles.

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: