Chapter 10. Specific DataObject types.

Table of Contents

MultiDBObjects
JoinedDataObject [Since Expresso 5.1]
Introduction
Creating a Joined DataObject
Instantiating and Using a Joined DataObject
Accessing JoinedDataObjects in DBMaint
Performing 'negative' security overrides
Using JoinedDigesterBean for polymorphism
MediaDBObject [Since Expresso 5.1]
Creating a MediaDBObject
Programmatically using the Media DBObject
Saving Time - Using DBMaint
AutoDBObject
Row Secured DBObjects
SynchronizedDataObjects
SynchronizedDataObject example
SecurityDBObjects
Conclusion
Contributors

Note

If you find this EDG documentation helpful please consider DONATING! to keep the doc alive and current.

 Maintainer:Malcolm Wise

Expresso provides the database abstraction layer through the DBObject class and DataObject Interface API. But it provides many more services through extending DBObject in several different subclasses to provide additional functionality. This chapter will cover several of the classes that extend DBObject/DataObject to provide special functionality.

MultiDBObjects

Note

As of Expresso 5.1, it is recommended that new developers work with JoinedDataObject due to its increased flexibility and speed over MultiDBObject. However, MultiDBObject will remain supported for the foreseeable future, since it is used quite a bit in existing applications.

It is often necessary to deal with Join relationships between tables in relational databases - the MultiDBObject exists to handle this. If a DBObject is analogous to a table, a MultiDBObject is analogous to a view (of joined tables).

Many of the same operations available to DBObjects are available to MultiDBObjects - including searchAndRetrieve(), clear(), setField (although with different arguments), etc.

When setting up a MultiDBObject however, you do not use addField - instead, you add entire DBObjects via the addDBObject method.

Also unlike DBObject, MultiDBObject is not an abstract class - you can directly instantiate MultiDBObject objects, rather than having to subclass. For example:

1.  MultiDBObject myMulti = new MultiDBObject();
2.  myMulti.setDataContext(getDataContext());
3.  myMulti.addDBObj(
        "com.jcorporate.expresso.services.dbobj.UserDBObj");

4.  myMulti.addDBObj( "com.jcorporate.expresso.services.dbobj.UserGroup",
                      "group");

5.  myMulti.addDBObj( "com.jcorporate.expresso.services.dbobj.GroupMembers",
                      "members");

6.  setForiegnKey("members", "UserName", "User", "UserName");
7.  setForiegnKey("members", "GroupName", "group", "GroupName");

8.  MultiDBObject oneMulti = null;
9.  myMulti.setField("User", "Username", "Fred");
10. System.out.println(
        "User Fred belongs to the following groups:");

