Eiffel for Visual FoxPro Programmers: It's about the Data!

by Larry Rix (modified: 2011 Jan 10)

The heart and soul of Visual FoxPro is a decent database engine coupled with a capable attempt at an Object Oriented language. Any FoxPro engineer will quickly point out this marriage as a key strength of FoxPro. It has been a tremendous selling point that I have used personally with clients again and again over the last twenty years. So, one of the first and most interesting points to explore is: What does it mean to connect to the data as a relative comparison between FoxPro and Eiffel.

Let's connect to a database in Visual FoxPro. We'll assume the database is already created. We'll further presume our example is a simple list of names (e.g. first name, last name, birth date, sex and so on). Our example will make the data connection first and then take our data, create a window and place the data into a grid for our users to see. We'll begin with the connection and gathering of the data first.

USE DATABASE my_database.dbc SHARED NOUPDATE USE person.dbf IN 0 SHARED

This is the simplest example of VFP code to open a database and then open one table in a "work area" (e.g. "IN 0", meaning the next available work area). In this case we're opening a database named "my_database.dbc" and then a table within that database container (e.g. dbc) called "person.dbf". That's it. Nothing more is needed -- or is it? There are several items to note about the example:

1. There is a presumption the current directory has the database container file.

2. There is a presumption the database container opens successfully without errors.

3. There is a presumption the database table opens without errors.

4. There is a presumption the database table has the correct fields with correct data types.

A more careful and robust FoxPro example might become:

CLOSE DATABASES ALL && Prepare by ensuring all databases and tables are closed CLOSE TABLES ALL SET ASSERTS ON && An assertion technology in VFP that I use like a simple Design-by-Contract LOCAL ; lc_dbc as String, ; lc_dir as String, ; lo_exception as Exception, ; ll_has_first_name as Boolean, ; ll_has_last_name as Boolean LOCAL ARRAY la_fields[1] lc_dbc = "northwind.dbc" lc_dir = "C:\PROGRAM FILES (X86)\MICROSOFT VISUAL FOXPRO 9\SAMPLES\NORTHWIND\" TRY OPEN DATABASE (ADDBS(lc_dir) + lc_dbc) SHARED NOUPDATE USE employees IN 0 SHARED ln_field_count = AFIELDS(la_fields, "employees") FOR ln_field = 1 TO ln_field_count IF INLIST(la_fields[ln_field, 1], "firstname", "lastname") IF la_fields[ln_field, 2] = 'C' ELSE THROW && The expected datatype is Character for first and last name. ENDIF ENDIF ENDFOR CATCH TO lo_exception DO CASE CASE lo_exception.ErrorNo = 1520 && No database is open or set as the current database. ASSERT .f. MESSAGE "check: The DBC did not load properly." CASE lo_exception.ErrorNo = 1 && Result of THROW ASSERT .f. MESSAGE "check: There is a problem with the table field structure." OTHERWISE ASSERT .f. MESSAGE "check: Some unknown error with data load." ENDCASE ENDTRY

While the previous example works in a very safe and simplistic environment, it is not anywhere near "road worthy" for a production system. This is especially true if an engineer includes code in his or her design that attempts to watch for safety issues, including data type safety. Moreover, the example code above completely presumes using the native FoxPro data engine, but what if SQL Server is substituted? At this point, FoxPro begins to look a lot more like C#.NET or even Java in how the data back-end is brought into the FoxPro environment where it can be used.

What does a similar task look like in Eiffel 6.7?

db_manager.set_connection_information ("my_user_name", "my_password", "my_odbc_database") db_manager.establish_connection data_list := db_manager.load_list_with_select ("SELECT * FROM [dbo].[person]", person)

Again, the code snippet above is rather simplistic and does not reveal the entire story. The rest of the story involves the last argument on the "db_manager.load_list_with_select" call. Yet, don't let this simplicity fool you. The code is really NOT simple. The complexity is hidden. The safety is also hidden. Some of the hiding is buried in the classes used and some is hidden in plain view, but your FoxPro skills won't let you see it. Nevertheless -- there are items that ought to be plainly obvious to you from simply reading the code -- even with your FoxPro eyes and understanding and this is VERY OK! Read the code above as you see it with your current understanding and simply take it at face value, then digest what it says to you.

There is a thing called a database manager (e.g. "db_manager") that knows about connection information and how to establish a connection based on that information. In this case, we're connecting to an ODBC connection called "my_odbc_database" with a user name and password. We make the connection on the second line. Finally, the last line pulls our data using a SQL SELECT command and places instances of class PERSON into our feature data_list (e.g. data_list: ARRAYED_LIST [PERSON]). Viola -- the task is done!

The person argument refers to a person feature on the same class. There are additional features as well:

feature -- Access: Database data_list: ARRAYED_LIST [PERSON] db_manager: DATABASE_MANAGER [ODBC] person: PERSON

The first two features (e.g. data_list and db_manager) are Eiffel Store library classes and are readily available for you to use. The person feature, however, is another matter. It turns out (in this case), I am using SQL Server Express on my local laptop. Eiffel Store includes a small utility call RM2OOM, which connects to a database, reads a table and then produces an Eiffel class (e.g. PERSON) for me to use to interact with the data (e.g. data_list: ARRAYED_LIST [PERSON]). The term "RM2OOM" stands for Relational Model to Object Oriented Model. The resulting class PERSON has various parts, such as:

feature -- Access firstname: STRING -- Auto-generated. lastname: STRING -- Auto-generated.

and ...

feature -- Settings set_firstname (a_firstname: STRING) -- Set the value of firstname require value_exists: a_firstname /= Void do firstname := a_firstname ensure firstname_set: a_firstname = firstname end set_lastname (a_lastname: STRING) -- Set the value of lastname require value_exists: a_lastname /= Void do lastname := a_lastname ensure lastname_set: a_lastname = lastname end

