9
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:

Oracle Objects vs Cache Objects

Embed Size (px)

DESCRIPTION

A hands-on description of Oracle and Cache database object usage in .NET.

Citation preview

Page 1: Oracle Objects vs Cache Objects

Oracle Objects vs Caché ObjectsAccount

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).

Page 2: Oracle Objects vs Cache Objects

“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:

Page 3: Oracle Objects vs Cache Objects

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é:

Page 4: Oracle Objects vs Cache Objects

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.

Page 5: Oracle Objects vs Cache Objects

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.FactorySet 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();

Page 6: Oracle Objects vs Cache Objects

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:

Page 7: Oracle Objects vs Cache Objects

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");

Page 8: Oracle Objects vs Cache Objects

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