11. for (Enumeration e = myMulti.searchAndRetrieve().elements();
        e.hasMoreElements()); {

12.     oneMulti = (MultiDBObject)e.nextElement();
13.     System.out.println(oneMulti.getField("group", "Descrip");
14. }

Long lines above are broken into multiple lines for clarity, but need not be in your application

Line 1 instantiates the MultiDBObject that we will query.

Line 2 sets the database/context of this MultiDBObject to the db/context of whatever object we are using MultiDBObject from within - e.g. if we are using a Controller object, the "getDataContext()" method accesses the name of the current database/context, making sure the MultiDBObject is operating within the same context.

Lines 3 through 5 specify the DBObjects that this object "contains" and specifies a "short" name for the objects - for example, com.jcorporate.expresso.services.dbobj.UserDBObj is referred to by the short name "user".

Lines 6 and 7 establish the relationships between the 3 objects by specifying a foreign key object and field and a related primary key in another object. Two such relationships exist in our example, the "members" object's Username field must match the "user"'s object Username field, and the "members" object's GroupName field must match the "GroupName" field in the groups object.

Line 8 declares a new MultiDBObject to hold each query result now - just like DBObjects.

Line 9 sets the search criteria - you will note that the object short name, field name and value must be specified - this lets the MultiDBObject know which field in which object is to be set with the given value.

Lines 11 through 13 retrieve the results of the query, just like DBObjects - except you will note that the getField call also takes the "short" name of the object to retrieve the field value from. All 3 DBObjects have their values populated for each result item returned.

You can also use the MultiDBObject by extending it in your own "predefined" MultiDBObject, by implementing the setupFields() method, just like regular DBObjects but instead of calling addField you specify addDBObject calls and setForeignKey calls.

MultiDBObjects are currently read-only. e.g. No Update operations are supported but these will be added shortly.

JoinedDataObject [Since Expresso 5.1]

Introduction

As you might recall near the beginning of the chapter, Expresso is in the midst of abstracting services common to DBObjects into a series of interfaces that provide for generic access to multiple data source types. JoinedDataObject is an example of the results of this effort.

JoinedDataObject is very similar to MultiDBObject except that it was a ground up rewrite for efficiency, as well as its goal is to act to a client programmer just like a standard DataObject. While the convenience services available to DBObjects are often not available, the DataObject API is no less powerful. After all, DBMaint can accept any object that implements the DataObject interface for management. By extension, DBMaint can then manager a JoinedDataObject just like any other DBObject.

JoinedDataObject UML

For those that are proficient in understanding UML, a UML diagram describing the JoinedDataObject class has been included here. The details won't be gone into here, but remember that it simply uses the same interfaces that have been defined earlier in the other UML diagrams. The implementation is just different.

Creating a Joined DataObject

Defining your Join

This guide will not get into the details of database design when it comes to how to create a join on the SQL level. Rest assured, you will not have to figure out the SQL to create the join, you only need to know the theory of what is a join and when to use it.

So let's get into a concrete example of define your join. If you ever dig through the Download controller and its related DataObjects, you will notice that there is a relationship between the DownloadLog table and the DownloadFiles table. Specifically, the DownloadLog contains the File number of the specified table. But what if you want to view the DownloadLog AND the DownloadFile at the same time without multiple browser windows? This is where the JoinedDataObject comes into play.

Instead of creating a new subclass like you do for many other DataObjects, in a JoinedDataObject, you simply write a definition file, a special XML file, to define how the DataObjects are going to be joined together.

So let's show a JoinedDataObject that joins DownloadLog and DownloadFiles tables based upon the file number in Download Log.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dataobject-join PUBLIC "-//Jcorporate Ltd//DTD Expresso DataObject Join 5.1//EN"
      "http://www.jcorporate.com/dtds/jdbc-join_5_1.dtd">
<dataobject-join description="Download Log View">
  <dataobject className="com.jcorporate.expresso.ext.dbobj.DownloadLog"
              alias="DownloadLog"/>
  <dataobject className="com.jcorporate.expresso.ext.dbobj.DownloadFiles"
              alias="DownloadFiles"/>
  <relations>
    <foreign-key local-alias-ref="DownloadLog" local-alias-key="FileNumber"
                 foreign-alias-ref="DownloadFiles" foreign-alias-key="FileNumber" />
  </relations>
</dataobject-join>

Now let's split up the definition file bit-by-bit.

  1. XML Definition and Doctype: True to any XML document, you need to give it the xml processing syntax and a Doctype. The doctype for JoinedDataObjects is database-join It is included within the Expresso code distribution for fast validation during runtime.

  2. <dataobject-join/> This is the 'root' element of the join definition. It has one attribute called 'description', which is the equivalent of setDescription() for DBObjects. Whatever you put here, DBMaint will list as the description for the DataObject. It has no other functional bearing.

  3. <dataobject/> For each table you wish to join with this database object, you need to list it here as dataobject elements. They have two elements. The class name of the DataObject, and an alias for the DataObject, who's purpose will be explained later.

  4. <relations/> Following the listing of all DataObject , the relations section defines how the various DataObject are related. Usually there will be (X - 1) relations, where X is the number of DataObjects used in the Join.

  5. <foreign-key/> Now it's time to define how the tables relate to each other. In it is the 'left' table, which is referred to as the local alias and key here, and the 'right' table, which, here is referred to as the 'foreign' alias and key. As you can see from the example, the DownloadLog field named FileNumber corresponds to the DownloadFiles' field FileNumber.

When you have a complete file, you save it somewhere in your classpath, preferably somewhere that makes sense. An example we'll use is "/org/example/myapp/dbobj"

Now on to the purpose of an alias. As mentioned several times, JoinedDataObjects implement the DataObject API. One of the methods in the DataObject interface is: setFieldValue(String fieldName, Object o). We all know about the field names for various DataObjects, but how do we specify which DataObject to set the field of? MultiDBObject used the method setField(String shortName, String fieldName, String value); Obviously, we can't use that and conform to the API. So JoinedDataObjects expect field names to be specially formatted. They go by the format [alias].[fieldName] . So, for example, to refer to the file number in DownloadLog, you would type in code:

                    setField("DownloadLog.FileNumber",4");
Although JoinedDataObject parses the string, to all external clients, it looks like a single data object, with weird field names.

Instantiating and Using a Joined DataObject

Manual Construction

JoinedDataObjects implement Defineable interface. That means that when you instantiate an Defineable object, you need to call the method setDefinition() before you can use the object. Defineable allows Expresso to uniquely differentiate objects by setting some sort of String value for the object to configure itself with. For AutoDBObject, for example, you set the table name to attach to. JoinedDataObjects take the path (in classpath) to the definition file. So let's use a sample:

import com.jcorporate.expresso.core.dataobjects.jdbc.JoinedDataObject;
import com.jcorporate.expresso.core.dataobjects.Securable;
//...

JoinedDataObject myjoin = new JoinedDataObject();
myjoin.setRequestingUid(Securable.SYSTEM_ACCOUNT);
myjoin.setDataContext("default");
myjoin.setDefinition("/org/example/myapp/dbobj/myjoin.xml");

Factory Method Construction

Expresso 5.1 now has a DataObject factory that considers all the construction order of various dataobjects including Securable and Definable. Instead of the sample above, you can instead write the code:

import com.jcorporate.expresso.core.dataobjects.DataObjectFactory;
import com.jcorporate.expresso.core.dataobjects.jdbc.JoinedDataObject;
import com.jcorporate.expresso.core.dataobjects.Securable;

//...
JoinedDataObject myjoin = (JoinedDataObject)DataObjectFactory
  .createObject(JoinedDataObject.class,
        "default", "/org/example/myapp/dbobj/myjoin.xml",
        Securable.SYSTEM_ACCOUNT);
Sometimes, the single factory method is more convenience than all the methods in the first example.

Typical Usage

Since JoinedDataObject implements DataObject API, the much of the same logic for DBObjects can also be applied to JoinedDataObjects. Examples:

  • Getting a field string value:

    String myValue = myjoin.getDataField("alias1.field1").asString();

  • Setting a field value:

    myjoin.setFieldValue("alias1.field2","abcdefg");

  • Retrieving search results:

    myJoin.setFieldValue("alias1.field3", "ab%");
    myJoin.setFieldValue("alias2.field2", "e%");
    java.util.ArrayList results = myJoin.searchAndRetrieveList("alias1.field1")'
    This version searched for all records where table 1 field 3 starts with 'ab' and table 2 field 2, starts with 'e'. It then orders the results by table 1 field 1.

  • Clearing loaded fields

    myJoin.clear();

Accessing JoinedDataObjects in DBMaint

Since Expresso 5.1, DBMaint has been rewritten to manage any DataObject, whether it is backed by a database or an in-memory hashmap. So, of course, DBMaint can now handle displaying and managing JoinedDataObjects.

Traditionally, you launch DBMaint with the 'dbobj' parameter telling it what class to instantiate and use. But of course, JoinedDataObjects use the 'Defineable' interface to uniquely retrieve their definition. To this end, the 'definition' parameter has been added to DBMaint's capability. The definition parameter is identical to that of what you set earlier when instantiating it by memory. So to list all records of the join, enter (all in one line)

/DBMaint.do?dbobj=com.jcorporate.expresso.core.dataobjects.jdbc.JoinedDataObject&definition=
/org/example/myapp/dbobj/myjoin.xml&state=List

Performing 'negative' security overrides

Normally, JoinedDataObject checks the security of all dbobjects involved and takes the most negative result. In other words, a security check in a JoinedDataObject only returns true if access is granted for all the joined objects.

However, sometimes you flat don't want certain actions available to the client. An example of this might be the 'Add' action. Sometimes adds on a join can be extremely fickle. While JoinedDataobject first adds the rightmost object (the last data object defined in the xml file) and then works to the left (the first data object defined in the xml file), sometimes it's too likely that the end user will muck up the relations. So Expresso provides a feature to allow you to turn off the create/read/update/delete capabilities in an override fashion. To do this, you add a new element to your join definition file called 'permissions'. An example is like so:

<dataobject-join description="Download Log View">
  <dataobject className="com.jcorporate.expresso.ext.dbobj.DownloadLog" alias="DownloadLog"/>
  <dataobject className="com.jcorporate.expresso.ext.dbobj.DownloadFiles" alias="DownloadFiles"/>
  <dataobject className="com.jcorporate.expresso.services.dbobj.MimeTypes" alias="MimeTypes"/>
  <distinct distinctJoin="true"/>
  <permissions add="false" read="true" update="true" delete="false"/>
                 .....

Take a look at the permissions line. In this case, we never want to allow an 'add' or 'delete' through this join. But don't mind allowing updates or reads. In short, we want this particular join to allow easy browsing of the underlying database tables, but not allow the user to seriously modify the underlying data. (In 'update', the user is not allowed to modify the foreign keys in an object, so referential integrity will not be destroyed)

Using JoinedDigesterBean for polymorphism

Question: I'm trying to make a JoinedDataObject within RowSecuredDBObject.searchAndRetrieveList() which will help do security checks w/i DB, rather than as N tests after getting the list. But RowSecuredDBObject is the superclass. The real join is on the subclass. In MultiDBObject, I can use getInstance(), but in the XML for JoinedDataObject, I think I'm out of luck.

Answer: You can provide a populated JoinedDigesterBean instance that is built on the fly that gives you the query capabilities:

JoinedDigesterBean bean = new JoinedDigesterBean();
bean.setName("RowPermissions/" + this.getClass().getName());
bean.addDataObject(this.getClass().getName(),null,"class1");
bean.addDataObject(myClassTwo, null,"class2");
bean.addRelation(....  [API mirrors XML]);

JoinedDataObject jdo = new JoinedDataObject(bean, "RowPermissions/" +
                        this.getClass().getName());

Because it's expensive to initialize metadata, each ad-hoc join will need a distinct name since the metadata is cached. However, after the initial build, then you get:

JoinedDataObject jdo = new JoinedDataObject("RowPermissions/" +
                            this.getClass().getName());

And you will get a JoinedDataObject with the definition previously defined.

[Begin API Dreaming] In reality, what is really needed is a "JoinedDigesterBean Builder" in the form of a query specification object. It would be similar, but less powerful than the Hibernate "Criteria" API. Off the top of my head pseudocode:

QueryObject query = dataObject.buildQuery();

query = query.criteria(example(dbobject1)
            .and(example(dbobject2))
            .and(example(dbobject3)));
query = query.firstRow(30).numRows(60);



query = query.criteria(
            field(DBObject1.class,"fieldName",value)
                .and(DBObject2.class,"fieldName2", value2));


// etc.

//Make implementation of List a 'lazy list' that keeps returning
//values until end of Recordset or until
//query.close() is called
try {
    List result = query.execute();
    for (Iterator i = result.iterator(); i.hasNext();) {
        DataObject eachObject = (
        ....
    }
} finally {
    query.close();
}

Now, I think the only real problem with the above is that I don't think we embed enough relationship metadata within DBObjects to have the dataobject API automatically navigate the relations. So DataObject API metadata may need some steroids. :)

MediaDBObject [Since Expresso 5.1]

A frequent desire when working with a database is to be able to upload and store various media into a database. But when you do this, there are several issues that might arise. How do I store the file name? How do I tell what type of file (image/video/text) the stored BLOB is? How can I provide a way with retrieving the file. To this end, Expresso 5.1 now has the MediaDBObject to assist in your file storage

Creating a MediaDBObject

Creating a MediaDBObject basically two steps: to start with, derive your class from com.jcorporate.expresso.services.dbobj.MediaDBObject. Secondly, instead of calling addField() in your setupFields function, you call addBlobField() instead.

A concrete example:

public void setupFields() throws DBException {
    setTargetTable("TESTMEDIA");
    setDescription("Blob Storage Test");
    setCharset("ISO-8859-1");
    addField("TestKey", "auto-inc", 0, false, "Test Table Key");
    this.addBlobField("Data", "Sample Media Field");
    addKey("TestKey");
}

The crux of the above code is the addBlobField(). In reality all it does is create three additional read-only fields for each file. You do not have to worry about these fields yourself as MediaDBObject uses and sets them automatically for proper BLOB bookkeeping. You CAN reference the fields if you so wish for your own programming needs.

  • Mime Type - What is the MIME type of the file that is stored in this database field

  • File Size - How big is the file that is stored in the database?

  • File Name - What is the name of the file being stored in the database?

Note

You can have more than one BLOB field in a MediaDBObject.

Programmatically using the Media DBObject

You can save your BLOB fields in the following example code:

// Open the file, get its length
File f = new File(importFile);
InputStream is = new FileInputStream(f);
int fileSize = (int) f.length();

// Create an instance of your media dbobject.
MyMediaObject myObj = new MyMediaDBObject(Securable.SYSTEM_ACCOUNT);
myObj.setDataContext("default");

// Add the dbobject with key == 1
myObj.setField("key", 1");
myObj.add();

// Now we save the BLOB fields in a different 'statement'.
myObj.saveBlob("Data", is, importFile, fileSize);

The code to retrieve the BLOB is similar:

// Open the file, get its length
File f = new File(exportFile);
OutputStream os = new FileOutputStream(f);

// Create an instance of your media dbobject.
MyMediaObject myObj = new MyMediaDBObject(Securable.SYSTEM_ACCOUNT);
myObj.setDataContext("default");

// Load the dbobject with key == 1
myObj.setField("key", 1");
myObj.retrieve();

// Now we retrieve the BLOB fields in a different 'statement'.
InputStream is = myObj.retrieveBlob("Data");

//
// The following code is basically the fastest way to read an input
// stream and copy it to an output stream.  Error checking for
// flush and close() have been removed.
//
byte[] buf = new byte[4096]; // 4K buffer
int bytesRead;

while ((bytesRead = is.read(buf)) != -1) {
    os.write(buf, 0, bytesRead);
}

os.flush();
os.close();

Saving Time - Using DBMaint

Ok, so that may not appear to be the easiest way of dealing with it. How about if we look at DBMaint? If you haven't encountered DBMaint already, you soon will. It is saturated throughout Expresso for maintaining database tables. It has search/add/update/delete capabilities and if you take the time to add a custom UI, can save you buckets of coding time for webapp management controllers.

If you use the URL with something like:

http://localhost:8080/DBMaint.do?state=Add&dbobj=org.example.dbobj.MyMediaDBObject
You will notice that there is a 'File Upload' field where you added the BLOB field. You can use this to upload any file to the database and DBMaint will take care of the rest for you. Use the browse button to find a file to upload and hit 'save' record. The file will automatically be uploaded and saved.

A brief note about database compatibility with BLOB datatypes

As the Expresso development team has been exploring BLOB capabilities with various databases, we've been disappointed in performance capabilities with the various databases:

  • HSQLdb - Because all BLOBs are HEX encoded, you have a practical limit of about 200K before you run out of memory.

  • Oracle - We now have code that uses Oracle's native BLOB API. BLOBs should work well here.

  • Most other databases - It appears that most of the time the JDBC driver loads the entire BLOB into memory before handing Expresso the InputStream to read it. This could create large memory issues if BLOBs are heavily used in conjunction with multi-megabyte BLOBs.

    You have been forewarned!

AutoDBObject

The AutoDBObject is the easiest way to get access to your database tables and can be very valuable for prototyping your application. AutoDBObject can populate its fields automatically from the schema information of its target table. This allows an AutoDBObject to be instantiated and used to access a table without any coding at all! The DBMaint servlet has a special parameter to allow an AutoDBObject to be used:

1. /DBMaint.do?dbobj=com.jcorporate.expresso.core.dbobj.AutoDBObject&definition=SCHEMALIST&state=List

This command will list (and enable editing) on the SCHEMALIST table on the current database. No coding at all is required but the user must have access to the AutoDBObject object (AutoDBObject is a SecuredDBObject).

Warning: Giving a user full access to AutoDBObject allows them read/write access to any table in the current database (or at least any tables that the database user specified in the property file has access to). It should be used with great caution, particularly in a production environment.

Row Secured DBObjects

Recent additions to Expresso are the ability to provide DBObjects that secure database tables row-by-row. Row Secured DBObjects are further explained in the EDG Security Chapter

SynchronizedDataObjects

DBObjects and DataObjects themselves are not thread safe. While the words "not thread safe" may inspire much great fear among many readers, it isn't nearly as bad as it sounds. Each request in a Servlet environment is handled by one thread. You don't start the request having it handled by one thread and finish the request having it handled by another. The only time you need something thread safe is if you are sharing the same instance between multiple requests, or possibly something that is being accessed on a remote machine by multiple clients. Either way, a thread-safe DataObject is actually seldomly required.

On very rare occasions, you might want to share a DataObject between threads. While it is more often advisable to perform some sort of manual synchronization to ensure data integrity, Expresso 5.3 now has a wrapper class called com.jcorproate.expresso.core.dataobjects.SynchronizedDataObject that creates a thread-safe variety of DBObjects.

SynchronizedDataObject example

DataObject sharedObject = SynchronizedDataObject.newInstance(mydbobj);

The concept is simple, pass the newInstance method an already instantiated instance of a non-threadsafe DBObject or DataObject, and it will wrap that object in a threadsafe manner. You can use the threadsafe dataobject in the same way you could use any other DataObject. The API is conformed to.

Performance Note

SearchAndRetrieve for a SynchronizedDataObject is not recommended for performance reasons: what happens under the hood is that the data object locks, creates a search and then copies it to the target thread. The result is much overhead in the copy, especially if you have large resultsets. Use with care!

SecurityDBObjects

Take care with the spelling: SecurityDBObject is described here; contrast this with SecuredDBObject, which is much more commonly used, and is discussed in the Security chapter. You probably will never create a subclass of SecurityDBObject yourself--it is a framework tool embedded in the Expresso security system. The idea is to provide a flexible way to redirect authentication classes to another database context in another Expresso application. For example, consider a situation where there are two Expresso applications, and you wanted the second application to rely on the first in order to authenticate users and otherwise supply user information.

The following classes extend SecurityDBObject in Expresso:

  • ControllerSecurity

  • DBObjSecurity

  • DefaultUserInfo

  • GroupMembers

  • GroupNest

  • JobSecurity

  • TmpUser

  • UserGroup

  • UserPreference

  • UserPreferenceDef

SecurityDBObject provides a means to control the DB context (for some or all of the classes listed above) via Setup values. Two Setup values are important for SecurityDBObject:

  • SecurityDB -- Database to use for User/Group Security Info

  • SecurityDBObjs -- Database Objects that should use the 'fixed' context found in the SecurityDB parameter

If these Setup values are empty, SecurityDBObject does nothing special. However, if these 2 Setup values contain meaningful info, subclasses of SecurityDBObject may ignore any DB context supplied (e.g., they can ignore a context like 'default' that comes from the ControllerRequest). The logic is two-fold: the Setup value SecurityDB must be filled, AND the Setup value SecurityDBObjs must contain the fully-qualified class name of all objects (from the list of SecurityDBObject subclasses listed above) that will use the 'fixed' context value in Setup value SecurityDB. That's a bit tricky, so to repeat: even though all the security classes listed above are instances of SecurityDBObject, each one will use the fixed context only if that individual class is also listed in the Setup value, SecurityDBObjs.

This scheme is useful in at least one case: one primary Expresso application supplying User information to a secondary Expresso application. For example, consider a case where we have a primary application 'Primary', and a secondary application 'Secondary'. Primary has standard Expresso Setup values and behavior--the Users are all described in the local database, and the Setup value 'SecurityDB' is empty. Primary knows nothing about Secondary. Secondary has two database contexts defined: "default" and "userInfoDB", where "default" context includes Setup values, but "userInfoDB" does not.

<context name="userInfoDB">
    <description>mysql DB for user info</description>
    <hasSetupTables>false</hasSetupTables>
    ...
     
The Secondary application also has Setup values entered to indicate special context handling for two classes (in this example, these setups are added programmatically within the SecondarySchema constructor):
addSetup(ExpressoSchema.class.getName(), SecurityDBObject.SECURITY_CONTEXT,
    "Database to use for User/Group Security Info",
    "userInfoDB");

addSetup(ExpressoSchema.class.getName(), SecurityDBObject.SECURITY_OBJECTS,
    "Database Objects that use the SecurityDB context",
    DefaultUserInfo.class.getName() + " " + UserGroup.class.getName());

addSetup(ExpressoSchema.class.getName(), DBObject.IS_CHECK_RELATIONAL_INTEGRITY,
    "check relational integrity w/i middleware", "N");
...
As always, this sample may not be representative of your needs, so attempt this only with caution and a single-step debugger handy.

Conclusion

Contributors

The following persons have contributed their time to this chapter:

Note

Was this EDG documentation helpful? Do you wish to express your appreciation for the time expended over years developing the EDG doc? We now accept and appreciate monetary donations. Your support will keep the EDG doc alive and current. Please click the Donate button and enter ANY amount you think the EDG doc is worth. In appreciation of a $35+ donation, we'll give you a subscription service by emailing you notifications of doc updates; and donations $75+ will also receive an Expresso T-shirt. All online donation forms are SSL secured and payment can be made via Credit Card or your Paypal account. Thank you in advance.

Copyright © 2001-2004 Jcorporate Ltd. All rights reserved.