Rick Strahl's Weblog
Rick Strahl's FoxPro and Web Connection Weblog
White Papers | Products | Message Board | News |

Business Objects vs. direct Database Data Access in the Web Control Framework


Tuesday, July 25, 2006, 5:20:00 PM

I’ve heard a lot of questions from people on the forum, asking if they can use database tables directly to do their databinding to controls. All the samples I’ve published with the Web Control Framework uses business objects to load up data, so it may appear that business object are required.

 

You can absolutely bind to database tables directly – the Web Control Framework is very flexible both in its list binding and controlsource databinding of individual control properties. In fact, it works pretty much the way it works in FoxPro desktop forms.

 

However, binding data to tables is actually more complicated than using objects, because of the stateless nature of the Web and how data needs to be saved.

 

Assume that you have a few textboxes on the page that are bound to data from a cursor result from SQL statement:

 

<ww:wwWebTextBox runat="server" id="txtCompany" ControlSource="TQuery.Company" /><br />

<ww:wwWebTextBox runat="server" id="txtCareOf" ControlSource="TQuery.Careof" /><br />

<ww:wwWebButton runat="server" ID="btnSave"  Click="btnSave_Click" Text="Save" />

 

Now if you want to databind these fields all you need to do is this:

 

FUNCTION OnLoad()

 

SELECT * FROM TT_CUST WHERE Company = "West Wind" ;

      INTO CURSOR TQuery

 

IF (!THIS.IsPostBack)

      THIS.DataBind()

ENDIF

 

ENDFUNC

 

So far so good. Binding to the data is easy enough. However, unbinding is a little more tricky in this scenario. Your first impulse might be this:

 

FUNCTION btnSave_Click()

 

*** Bind Data back into the table

this.UnBindData()

 

IF this.BindingErrors.Count > 0

      this.ErrorDisplay.Text = this.BindingErrors.ToHtml()

      RETURN

ENDIF

 

this.ErrorDisplay.ShowMessage("Data saved..." + TQuery.Company)

 

ENDFUNC

 

This however will fail, because what’s happening is that you are in effect unbinding back into a Read-Only cursor, which of course doesn’t work.

 

So if you reconsider this, you might want to think about using a table directly instead. You’d change the ControlSource fields to bind to “TT_CUST.Company” and “TT_CUST.CareOf” and then change the code to:

 

FUNCTION OnLoad()

 

IF !USED("TT_Cust")

   USE TT_Cust IN 0

ENDIF

 

SELECT TT_Cust

LOCATE FOR Company = "West Wind"

IF !FOUND()

      this.ErrorDisplay.ShowError("Invalid record")

      RETURN

ENDIF

 

IF (!THIS.IsPostBack)

      THIS.DataBind()

ENDIF

 

ENDFUNC

 

 

FUNCTION btnSave_Click()

LOCAL lcCustno

 

*** Bind Data back into the table

this.UnBindData()

 

IF this.BindingErrors.Count > 0

      this.ErrorDisplay.Text = this.BindingErrors.ToHtml()

      RETURN

ENDIF

 

this.ErrorDisplay.ShowMessage("Data saved..." + TT_Cust.Company)

 

ENDFUNC

 

Which in fact works and saves the data…

 

Or does it?.................................

 

Quick – what happens if there’s a binding error of any sort?

 

What’s happening in this second example is that we are indeed now binding back directly into the table because the OnLoad() fires, and then the btnSave_Click() is fired. The record is loaded, and the data is unbound into it. If there are no errors all is well.

 

But if there are errors we now have a problem. Some of the fields will have potentially unbound directly into the table. Then a field is encountered that has an error, so it won’t be unbound into the table. Ooops… we have just corrupted that data.

 

This approach of using data directly is very dangerous and not recommended for this very reason. There are ways around this of course. You can enable data buffering on the the table and only commit the data when you actually hit your save point. Or you can use a SCATTER NAME approach where you first load the data into a SCATTER NAME object, then bind to that object and unbind into it as well. Then only when the data is validated do you GATHER NAME back into the actual table.

 

So, while you certainly can use Tables directly for binding, the problem with this approach is that there are lots of ways to get in trouble. There are lots of different ways to load and retrieve data in this fashion, but it’s rather inconsistent. Using Business objects makes the process much more consistent.

 

Let’s look at a business object example:

 

FUNCTION OnLoad()

 

this.oDeveloper = NEWOBJECT("wwDevRegistry","wwDevRegistry.vcx")

 

lnId = VAL(Request.QueryString("Id"))

 

IF lnId = 0 OR !this.oDeveloper.Load(lnId)

   THIS.ErrorDisplay.ShowError ("Invalid Developer")

   RETURN

ENDIF

 

IF !this.IsPostBack

   THIS.DataBind()

ENDIF  

 

 

ENDFUNC

 

FUNCTION btnSubmit_Click()

 

*** Unbind the data back into the control source for this ID

this.UnbindData()

 

IF !this.oDeveloper.Validate()

   this.AddValidationErrorsToBindingErrors(this.oDeveloper.oValidationErrors)

ENDIF

 

IF THIS.BindingErrors.Count > 0

   this.ErrorDisplay.Text = this.BindingErrors.ToHtml()  

   RETURN

ENDIF

 

*** If we get here there are no errors

IF !this.oDeveloper.Save()

   this.ErrorDisplay.Text = this.oDeveloper.cErrorMsg

   RETURN

ENDIF

 

this.ErrorDisplay.ShowMessage("Developer Entry Saved")

ENDFUNC

 

ENDDEFINE

 

This may not seem like it’s any less code and it isn’t (although it does a few extra things like validating the data before saving and displaying the validation errors back in the UI), but what it does do is bring a consistent mechanism for retrieving and setting the data. There no ambiguity about how the data is written and saved because the data is always ‘cached’ first in the object members. The business object handles setting up the object members and saving the object members so there’s no low level code. If you use business objects for edit and save operations like this the code is almost cut and paste for different objects! It’ll look nearly the same no matter how much data you have on a form and using the object in this fashion really means your databinding can be done only one way (well you could still bind to the table to but that would more than a little non-obvious <s>).

 

I don’t want to harp on this too much – I know there are a still plenty of folks out there living in pure DBF data access mode and that’s fine. But regardless of whether you use the Web Control Framework or standard Web Connection or any other Web based mechanism involving VFP that provides any abstraction at all for saving data beyond plugging data directly into INSERT/UPDATE statements, you will always run into this issue of many inconsistent ways of doing the data saving directly against tables. Should you directly update, should use CursorUpdate(), should you use SCATTER NAME? All of that works but that’s a decision that has to be made at development time and it’s not always easy to remember the right choice. I know – I’ve given enough Web Connection demos in the past where I forgot and did something stupid like directly save the data before validating in front of a roomful of people only to find that the data is essentially corrupted in the process.

 

In Web applications in particular, business objects make a lot of sense unless you are writing purely raw procedural code for handling all your Web interaction code.

Posted in:

Feedback for this Weblog Entry