Bear in mind, however, how this class has been generated by the RM2OOM utility. I did not have to write it (although it can be hand generated -- that is completely up to you!).

As a FoxPro programmer, it is difficult to appreciate how much safety and protection is afforded in the Eiffel code shown above. Mostly, this is true because technologies like Design by Contract, Multiple Inheritance and Generic classes (e.g. ARRAYED_LIST [PERSON]) are completely foreign to the VFP engineer. I will explore these topics together with you as a VFP engineer in blog entries to come. For the moment, it is enough to become aware that such technologies exist, even if you cannot yet understand how they impact and improve the quality of your code. We'll come to it. :-)

The bottom line is how, if I use the RM2OOM tool to generate the PERSON class, the only code left for me to write is:

db_manager.set_connection_information ("my_user_name", "my_password", "my_odbc_database") db_manager.establish_connection data_list := db_manager.load_list_with_select ("SELECT * FROM [dbo].[person]", person)

I set the connection information, make the connection and use a SQL SELECT command to load my data list with instances of PERSON. While we could stop here, it is also interesting to point out how the references to PERSON in data_list are NOT direct manipulations of records (rows) and fields (columns) on data elements or items. There is no BROWSE window to access the data. There is immediate separation of the data and business tier as the data_list feature contains instances of class PERSON, which were loaded and constructed from the person table in the SQL Server Express database. Said another way -- I am dealing with objects: First-class citizens of an Object Oriented world in memory and not quasi-data elements living in some no-man's land somewhere between the data and object memory. Moreover, the instances of PERSON can be extended with all the accoutrements of a class, including command and query routines as well as attributes.

Another excellent item to point to about this distinction between data-element and class-object-instance is how polymorphism and other inheritance technologies apply. This means that I can create descendents of PERSON and apply them generically to the data_list, freeing the person table in the database from a strong coupling to the PERSON class in Eiffel. My PERSON class might descend to an EMPLOYEE class and my data_list then becomes:

data_list: ARRAYED_LIST [EMPLOYEE]

Working with data, databases and data connections in Eiffel is certainly based on a different way of thinking than how it is represented in FoxPro. The examples given above are a good starting place, but they are by no means the only ones, means, methods or ways (in the Eiffel world). Here is a key difference to note for a FoxPro person between FoxPro and Eiffel: Eiffel lets you see all the way to the bottom of the pool -- LITERALLY!

When I first started building the Eiffel example above, I did NOT start with the example above. I started much closer to the database (from an Eiffel perspective). Let me show you. Here is the code I started with: note description : "afug_simple application root class" date : "$Date$" revision : "$Revision$" class APPLICATION inherit ARGUMENTS DATABASE_APPL [ODBC] create make feature {NONE} -- Initialization make -- Run application. do set_data_source("afug_simple") login ("lrix","") set_base create session_control.make session_control.connect if session_control.is_connected then create repository.make ("model") -- VFP: OPEN DATABASE [FileName] repository.change_name("person") -- VFP: USE [TableName] repository.load if repository.is_ok and then repository.is_connected then create selection.make create person.make -- VFP: Equivalent of a record selection.object_convert (person) create person_list.make (selection, person) -- VFP: Equivalent of a BROWSE cursor selection.set_action (person_list) selection.set_query ("SELECT * FROM [dbo].[person]") selection.execute_query -- VFP: SQLEXEC(nStatementHandle [, cSQLCommand [, cCursorName[, aCountInfo]]]) selection.load_result if selection.is_ok then across person_list.list as ic_list loop print (ic_list.item.out + "%N") end else print ("Selection is not ok%N") end else print ("Repository is not connected or not ok%N") end else -- Something went wrong, and the connection failed session_control.raise_error; io.putstring("Can't connect to the database.%N") end end feature -- Access app: EV_APPLICATION main_window: MAIN_WINDOW repository: DB_REPOSITORY session_control: DB_CONTROL selection: DB_SELECTION person_list: DB_ACTION [PERSON] person: PERSON end

The code above does PRECISELY the same thing as the few lines of example code. You can still see some of the precise same features (e.g. person_list instead of data_list and person: PERSON). Otherwise, the few lines of code in my starting example are greatly expanded. Essentially, I hand-coded for myself what is in the DATABASE_MANAGER class already. Note that I did not do this on purpose. When I wrote the above code, I did so because I was unaware of the DATABASE_MANAGER class. Once I became aware of another way, I used it. The nice part is how I have an example to show you how you can code much more closely to the database than what was presented and certainly what is available to you in FoxPro.

Eiffel affords you the capacity to write code all the way down to the level of C (e.g. Externals) if you need to. This is why you can use Eiffel for just about anything. You can write COM. You can write device drivers. You can write for embedded systems (e.g. micro-controllers, etc). The landscape is limited solely by your own imagination and technical prowess.

FoxPro is an Ok box to be locked up in, but it is -- still -- a box. You're living within the limits placed upon you by the good folks on the FoxPro team via the FoxPro run-time. You're limited to the commands, options and choices of that system. Moreover, you're limited to a single-threaded (e.g. synchronous) 32-bit, interpreted p-code system that, while powerful from a certain point of view, is highly limited and not YOURS to change as you like and will.

Eiffel on the other hand -- is YOURS! Yes, you get base libraries, but you get to engineer, design and code from Chips-to-Systems and every step in between. You get to live where you like. You can stay at a high level and never leave the comfort of using high-end library classes and their routines. Yet, if you want or circumstances dictate, you can express yourself all the way to the core parts of your PC as required or wanted. It bears repeating -- Eiffel IS YOURS!

Have FUN! :-)