Вы находитесь на странице: 1из 8

Oracle Objects vs Caché Objects

Account Julian @ Thales

I’m very interested in the object features of different database systems. This is an overloaded
statement, as objects can mean several things, including:

- .NET projection of the object-oriented features of the database

- direct access via COM/ActiveX “objects” to the database

- ORM-mappings such as ADO or LINQ

Both Oracle and Caché support these features. I have talked about the first before, so now let me have a
look at the second (a more detailed look actually, since a recent post is discussing Caché connectivity via
ActiveX). This is Windows-specific, I could take a look at CORBA (is it still around? Seems so 1996)/etc
perhaps… if I have the time.

For Oracle:

The samples in the Oracle® Objects for OLE Developer's Guide are for VB/Excel/ASP, but there is no
reason why they cannot be used in C#. In a C# project, add a reference to OracleInProcServer, which will
come in as an Interop (C# as .NET, OracleInProcServer as COM). A side effect of the interop is that the
results have to be cast to their respective types (… as OraDynaSet).
“Translating” the code from the OLE Developer's Guide to C#:
OraServer orclSvr;
OraSession orclSession;
OraDatabase orclDb;
OraDynaset orclDynaset;
OraField orclField;

orclSession = new OracleInProcServer.OraSessionClassClass();


Console.WriteLine("Session: {0}", orclSession.Name);
orclDb = orclSession.get_OpenDatabase("localhost",
@"user/password", 0) as OraDatabase;
// even if the db name is XE in V$DATABASE

orclSvr = new OraServerClassClass(); // in doc: OraServerClass


orclSvr.Open("localhost");

if (orclDb.ConnectionOK == true)
Console.WriteLine("Connected: db-{0} server-{1}",
orclDb.DatabaseName, orclSvr.Name);
else
{
Console.WriteLine("Not connected.");
return;
}

object dummy = new object();

orclDynaset = orclDb.get_CreateDynaset("select * from


kv244.person", 0, ref dummy) as OraDynaset;

orclDynaset.MoveFirst();

// if inserting from the SQL console, need to issue a COMMIT


while (orclDynaset.EOF == false)
{
orclField = orclDynaset["person_name"] as OraField;
Console.WriteLine("Field: value-{0} maxSize-{1} name-{2}",
orclField.Value, orclField.OraMaxSize, orclField.Name);
orclDynaset.MoveNext();
}

On the database side it results in:


So the original SQL from the dynaset is translated into database-aware SQL.

OraDynaset encapsulates a client-side scrollable/updateable cursor. It Cachés the results locally; it


does not lock data unless an Edit operation is specified. To address a specific record in the Dynaset you
have to use the index property: (Dynaset[“column_name”] as OraField).Value (or use
the numeric index, faster I think).

Update (with bound parameters):


orclStmt = orclDb.get_CreateSql("update kv244.person set
person_name = :pname where person_name = :ename", 0) as OraSqlStmt;
// the equivalent of OraDb.CreateSql in VB

OraParameters pars = orclDb.Parameters as OraParameters;


pars.Add("pname", "samitivej" as object, ORAPARM_INPUT,
ORADB_TEXT, "");
pars.Add("ename", "bumrungrad" as object, ORAPARM_INPUT,
ORADB_TEXT, "");
orclStmt.Refresh();

For Caché:
The other ways of connecting to the database from a Windows client include ADO (using the Caché ADO
provider) and the proxy classes (stubs generated using the Caché dev environment), whose architecture
is:

Not surprisingly, the ActiveX toolkit is very similar to that of Oracle, including several front-end
databound controls and a form wizard.

However, Caché has a much more object oriented character than Oracle (inside the database itself), so it
is interesting to see how the Caché objects map to the application objects – in Oracle’s case it is mostly
about ORM, where the relational structures map to some object structures.
The DLL that has to be referenced is CachéActiveX.dll. There is another one, CachéObject.dll, but it is an
older/slower interface.

As in the case of the Oracle documentation, to use the samples in the Caché documentation (Language
Bindings] >  [Using ActiveX with Caché]) a translation is necessary if you use C#:
Dim factory As CachéActiveX.Factory
Set factory = CreateObject("CachéActiveX.Factory")
Becomes:
CachéActiveX.FactoryClass CachéFactory = new FactoryClass();
CachéFactory.Connect();

(I don’t understand why both Oracle and Caché documentation prefer the old style object
instantiation?)

Once this is done, here is where Caché’s object features become really interesting: any objects (i.e.,
tables!) you have created there become immediately available to the application; unfortunately, as late-
bound objects: while JS and older VB as weak typed languages let you access any properties/methods of
any objects, and fail in case these are missing, in C# you cannot reference an undefined method.

Also, the ActiveX library for Caché doesn’t expose a generic Caché object on which to Invoke a method
(e.g., in .NET:
myCachéObject = Type.Type(“CachéObject”);
MyMethodInfo my = myCachéObject.GetMethod(“CachéMethod”);
my.Invoke(myCachéObject);

At least, something like: CachéObjectInvoke(OID, objectName) would have been nice, but it
does not appear to be available.)
So it seems that C# with ActiveX is not an option for Caché, so we have to use a component of the
native .NET Caché provider: Intersystems.Data.CachéTypes.

There are several ways to access the objects in the database:


- use the provided tools to generate the stub classes for you, and include them in the source code
- use the tools to generate the DLL’s for you, reference them manually, and include them in the
compiled code distribution
- (the above can be done either programmatically, as I am discussing below, or using separate
tools provided by the Caché environment)
- But the coolest way would be to reference the compiled DLL dynamically and invoke the Caché
objects dynamically as well, through late binding – i.e., MyCacheObject.Invoke(“Method”,
“Parameter”)

The reason this interests me is that ADO.NET, for example, brings the database objects to the middle
layer; the methods I am discussing here completely obscure the database and project the database
objects (which exist inside Cache as object themselves) in the runtime environment.

To create the object programmatically:

System.Collections.ArrayList classes = new


System.Collections.ArrayList();
classes.Add("CM.StrmClass");
cconn.GenAssembly(@"D:\ostrm.dll", null, classlist, options,
errors);

The new object is fully inspectable using reflection, and it has both the methods belonging to the base
Caché object (in SysLibrary) and those specific to the user definition (in CM):

System.Reflection.Assembly oStrmAssembly =
System.Reflection.Assembly.LoadFile(@"D:\ostrm.dll");
object o = oStrmAssembly.CreateInstance("CM.StrmClass");

Type cfact2 = o.GetType();

Console.WriteLine("CM.StrmClass by .NET proxy:");


Console.WriteLine("Type: {0}", cfact2.Name);
foreach (System.Reflection.MethodInfo m2 in cfact2.GetMethods())
{
Console.WriteLine(" - Method: {0}", m2.Name);
}

Yields:
Here is how to invoke a Cache object member function (in this case, a static function) from .NET:

object s = cfact2.InvokeMember("OpenId", BindingFlags.Public |


BindingFlags.Instance |
BindingFlags.Static |BindingFlags.IgnoreCase |
BindingFlags.InvokeMethod,
null, o, new Object[]{cconn, "1"});

s is a StrmClass object, the result of an OpenId operation. Notice the way the parameters are passed
({cconn, “1”}, which are defined in the signature of the OpenId method as it is exposed to .NET; getting
the parameters wrong in the Invoke will result in a “Member not found” error).

If you were to include the file (either the source or the DLL), the same operation would be:

CM.StrmClass c1 = new CM.StrmClass(cconn);


CM.StrmClass c1 = CM.StrmClass.OpenId(cconn, "1");
Console.WriteLine("Loaded patient name: {0}", c1.PatientName);
c1.PatientName = "Zoe Sam";
c1.Save();
c1.Close();

But as I said, I prefer the dynamic invocation. Using the Cache-provided reflection (via CacheObject)
should be similar; however, the documentation is sparse, so the following method invoke (RunMethodS)
will fail as I am not initializing the StrmClass properly as the documentation on how to do that is
insufficient. It is a step in the right direction though and some Google time should provide the answers.

InterSystems.Data.CacheTypes.CacheObject oStrmClass = new


InterSystems.Data.CacheTypes.CacheObject(cconn, "CM.StrmClass");

InterSystems.Data.CacheTypes.CacheMethodSignature sig = new


InterSystems.Data.CacheTypes.CacheMethodSignature();
InterSystems.Data.CacheTypes.CacheStringArgument oid = new
InterSystems.Data.CacheTypes.CacheStringArgument("Zoe Sam");
sig.Arguments.Add(oid);

oStrmClass.SetProperty("PatientName", sig); // works

oStrmClass.RunMethodS("Save", null); // fails

Вам также может понравиться