Encapsulating Your ASP Page: One Common Method for Database Access to SQL Server and Oracle
[next] |
Encapsulating Your ASP Page: One Common Method for Database Access to SQL Server and Oracle By ( Kathy Pendracky )
A primary definition of good object-oriented design is to promote true encapsulation of objects. Within the confines of an application dedicated for multi-user sites, this requirement becomes much more than a theoretical requirement, and can be the "make-or-break" difference in whether you are able to reach a customer.
When our application reached a point where it was obvious that we needed to support ActiveX Data Objects(ADO) access through ASP to both an ORACLE as well as an SQL Server database, we worked to determine the best method to do this. In case you are not familiar with ADO, here is an article that should bring you up to speed: https://www.microsoft.com/accessdev/articles/movs202.htm. We realized that how we set up an ADO access would probably be the same setup that we employ to provide access to other future databases. All of our ASP pages were initially written for SQL Server database and relied on ADO code processes to access stored procedures. Immediately we recognized a need to provide the same advantages of stored procedures and began our conversion process. We also realized that in a few cases existing stored procedures utilized techniques unavailable through Oracle, such as returning multiple recordsets, and reliance on specific data types to be returned for calculations. Luckily, these problems were atypical. The most formidable challenge was that ADO access to Oracle required the addition of code for defining and appending parameter information to the procedure call on EACH ASP page. Oracle requires a call to the stored procedure in a format somewhat like the following:
cmdStoredProc.CommandText = "{call package_name.procedure_name(?,..,n,
{resultset max#_recs_returned,
output_parm1, output_parm2, etc. })}"
cmdStoredProc.CommandType = adCmdText
cmdStoredProc.Parameters.Append
cmdStoredProc.CreateParameter("input_parm1",adInteger,adParamInput,1,1)
:
cmdStoredProc.Parameters.Append
cmdStoredProc.CreateParameter("input_parmn",adInteger,adParamInput,1,1)
In the latter case, the worse case scenario suggested we might be required to maintain a separate set of pages and/or code if the stored procedures changed. To offset this we opted to define a process that would encapsulate the ASP pages from Database access code to ensure the ease of future accessibility.
To address the multiple problems associated with each, we came up with the following solution.
Use XML to define database requirements
Our final resolution relied on the use of XML (XMLDOM) to define and derive the database characteristics of the current catalog and a common VB solution for inclusion on each page to build the ASP ADO parameter lines if the XML warranted it. Our XML database definition looked something like the following (example shows Oracle definition):
Example 1:
<Database>
<DataType>Oracle</DataType>
<DataSource>my_datasource</DataSource>
<initial_catalog>my_catalog</initial_catalog>
<UserID>my_userID</UserID>
<Password>my_password</Password>
</Database>
Upon initial entry, we relied on the normal server methods of creating the XMLDOM object and then map to the XML path. Connection to the database was then done as the DataType entry value required. The requirement information could then be stored in session variables as needed or, if desired, access to the XML data could even be cached. Our own version was included on each page so that entry did not have to be reliant on a specific access path.
The use of XML fulfilled our basic requirement of a flexible method of defining your database and access requirements.
Building a Universal VB function to Access both Databases
Our next challenge was to determine how to address the problem of having to modify each ASP page for the additional lines that Oracle access required. We certainly did not want to maintain a separate copy of each page with lines dedicated to database access, and we also chose not to opt to include asps for each access type. We ultimately decided to replace all database access code with a single call to a VB function that required the name of the procedure and the output parameters.
Example 2:
myparms="<x>"+xml_para(parm1)+
xml_para(parm2)+xml_para(parm3)+xml_para(parm4) +"</x>"
set rs=common_function("my_procedure_name",myparms)
The VB function had to be able to handle the specifics of building the structure of the call for either SQL Server or Oracle. We decided to build the parameters to the function call as XML as noted in the example above. If the Database Definition XML indicated 'SQL Server', we could set the CommandType = 4 (adCmdStoredProc), and build the CommandText to equate to the full procedure name followed by a Parameters.Refresh to gain accessibility to the parameters from the catalog procedures. Then after loading XML input, we utilized XMLDOM techniques to append the Parameters as passed into the function. The example below gives the general procedural outline. This example can also be modified to utilize caching of parameter information if the programmer elects to do so.
Example 3: Building SQL Server Procedure calls
cmd.CommandType =4
cmd.CommandText="my_procedure_name"
cmd.Parameters.Refresh
bSucc = oXmlDom.loadXML(parmx)
:
'handle false response code
:
Set xy = oXmlDom.selectNodes("//para")
For ct = 1 to cmd.Parameters.count - 1
cmd.Paramters(ct).Value = xy(ct-1).text
Next
Constructing the call to the Oracle database was more formidable since "Parameters.Refresh" is not an option. After examining several alternatives, we opted to build our parameters in VB employing the use of a stored procedure to return vital information from the USER_ARGUMENTS table and incorporating returned data into functions that would construct the remaining code required. Our stored procedure looked something like the following:
Example 4: A General procedure to Retrieve Oracle Procedure parameter information
CREATE OR REPLACE PACKAGE BODY mypackage
AS
PROCEDURE myproc_getArgs
(i_Procedure IN VARCHAR2,
ARG_COL OUT tvarchar30,
ARG_TYPE OUT tvarchar9,
ARG_DEF OUT tvarchar30,
ARG_USER_DEF OUT tvarchar30,
ARG_SEQ OUT tnumber12,
ARG_PKG OUT tvarchar30,
ARG_PROC OUT tvarchar30)
IS
CURSOR argcur is
SELECT ARGUMENT_NAME AS ARG_COL,
IN_OUT AS ARG_TYPE,
DATA_TYPE AS ARG_DEF,
TYPE_SUBNAME AS ARG_USER_DEF,
SEQUENCE AS ARG_SEQ,
PACKAGE_NAME AS ARG_PKG,
OBJECT_NAME AS ARG_PROC
FROM USER_ARGUMENTS
WHERE OBJECT_NAME = UPPER(i_Procedure)AND ARGUMENT_NAME IS NOT NULL
ORDER BY IN_OUT, SEQUENCE;
argct NUMBER DEFAULT 1;
BEGIN
FOR d1 IN argcur LOOP
ARG_COL(argct) := d1.ARG_COL;
ARG_TYPE(argct) := d1.ARG_TYPE;
ARG_DEF(argct) := d1.ARG_DEF;
ARG_USER_DEF(argct) := d1.ARG_USER_DEF;
ARG_SEQ(argct) := d1.ARG_SEQ;
ARG_PKG(argct) := d1.ARG_PKG;
ARG_PROC(argct) := d1.ARG_PROC;
argct := argct+1;
END LOOP;
END myproc_GetArgs;
END mypackage;
/
If your site uses the same procedure names across multiple packages then you would want to modify the above procedure to test for a valid package name as well.
Using the above procedure to build the calls to the procedure was not too difficult after examining and thoroughly determining a valid cross-reference for our release of Oracle between Data types. Our version of logic returned an array of strings to the caller, first traversing the recordset returned through the input parms to collect the number of "?" to be displayed in the call statement for each input parm so that when the output parms are read, they are appended to the same string. This string was built in the first array element. While the input parms were being read, a data type determination was made to pass back to the calling routine as well for each so that the proper format of the append statement could be realized. This information was passed in succeeding elements of the array. The example below is a loosely formulated version of our processing function. It does not process input-output parameter types for simplicity sake.
Contents |
[next] |
Created: April 6, 2001
Revised: April 6, 2001