Friday, November 07, 2008

HotSos Symposium 2009

The HotSos Symposium 2009 is coming up soon. For 2009, the symposium runs from 8 March through to 12 March. This is always one of my favorite conferences of the year, and I will be presenting on "Instrumented Code is Better Code".

The conference, as always, features a fantastic line-up this year. I'm particularly interested in the keynote being delivered by Chris Date. In addition, Jonathan Lewis will be delivering the training day material. If you have the opportunity to attend this conference, I highly recommend it.

Now, I'd better get to work!

Thursday, October 16, 2008

Does ODP.NET Send Data to the Server for Out PL/SQL Parameters?

I was recently discussing (and by that I mean trading emails) with a friend about an interesting behavior observed when using PL/SQL "out" VARCHAR2 variables with ODP.NET - in this case it looks like ODP.NET is sending data from the client and to the server for a parameter that is defined as "out" in a PL/SQL procedure. This seems like strange behavior and I don't have a good answer as to why data would be sent from the client to the server for a parameter declared as an "out" parameter.

In any case here's a quick and dirty sample that illustrates this (on my test system anyway):

PL/SQL Procedure

create or replace procedure ptest (p_vc out varchar2) as
begin
  p_vc := rpad ('a', 31999, 'a');
end;
/

C# Sample Console

using System;
using System.Data;
using System.Data.OracleClient;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;

namespace Miscellaneous {
  class Program {
    static void Main(string[] args) {
      string constr = "User Id=markwill;" +
                      "Password=oracle;" +
                      "Data Source=orademo;" +
                      "Pooling=false;" +
                      "Enlist=false";

      TestODP(constr);

      TestMS(constr);

      Console.WriteLine();
      Console.Write("ENTER to continue...");
      Console.ReadLine();
    }

    static void TestODP(string constr) {
      Oracle.DataAccess.Client.OracleConnection con = new Oracle.DataAccess.Client.OracleConnection(constr);
      con.Open();

      Oracle.DataAccess.Client.OracleCommand cmd = con.CreateCommand();

      cmd.CommandText = "alter session set events '10046 trace name context forever, level 12'";
      cmd.ExecuteNonQuery();

      cmd.CommandText = "ptest";
      cmd.CommandType = CommandType.StoredProcedure;
      cmd.BindByName = true;

      Oracle.DataAccess.Client.OracleParameter p_vc = new Oracle.DataAccess.Client.OracleParameter();
      p_vc.ParameterName = "p_vc";
      p_vc.OracleDbType = OracleDbType.Varchar2;
      p_vc.Size = 32000;
      p_vc.Direction = ParameterDirection.Output;

      cmd.Parameters.Add(p_vc);

      cmd.ExecuteNonQuery();

      Console.WriteLine("p_vc = {0}", p_vc.Value);

      p_vc.Dispose();
      cmd.Dispose();
      con.Dispose();
    }

    static void TestMS(string constr) {
      System.Data.OracleClient.OracleConnection con = new System.Data.OracleClient.OracleConnection(constr);
      con.Open();

      System.Data.OracleClient.OracleCommand cmd = con.CreateCommand();

      cmd.CommandText = "alter session set events '10046 trace name context forever, level 12'";
      cmd.ExecuteNonQuery();

      cmd.CommandText = "ptest";
      cmd.CommandType = CommandType.StoredProcedure;

      System.Data.OracleClient.OracleParameter p_vc = new System.Data.OracleClient.OracleParameter();
      p_vc.ParameterName = "p_vc";
      p_vc.OracleType = OracleType.LongVarChar;
      p_vc.Size = 32000;
      p_vc.Direction = ParameterDirection.Output;

      cmd.Parameters.Add(p_vc);

      cmd.ExecuteNonQuery();

      Console.WriteLine("p_vc = {0}", p_vc.Value);

      cmd.Dispose();
      con.Dispose();
    }
  }
}

This is pretty straight-forward and simply opens a connection, enables Extended SQL Tracing via setting event 10046, creates a parameter for the output value, and calls the stored procedure.

Looking at the respective trace files I find this for the ODP.NET test:

PARSING IN CURSOR #4 len=28 dep=0 uid=88 oct=47 lid=88 tim=9306513611 hv=1721325005 ad='7ff2eca8c38' sqlid='dk08s2dm9kpfd'
Begin ptest(p_vc=>:v0); End;
END OF STMT
PARSE #4:c=0,e=798,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=9306513607
WAIT #4: nam='SQL*Net more data from client' ela= 48 driver id=1413697536 #bytes=44 p3=0 obj#=-1 tim=9306513786
WAIT #4: nam='SQL*Net more data from client' ela= 7 driver id=1413697536 #bytes=24 p3=0 obj#=-1 tim=9306513827
WAIT #4: nam='SQL*Net more data from client' ela= 171 driver id=1413697536 #bytes=4 p3=0 obj#=-1 tim=9306514027
WAIT #4: nam='SQL*Net more data from client' ela= 28 driver id=1413697536 #bytes=49 p3=0 obj#=-1 tim=9306514085
BINDS #4:
Bind#0
  oacdty=01 mxl=32767(32000) mxlc=32000 mal=00 scl=00 pre=00
  oacflg=03 fl2=1000010 frm=01 csi=178 siz=32767 off=0
  kxsbbbfp=1c3d8000  bln=32767  avl=32000  flg=05
  value="Ä6¿¿"...
WAIT #4: nam='SQL*Net message to client' ela= 3 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=9306514373
WAIT #4: nam='SQL*Net more data to client' ela= 49 driver id=1413697536 #bytes=8216 p3=0 obj#=-1 tim=9306514456
WAIT #4: nam='SQL*Net more data to client' ela= 23 driver id=1413697536 #bytes=8192 p3=0 obj#=-1 tim=9306514507
WAIT #4: nam='SQL*Net more data to client' ela= 271 driver id=1413697536 #bytes=8192 p3=0 obj#=-1 tim=9306514805
EXEC #4:c=0,e=1139,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=1,tim=9306514839
XCTEND rlbk=0, rd_only=1
WAIT #4: nam='SQL*Net message from client' ela= 69517 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=9306584449
XCTEND rlbk=0, rd_only=1

As you can see there are several "SQL*Net more data from client" messages. Of course, these are followed by the sending of data to the client.

Now here is the relevant portion of the trace file for the MS provider test:

PARSING IN CURSOR #3 len=30 dep=0 uid=88 oct=47 lid=88 tim=9306615423 hv=1003497284 ad='7ff27721950' sqlid='42zzxzhxx09u4'
begin ptest(p_vc=>:p_vc); end;
END OF STMT
PARSE #3:c=0,e=1507,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=9306615420
BINDS #3:
Bind#0
  oacdty=01 mxl=32767(32000) mxlc=32000 mal=00 scl=00 pre=00
  oacflg=03 fl2=1000010 frm=01 csi=178 siz=32767 off=0
  kxsbbbfp=1c3d8000  bln=32767  avl=00  flg=05
WAIT #3: nam='SQL*Net message to client' ela= 3 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=9306615726
WAIT #3: nam='SQL*Net more data to client' ela= 69 driver id=1413697536 #bytes=8216 p3=0 obj#=-1 tim=9306615822
WAIT #3: nam='SQL*Net more data to client' ela= 18 driver id=1413697536 #bytes=8192 p3=0 obj#=-1 tim=9306615861
WAIT #3: nam='SQL*Net more data to client' ela= 200 driver id=1413697536 #bytes=8192 p3=0 obj#=-1 tim=9306616081
EXEC #3:c=0,e=612,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=1,tim=9306616112
XCTEND rlbk=0, rd_only=1
WAIT #3: nam='SQL*Net message from client' ela= 60351 driver id=1413697536 #bytes=1 p3=0 obj#=-1 tim=9306676628
XCTEND rlbk=0, rd_only=1

In this case there are no messages from the client and in the bind section there is no "value=" element.

An interesting wrinkle is that if I set the parameter direction to "ParameterDirection.InputOutput" using ODP.NET I do not observe the sending of data from the client.

Why, I wonder, does ODP.NET send data to the server for an output parameter? Ideas? Similar behavior observed?

Thursday, September 25, 2008

Oracle Open World 2008 Podcast

I've never really been a prolific blogger and the "interruption" of OOW 2008 has definitely impacted this. However, my podcast with Tom Haunert of Oracle Magazine is now available at the following location:

Oracle Magazine Feature Casts

The title of the podcast is:

Origins of a .NET Developer

Wednesday, August 27, 2008

My Current Development Environment

Whilst I am primarily a DBA during the day, one of the things that I really enjoy about my current engagement is that it has given me the opportunity to dip my toes back in the development pool a bit. I was describing my current development environment to a friend recently and this person was shocked by the details. I realize that in this day and age of code spitters (term coined by Jeff Richter?) and "click through the wizard for instant coding success" sorts of activities that this environment is something of an anachronism.

Without further ado, the o/s:

$ uname -a
HP-UX zzz B.11.31 U ia64 4131905082 unlimited-user license

My "IDE":

$ which vi
/usr/bin/vi

And the compiler:

$ gcc -v
Using built-in specs.
Target: ia64-hp-hpux11.31
Configured with: ../gcc/configure
Thread model: posix
gcc version 4.2.1

Yes, I actually have to do things like create a Makefile in such an environment!

Joking aside though, one of the things that I firmly believe is that instrumented code is better code. One of the characteristics of this kind of development environment, I feel, is that it really encourages strongly instrumenting the code. Yes, in some circumstances I could use the gdb debugger, but building proper instrumentation into the code and offering a simple switch to enable it (yes, it is in the release build) works very nicely.

Another thing that some people might find "weird" about this application is that it is a database application (Oracle of course) that contains absolutely no SQL statements. Really. It is a "thick database" application (Toon Koppelaars term?). That is, there are many statements like "begin package.procedure...; end;" in the code. All the real work is done using PL/SQL packages (and bodies) in the database.

As a .NET developer it is often easy to lean heavily on Visual Studio for debugging, but I think Microsoft also agrees with the "instrumented code is better code" philosophy - check out the System.Diagnostics.Trace class in the Visual Studio Combined Help Collection for more information.

Lastly, if I've misappropriated any of the terms above, apologies!

Friday, August 22, 2008

Oracle OpenWorld 2008 - Just Say Hi!

Just a quick note... if you happen to be coming to Oracle OpenWorld 2008 in San Francisco, I will be helping out in the .NET Hands-On Labs Monday (22 Sep) afternoon. I'll also be at the "OTN ACE Office Hours" on Monday (22 Sep) as well as Thursday (25 Sep). See the link for schedules as well as the great folks who will be available. I've also been known to be around the demo grounds booth as well. Please say "hi" if you get the chance!

Thursday, July 24, 2008

Creating a Windows Service that uses ODP.NET, Oracle Database, a Custom Event Log, and Local Service

The steps necessary to create a Windows Service that accesses Oracle Database via ODP.NET and uses the "NT AUTHORITY\LOCAL SERVICE" low-privileged operating system account are not substantially different from the steps necessary to create a service that does not access Oracle Database. As such, the steps below are substantially similar to the steps in the "Walkthrough: Creating a Windows Service Application in the Component Designer" topic available in the Visual Studio Programmer's Guide for the .NET Framework. You may wish to review this walkthrough in addition to the steps I provide below.

My goal is to provide the steps necessary to create a (very) simplistic Windows Service that uses the "NT AUTHORITY\LOCAL SERVICE" account to "talk" to Oracle Database. This is by no means an all-encompassing treatment of the topic of using a Windows Service in conjunction with ODP.NET, etc. However, it should provide the minimum amount of information to get started down this path.

So, without further ado, let's get started...

The Setup

Because this solution needs an installer, I am not using an Express Edition of Visual Studio. I'm using Visual Studio 2005 with ODP.NET 11.1.6.20 on a Windows XP Professional machine. This machine will also serve as the deployment target for the installer and is hosting an Oracle 11g Release 1 database.

Create the Visual Studio Project

1.  Select File -> New -> Project... from the main menu.

2.  In the New Project dialog expand the Visual C# node under Project types, click the Windows option, select Windows Service in the Templates list, enter OraWinService as the Name, and select an appropriate location (or simply accept the suggested location). The New Project dialog should resemble the following:

OraWinService01

3.  Click OK to create the project.

Add a Reference to the ODP.NET Assembly

1.  Select Project -> Add Reference... from the main menu.

2.  In the Add Reference dialog, scroll down and select Oracle.DataAccess under the Component Name. The dialog should look similar to the following:

OraWinService02

3. Click OK to add the reference.

Set the Service Properties

1.  The Service1.cs file should be displayed in the design view. If it is not, right-click the Service1.cs file in the Solution Explorer and select View Designer.

2.  Ensure the Service1.cs file is selected in the designer by clicking anywhere on the surface except for on the hyperlinks.

3.  In the Properties window for Service1.cs, enter OraWinSvcDemo for the (Name) property. Also enter OraWinSvcDemo for the ServiceName property. Set the CanPauseAndContinue property to True. The AutoLog property should also be set to True, which is the default. When complete, the Properties window should resemble:

OraWinService03

Edit the Main Method

1.  Double-click the Program.cs file in the Solution Explorer to open it in the editor.

2.  Replace the auto-created Main method with the following code:

static void Main() {
  System.ServiceProcess.ServiceBase[] ServicesToRun;
  ServicesToRun = new System.ServiceProcess.ServiceBase[] { new OraWinSvcDemo() };
  System.ServiceProcess.ServiceBase.Run(ServicesToRun);
}

3.  Select File -> Save All from the main menu and then select File -> Close from the main menu to close the Program.cs file.

Add an EventLog Component

1.  With the Service1.cs file displayed in the design view, expand the Components node in the Toolbox and drag and drop an EventLog component onto the design surface. This will create an instance of the EventLog component named eventLog1.

2.  If eventLog1 is not selected, left-click it to select. In the Properties window enter OraWinSvcLog for the Log property and enter OraWinSvcSource for the Source property. The Properties window should now look as follows:

OraWinService04

Define the Service Methods

1.  Right-click the Service1.cs file in the Solution Explorer and select View Code from the context menu to open the file in the editor.

2.  Add the following three lines to the end of the "list of using statements". A timer will be used to periodically trigger an event to write information to the Event Log. The other two lines are boilerplate inclusions for the ODP.NET namespaces.

using System.Timers;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;

3.  At the top of the class definition (but outside of any method definitions) add the following (be sure to adjust the Data Source in the connection string as appropriate for your environment). The user will be created later:

string constr = "User Id=/; Data Source=orademo; Enlist=false";
OracleConnection con = null;
Timer timer1 = null;

At this time the entire Service1.cs file should contain the following:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;
using System.Timers;

using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;

namespace OraWinService {
  public partial class OraWinSvcDemo : ServiceBase {
    string constr = "User Id=/; Data Source=orademo; Enlist=false";
    OracleConnection con = null;
    Timer timer1 = null;

    public OraWinSvcDemo() {
      InitializeComponent();
    }

    protected override void OnStart(string[] args) {
      // TODO: Add code here to start your service.
    }

    protected override void OnStop() {
      // TODO: Add code here to perform any tear-down necessary to stop your service.
    }
  }
}

4.  Change the constructor to the following to create the timer and set the handler function for the timer event:

public OraWinSvcDemo() {
  InitializeComponent();

  timer1 = new Timer();

  timer1.Elapsed += new ElapsedEventHandler(OnTimer);
}

5.  Add the OnTimer method below the constructor to handle the timer event:

private void OnTimer(object source, ElapsedEventArgs e) {
  if (con != null) {
    if (con.State == ConnectionState.Open) {
      OracleCommand cmd = con.CreateCommand();
      cmd.CommandText = "select user, sysdate from dual";
      OracleDataReader dr = cmd.ExecuteReader();
      while (dr.Read()) {
        eventLog1.WriteEntry(String.Format("User: {0}, Date: {1}", dr[0].ToString(), dr[1].ToString()));
      }

      dr.Dispose();
      cmd.Dispose();
      con.Close();
    }
  }
}

6.  Replace the generated OnStart method with the following which establishes a connection to the database and sets the timer properties (interval of 3 seconds and enables the timer):

protected override void OnStart(string[] args) {
  eventLog1.WriteEntry("In OnStart");

  con = new OracleConnection(constr);

  try {
    con.Open();
    eventLog1.WriteEntry("Successfully connected to Oracle!");
  }
  catch (OracleException ex) {
    eventLog1.WriteEntry("OracleException: " + ex.Message);
  }

  timer1.Interval = 3000;
  timer1.Enabled = true;
}

7.  Replace the generated OnStop method with the following code:

protected override void OnStop() {
  eventLog1.WriteEntry("In OnStop");

  timer1.Enabled = false;

  if (con != null) {
    con.Dispose();
  }
}

8.  Add a handler for the OnPause event for the service below the OnStop method:

protected override void OnPause() {
  eventLog1.WriteEntry("In OnPause.");

  timer1.Enabled = false;
}

9.  Add a handler for the OnContinue event for the service below the OnPause method:

protected override void OnContinue() {
  eventLog1.WriteEntry("In OnContinue.");

  timer1.Enabled = true;
}

10.  Add a handler for the shutdown event below the handler for the OnContinue event:

protected override void OnShutdown() {
  eventLog1.WriteEntry("In OnShutdown");

  timer1.Enabled = false;

  if (con != null) {
    con.Dispose();
  }
}

11.  Select File -> Save All from the main menu to save all work and File -> Close to close the Service1.cs file in the editor.

Create Installers for the Components

1.  Service1.cs should in the design view mode within the editor. If it is not, right-click it in the Solution Explorer and select View Designer from the context menu.

2.  Create an installer by right-clicking on the design surface and selecting Add Installer from the context menu:

OraWinService06

3.  After clicking Add Installer, ProjectInstaller.cs will be created and opened in the editor. Click the Service1.cs [Design] tab to switch back to Service1.cs in the editor. Add an installer for the eventLog1 component by right-clicking it and selecting Add Installer from the context menu:

OraWinService05

4.  After adding the eventLog1 installer, left-click the serviceProcessInstaller1 component to select it. Next select LocalService from the drop-down list:

OraWinService07 

5.  Select File -> Save All from the main menu to save all files.

6.  Select Build -> Build OraWinService from the main menu to build the project, then select File -> Close twice to close the ProjectInstaller.cs and Service1.cs files.

Create a Setup Project for the Service

1.  Select File -> Add -> New Project... from the main menu, in the Project types pane expand the Other Project Types node, select Setup and Deployment, select Setup Project in the Templates list, enter OraWinServiceSetup for the Name, and select an appropriate location (or accept the default supplied value). The Add New Project dialog should look as follows:

OraWinService08

2.  Click OK to add the setup project to the solution.

3.  In Solution Explorer, right-click OraWinServiceSetup then select Add -> Project Output... from the context menu. The Add Project Output Group dialog will be displayed:

OraWinService09

4.  Ensure Primary Output is selected and click OK.

5.  In Solution Explorer, right-click OraWinServiceSetup then select View -> Custom Actions from the context menu to open the custom actions in the editor.

6.  In the Custom Action editor, right-click Custom Actions and select Add Custom Action... from the context menu:

OraWinService10

7.  In the Select Item in Project dialog, double-click the Application Folder item, then select Primary output from OraWinService (Active), and click OK to add the output to each of the Install, Commit, Rollback, and Uninstall nodes:

OraWinService11

8.  Select File -> Save All from the main menu and then File -> Close for each opened file in the editor.

9.  Build the setup program by right-clicking OraWinServiceSetup in Solution Explorer and selecting Build from the context menu:

OraWinService12

Install the Windows Service

1.  Using Windows Explorer navigate to the directory where the setup project was built. For example, on my system this is in C:\My Projects\Test\C#\OraWinServiceSetup\Debug directory.

2.  Install the service by right-clicking the OraWinServiceSetup.msi file and selecting Install from the context menu. Step through the installation wizard to complete the installation.

Create the Database User

1.  Connect to the database that the service will use as a DBA user and determine the value of the os_authent_prefix parameter:

SQL> show parameter os_authent_prefix

NAME              TYPE        VALUE
----------------- ----------- -----
os_authent_prefix string

2. As you can see here I do not have a value for this parameter; therefore, when I create the user I do not use a prefix:

SQL> create user "NT AUTHORITY\LOCAL SERVICE" identified externally;

User created.

SQL> grant create session to "NT AUTHORITY\LOCAL SERVICE";

Grant succeeded.

3.  For more information on creating an Operating System authenticated user, see my earlier post on Configuring Windows Authentication.

Running the Service and Viewing Results

1.  Open the Services management console by clicking Start -> Administrative Tools -> Services. You can also right-click the My Computer icon on the desktop and select Manage from the context menu.

2.  Locate the OraWinSvcDemo service in the list of services and click the start button:

OraWinService13

3.  Open the Event Viewer by clicking Start -> Administrative Tools -> Event viewer. Or right-click the My Computer icon on the desktop and select Manage from the context menu (if you have not already done so).

4.  In the Event Viewer, select OraWinSvcLog by left-clicking it. As the service writes entries to the log every 3 seconds while it is executing, you should see several entries after a 10 seconds or so:

OraWinService14

5.  Experiment with the different service states (paused, resume, restart, etc) and verify the entries in the Event Viewer.

Saturday, June 28, 2008

Oracle OpenWorld 2008 San Francisco

It's hard to believe, but Oracle OpenWorld 2008 in San Francisco is coming round fast! I will be attending this year and here's my Oracle Mix profile link (free account required):

oow_mix_2008

I've been really busy with the day job though I plan to have some more technical content in the near future! I've a couple of ideas swimming around that may be interesting as they're based on real questions.

Tuesday, May 27, 2008

Recommended by Joe

A friend of mine at Oracle (that is to say Greg is still at Oracle whilst I am not) pointed out to me that Microsoft's Joe Stagner has Pro .NET Oracle Programming as a recommended book on the Oracle - ASP.NET forum. Currently the recommended books list looks like this (subject to change naturally):

joe_stagner_recommends_me

That got me to thinking a bit. It has been just over 4 years since I started writing that book. (I started the first chapter in March 2004). Certainly a lot has changed since then with several releases of the Oracle Data Provider for .NET, Oracle Database, Visual Studio, the Oracle Developer Tools for Visual Studio .NET, etc. I was just at one of the major booksellers here locally over the weekend and the "computer section" has dwindled immensely. I wonder if it would be worthwhile to update the book? There are several things I would want to change to be sure, but would it be worth it? Do people get their technical information from OTN and MSDN mostly now?

Friday, May 09, 2008

ODTUG Kaleidoscope 2008

It's ODTUG time again! It is a great opportunity to see lots of Oracle folks like Tom Kyte, Sue Harper, Joel Kallman, David Peake, Carl Backstrom, as well as a ton of other great people!

June 15-19, 2008
New Orleans, Louisiana

ODTUG Kaleidoscope 2008 – the annual conference of the Oracle Development Tools User Group, will be held this June 15-19, 2008 in New Orleans, Louisiana. Packed with technical sessions, four individual keynotes, three symposiums, a dedicated Hyperion track crafted by the masters, and a special gathering of Oracle Ace Directors, ODTUG Kaleidoscope offers the most comprehensive in-depth technical conference available for Oracle professionals. Unlike other conferences in this category, ODTUG Kaleidoscope strives to provide real world knowledge by real world technologists and provide a conference experience that will truly help Oracle developers take their role to the next level. For additional information and a complete agenda, visit www.odtugkaleidoscope.com.

Saturday, May 03, 2008

Using Delegates to make a Synchronous Database Call Asynchronously

There are times when a tight coupling exists between user interaction with an application and database calls. That is, a user initiates an action that requires a database call, the database call executes, the call to the database returns, the user responds, and the cycle continues in fairly rapid succession. However, there are also times when a user may initiate an action that requires a lengthy database call. Of course, lengthy is a relative term and would be defined within your own environment. What is lengthy to one may not be lengthy to another. Something of a judgement call on that one.

If a lengthy database call is initiated from an application you may desire the capability to continue to do other work within that application while the database call executes. However, as current production versions of the Oracle Data Provider for .NET (ODP.NET) do not offer BeginXXX and EndXXX methods (such as BeginExecuteNonQuery() for example), you may think offering this functionality in your ODP.NET application is not possible or is difficult. Again, depending on your application specifics, offering such asynchronous behavior may give your application better responsiveness and increase the end-user experience positively.

One method of achieving this goal (calling a synchronous method asynchronously) is by using delegates. If you are not familiar with delegates, I suggest reviewing the Delegates topic in the online version of the C# Programming Guide on Microsoft's MSDN site. In addition to the standard reference information, there is also a tutorial (again located on MSDN) which can be found here. Of course these links are subject to change in the future.

In order to implement this technique you will need two methods (other than the Main method of course). One method will perform the synchronous database call and the other method will be invoked when that call is complete. A delegate is created for the method that performs the database call. This is the key to the technique. By creating a delegate for this synchronous method it becomes possible to invoke it asynchronously via the BeginInvoke() method exposed by the delegate. The delegate keyword actually causes the compiler to expand the declaration into a class which contains a BeginInvoke() method (among others).

The sample code below is a simple implementation of this technique using a console application. In this application a "do nothing" call to the database occurs. This call does nothing more than sleep inside the database for a specified period of time. As noted in the source code comments, the database user must have the "execute" privilege on the dbms_lock PL/SQL package for the code to work. Of course a real application would not normally connect to the database only to sleep for a period of time and return; however, this is a sufficient method for illustrating that the call is asynchronous in relation to the main thread.

The basic flow is as follows:

  • Create the delegate
  • Invoke the delegate to execute asynchronously from the main thread
  • Continue processing in the main thread while the database call is in progress
  • When database call is complete, invoke the OnCallComplete() method
  • Work in the main thread continues for a short period of time
  • Along the way prompt information is displayed and a final prompt is provided

It is necessary to cause the main thread to have a longer duration than the thread performing the database call because if the main thread ended before the database call thread the entire console application would terminate.

When the application is executed you should see output similar to the following:

Asynchronous call in progress.
  Will sleep in database for 10 seconds.
    Doing work in Main thread...
  Enter LongDatabaseCall
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
  Exit LongDatabaseCall
  Enter OnCallComplete
    Perform post-database call actions...
  Exit OnCallComplete
    Doing work in Main thread...
    Doing work in Main thread...
    Doing work in Main thread...
Press ENTER to continue...

As you can see, the basic flow above is represented here and work in the main thread continues after the LongDatabaseCall method has been invoked via the delegate.

Here's the (hopefully) well-commented code to produce the above:

using System;
using System.Threading;
using System.Data;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;

namespace AsynchronousDemo
{
  class Program
  {
    // change as necessary for your environment
    // the user must have execute privilege on dbms_lock
    const string constr = "User Id=hr;" +
                          "Password=hr;" +
                          "Data Source=orademo;" +
                          "Enlist=false";

    // the delegate for the function that makes the db call
    internal delegate void LongCallDelegate(int sleepSeconds);

    static void Main(string[] args)
    {
      // the amount of time the call to the database will sleep
      int sleepSeconds = 10;

      // write marker text to console to indicate beginning of process
      Console.WriteLine("Asynchronous call in progress.");
      Console.WriteLine("  Will sleep in database for {0} seconds.", sleepSeconds.ToString());

      // used to call the function
      LongCallDelegate ldc = new LongCallDelegate(LongDatabaseCall);

      // call BeginInvoke to make call asynchronous
      // the method to invoke when the call has completed is passed
      // as the second parameter
      ldc.BeginInvoke(sleepSeconds, OnCallComplete, ldc);

      // cause main thread to "pause" while the database call is in progress
      // add an arbitrary number (5) to the sleepSeconds to help show that
      // the database call is "independent" of the main thread
      for (int i = 1; i < sleepSeconds + 5; i++)
      {
        Console.WriteLine("    Doing work in Main thread...");
        Thread.Sleep(1000);
      }

      // prevent console from automatically closing as when run from VS in debug mode
      Console.WriteLine("Press ENTER to continue...");
      Console.ReadLine();
    }

    static void LongDatabaseCall(int sleepSeconds)
    {
      // this is the function that makes the database call
      // it will be called from the delegate to make the call
      // asynchronous in relation to the main thread

      // marker text
      Console.WriteLine("  Enter LongDatabaseCall");

      // create and open connection to database
      OracleConnection con = new OracleConnection(constr);
      con.Open();

      // create command object
      // will call dbms_lock to sleep for x seconds in database
      OracleCommand cmd = con.CreateCommand();
      cmd.CommandText = "begin dbms_lock.sleep(:1); end;";

      // parameter for the sleep duration
      OracleParameter p_sleepSeconds = new OracleParameter("1",
                                                           OracleDbType.Decimal,
                                                           sleepSeconds,
                                                           ParameterDirection.Input);

      // add parameter to the collection for the command object
      cmd.Parameters.Add(p_sleepSeconds);

      // make the database call
      cmd.ExecuteNonQuery();

      // clean up
      p_sleepSeconds.Dispose();
      cmd.Dispose();
      con.Dispose();

      // marker text
      Console.WriteLine("  Exit LongDatabaseCall");
    }

    static void OnCallComplete(IAsyncResult ar)
    {
      // marker text
      Console.WriteLine("  Enter OnCallComplete");

      // get the delegate from the IAsyncResult object
      LongCallDelegate ldc = (LongCallDelegate)ar.AsyncState;

      // Must call EndInvoke to pair with BeginInvoke
      ldc.EndInvoke(ar);

      // marker text
      Console.WriteLine("    Perform post-database call actions...");
      Console.WriteLine("  Exit OnCallComplete");
    }
  }
}

Tuesday, April 08, 2008

Generating Test Data

I'm an advocate of testing things - it can be a great way to discover different behaviors and learn how things work (or perhaps why they didn't work the way you thought they might!). When it comes to testing database applications, having some test data is certainly helpful. Ideally, I like to have a fully-loaded production database from which to draw this data into a test environment. Sometimes, however, this is not possible. In cases like these I use the "trick" described below to create my own test data.

When I need to generate test data I frequently call upon a seemingly little-known PL/SQL Package supplied with Oracle Database called "dbms_random". As you may guess this package can be used to generated random data. Rather than explain the package details (they are short and you can read them using the link), I'll just present a quick way to generate some data using SQL*Plus and the dbms_random package.

First, I'll need a table:

create table test_data
(
  id number,
  first_name varchar2(16),
  last_name varchar2(24),
  amount number(6,2),
  purchase_date date
);

As you can see, this table is nothing to write home about, but it does mirror what a real-world table would look like in a lot of cases.

When I am creating test data I generally prefer the data to be reproducible. That is, if I execute the process more than once I like to get the same data each time. To accomplish this with the dbms_random package I call the "seed" procedure and provide an initial seed value:

exec dbms_random.seed(42);

Let's say I wanted to populate this table with 10,000 rows of data. I use the following to do this:

insert into test_data
select
  rownum,
  initcap(dbms_random.string('l',dbms_random.value(2,16))),
  initcap(dbms_random.string('l',dbms_random.value(2,24))),
  round(dbms_random.value(1,1000),2),
  to_date('01-JAN-2008', 'DD-MON-YYYY') + dbms_random.value(-100,100)
from
  (select level from dual connect by level <= 10000);

Starting at the bottom of the SQL text is a neat trick using "dual" and "connect by" to essentially create an "unlimited dual" for generating lots of rows. This trick was (I believe) originally put forth by Mikito Harakiri and I discovered it via an Ask Tom thread.

The remainder of the text is fairly straight-forward in its use of the dbms_random package. I use the "string" function to generate a random, lowercase value (which is subsequently passed to "initcap" to capitalize the first character of each string), the "value" function is used to create a random numeric value (which is passed to "round" to make it look like a purchase amount), and then I use a fixed-date to which I add (possibly a negative value) to create a set of valid dates within a range.

The first 10 rows of this data when selected from the table look like:

ID FIRST_NAME       LAST_NAME                    AMOUNT PURCHASE_DATE
--- ---------------- ------------------------ ---------- --------------------
  1 Oqq              Mxsazbwyx                    521.33 22-MAR-2008 16:49:40
  2 Jjgqrywtxbdn     Fwwbzshhkbqzb                921.47 04-OCT-2007 09:10:00
  3 Zxflhufls        Mstwydowbaogeyyjiles         172.34 20-MAR-2008 10:22:05
  4 Zjjxtyysitsog    Zxrzqeflxgo                  882.16 26-DEC-2007 18:56:44
  5 Kjmuvbrqx        Hfu                          742.61 16-OCT-2007 14:35:27
  6 Oywaibiyp        Angvlehlmeujfdlhdmtt          664.5 29-FEB-2008 12:50:40
  7 Uhwyvla          Nhbwcv                       168.99 27-DEC-2007 22:29:59
  8 Kpdiqafanbvzt    Phjeqwelyugrmahybocwbhvp     813.81 01-MAR-2008 09:15:59
  9 Tvezuvrgnzzqkpq  Pjyygoqx                     880.09 21-NOV-2007 00:42:07
10 Olchylbeft       Nflaxjqfkmkgt                847.71 07-DEC-2007 16:53:23

10 rows selected.

Can this technique always be used? No, probably not. For example, the names are not exactly what you might call "proper" names. However, I do find that this technique can be useful when I need to create some data to perform some testing with. Perhaps it will be helpful to you too if you experiment with it and find the right combination of values to use in your tests.

Sunday, April 06, 2008

Applications and Data == Importance Envy?

I was recently reading an interesting posting (and comments) over on Doug Burns' Blog:

http://oracledoug.com/serendipity/index.php?/archives/1394-Theres-Hope-for-us-all.html

Doug's blog is a great resource and I encourage you to check it out if you have a few spare minutes.

I found this interesting because the "debate" around which is more important -- the application or the data -- raises its head every now and then. Now, my being a DBA in my "day job" may automatically bias me in this context; however, I was a developer before I was a DBA. This, of course, does not mean I am bias-free, but I do hope that I am not so closed-minded that I may see both sides of a discussion.

When I hear this question (which is more important? the application? or the data? OK, so that is three questions, but you understand.) I immediately think of a question I was posed long ago in school - which is more important, the heart or the blood? Of course there is not a perfect correlation between biology and applications and data, but I think that is not an unreasonable analogy. While not exactly easy, it is possible to find other mechanisms to transport the blood, but it is not so easy to replace the blood itself.

Clearly both applications and data are important. However, rather than necessarily declare one to be more important than the other, my inclination is to view them in terms of life span. Often I hear something along the lines of: An application is used to access the data. To me this implies that there may be more than one application or a series of applications over time whereas the data itself is not really viewed as having multiple incarnations. If I may borrow a quote from The Kinks: "Rocks bands will come and rock bands will go, but Rock 'n Roll will go on forever!" (From a live performance of "All Day and All of the Night" as I recall).

Thursday, February 28, 2008

Experimenting with Connection Pooling

Connection pooling can be a great way to increase performance and scalability in your ODP.NET applications; however, it is also a feature that can be confusing to figure out as there are several parameters used in the connection string that control the behavior of the feature. These parameters are all fully documented in the documentation that ships with the data provider. In brief, the primary parameters used to control connection pooling are (see documentation for additional parameters):

  • Pooling - Enables or disables the connection pooling feature
  • Min Pool Size - Specifies the minimum number of connections that can be in the pool
  • Max Pool Size - Specifies the maximum number of connections that can be in the pool
  • Incr Pool Size - Specifies the increment value (the number of connections) to be added if the connection pool needs to grow in size
  • Decr Pool Size - Specifies the maximum number of connections that may be removed from the pool in a single "downsizing" operation
  • Connection Lifetime - Specifies the amount of time that a connection must be active after which the connection will not be returned to the pool (i.e. it will be disposed)
  • Connection Timeout - Specifies the amount of time that the provider will wait for an available connection from the pool

In ODP.NET each connection in the pool is represented by a physical connection to the database. There is a one-to-one relationship. If there are 4 connections in the pool there will be 4 corresponding connections in the database. This is another way of saying that ODP.NET connection pooling does not implement multiplexing (or sharing) of connections. Another important characteristic is that a connection pool is associated with a client process. This means that if you implement connection pooling in an application and there are 8 copies of that application simultaneously running at any given time, there will be 8 connections pools - one for each instance of the application process.

Because a connection pool is associated with a specific instance of an application it can possibly be a bit difficult to test the impact and operation of the various connection string parameters related to connection pooling. I've put together a simple project that uses threads to test connection pooling. This allows the single application process to create multiple connections (and thus multiple entries in the connection pool) to the database.

The sample code invokes a supplied PL/SQL procedure in the DBMS_LOCK package to sleep for a period of time (hard-coded as 30 seconds in the below code). The allows the connection to be "held active" and helps with testing the impact of the Connection Lifetime parameter. In order for this to work, the database user must be granted execution permission on the PL/SQL package. For example (as a DBA user via SQL*Plus):

grant execute on dbms_lock to orademo;

After starting the specified number of threads (and connections) the application holds the connection as active for the determined period of time, and then disposes of the connection object placing it back into the connection pool (depending on how you set the parameters!). In order to monitor the connections in the database, I executed the following query as a DBA user via SQL*Plus:

SELECT   SID,
         SERIAL#,
         USERNAME,
         STATUS,
         to_char(LOGON_TIME, 'DD-MON-YY HH24:MI') LOGON_TIME
FROM     V$SESSION
WHERE    LENGTH(USERNAME) > 1
ORDER BY 3, 4, 1;

The output from the application is as follows:

Thread 1 started...
Thread 2 started...
Thread 3 started...
Thread 4 started...
Thread 5 started...
Thread 6 started...
Thread 7 started...
Thread 8 started...
Thread 1 completing...
Thread 2 completing...
Thread 3 completing...
Thread 4 completing...
Thread 5 completing...
Thread 6 completing...
Thread 7 completing...
Thread 8 completing...

Paused after threads complete.
Monitor connections using tool of choice.
Be sure to wait several minutes (approx. 6) for clean-up to occur.

Press Enter when finished.

It is important to execute the monitoring query while the application is "paused" otherwise the application will terminate and the connection pool will be destroyed.

Here is the output of the monitoring query after the application has reached the "pause point":

SQL> SELECT   SID,
  2           SERIAL#,
  3           USERNAME,
  4           STATUS,
  5           to_char(LOGON_TIME, 'DD-MON-YY HH24:MI') LOGON_TIME
  6  FROM     V$SESSION
  7  WHERE    LENGTH(USERNAME) > 1
  8  ORDER BY 3, 4, 1;

       SID    SERIAL# USERNAME                       STATUS   LOGON_TIME
---------- ---------- ------------------------------ -------- ---------------
       135          2 ORADEMO                        INACTIVE 28-FEB-08 18:22
       136          2 ORADEMO                        INACTIVE 28-FEB-08 18:22
       137          2 ORADEMO                        INACTIVE 28-FEB-08 18:22
       138          4 ORADEMO                        INACTIVE 28-FEB-08 18:22
       139          6 ORADEMO                        INACTIVE 28-FEB-08 18:22
       142          3 ORADEMO                        INACTIVE 28-FEB-08 18:22
       143          3 ORADEMO                        INACTIVE 28-FEB-08 18:22
       164          6 ORADEMO                        INACTIVE 28-FEB-08 18:22
       134          2 SYSTEM                         ACTIVE   28-FEB-08 18:22

9 rows selected.

This shows the 8 connections from the 8 threads (adjustable) in the application and my SQL*Plus connection.

If you simply let the application sit paused for a period of time, the connections will automatically be cleaned up by ODP.NET (again, depending on the parameter values, but the values as provided in the below code allow this to occur). On my system it took approximately 6 minutes for the connection pool to be "cleaned" (i.e. reduced to the minimum of one connection based on the values I used in the connection string).

[ after waiting for about 6 minutes ]

SQL> /

       SID    SERIAL# USERNAME                       STATUS   LOGON_TIME
---------- ---------- ------------------------------ -------- ---------------
       164          6 ORADEMO                        INACTIVE 28-FEB-08 18:22
       134          2 SYSTEM                         ACTIVE   28-FEB-08 18:22

2 rows selected.

This shows the connection pool has been "downsized" to the minimum number (one) I specified.

I encourage you to experiment with the different parameters and values to see how they operate and what impact they have on your system.

The Code

using System;
using System.Threading;
using System.Data;
using Oracle.DataAccess.Client;
using Oracle.DataAccess.Types;

namespace Miscellaneous
{
  class Program
  {
    static void Main(string[] args)
    {
      // change connection string as appropriate and experiment with different values
      const string constr = "User Id=orademo; " +
                            "Password=oracle; " +
                            "Data Source=orademo; " +
                            "Enlist=false;" +
                            "Pooling=true;" +
                            "Min Pool Size=1;" +
                            "Max Pool Size=8;" +
                            "Incr Pool Size=1;" +
                            "Decr Pool Size=8;" +
                            "Connection Lifetime=60;" +
                            "Connection Timeout=5";

      // set to number of threads / connections to use
      const int numberOfThreads = 8;

      // create arrays for class instances and events
      ConnectionThread[] threadArray = new ConnectionThread[numberOfThreads];
      ManualResetEvent[] doneEvents = new ManualResetEvent[numberOfThreads];

      // populate arrays and start threads
      for (int i = 0; i < numberOfThreads; i++)
      {
        // initialize each event object in the array
        doneEvents[i] = new ManualResetEvent(false);
        // create a new instance of the ConnectionThread class
        ConnectionThread ct = new ConnectionThread(i + 1, constr, doneEvents[i]);
        // assign the new instance to array element
        threadArray[i] = ct;
        // Queue the thread for execution and specify the method to execute
        // when thread becomes available from the thread pool
        ThreadPool.QueueUserWorkItem(ct.ThreadPoolCallback);
      }

      // wait until all threads have completed
      WaitHandle.WaitAll(doneEvents);

      // keep application from terminating while monitoring connections in database
      // if the application / process terminates all connections will be removed
      Console.WriteLine();
      Console.WriteLine("Paused after threads complete.");
      Console.WriteLine("Monitor connections using tool of choice.");
      Console.WriteLine("Be sure to wait several minutes (approx. 6) for clean-up to occur.");
      Console.WriteLine();
      Console.Write("Press Enter when finished.");
      Console.ReadLine();
    }
  }

  public class ConnectionThread
  {
    // private class members
    private int _threadNumber;
    private string _constr;
    private ManualResetEvent _doneEvent;

    // parameterized constructor
    public ConnectionThread(int threadNumber, string connectionString, ManualResetEvent doneEvent)
    {
      _threadNumber = threadNumber;
      _constr = connectionString;
      _doneEvent = doneEvent;
    }

    // this will be called when the thread starts
    public void ThreadPoolCallback(Object threadContext)
    {
      Console.WriteLine("Thread {0} started...", _threadNumber);

      // do some database work that holds the connection open
      DoWork();

      Console.WriteLine("Thread {0} completing...", _threadNumber);

      // signal that this thread is done
      _doneEvent.Set();
    }

    public void DoWork()
    {
      // create and open connection
      OracleConnection con = new OracleConnection(_constr);
      con.Open();

      // command to do the database work (simply hold connection open for 30 seconds)
      // NOTE: execute privilege must be granted to user on the dbms_lock package
      OracleCommand cmd = con.CreateCommand();
      cmd.CommandText = "begin dbms_lock.sleep(30); end;";

      // execute the anonymous pl/sql block to does nothing but sleep to hold the connection
      cmd.ExecuteNonQuery();

      // clean up and return connection to pool (depending on connection string settings)
      cmd.Dispose();
      con.Dispose();
    }
  }
}

Thursday, January 31, 2008

VB, OracleCommandBuilder, and What's Wrong With This Code?

Below is some sample code. Create a new VB console application, add a reference to the Oracle.DataAccess.dll assembly, and add Oracle.DataAccess.Client to the Imported namespaces using the properties page for the project. Copy and paste the code into the .vb source file. Do you get any errors? Does it compile? Is there anything wrong with this code?

Module Module1
  Sub Main()
    ' connection string -- change as necessary
    Dim constr As String = "User Id=scott; " & _
                           "Password=tiger; " & _
                           "Data Source=orademo; " & _
                           "Enlist=false; " & _
                           "Pooling=false"

    ' will use below
    Dim ds As New DataSet
    Dim da As OracleDataAdapter
    Dim con As OracleConnection

    ' open connection
    con = New OracleConnection(constr)
    con.Open()

    ' get a data adapter for the emp table
    da = New OracleDataAdapter("select * from emp", con)

    ' get the schema information
    da.FillSchema(ds, SchemaType.Source)

    ' get command builder from data adapter
    Dim cb As New OracleCommandBuilder(da)

    ' set the insert command from the command builder
    da.InsertCommand = cb.GetInsertCommand(True)

    ' simple prompt to keep console from closing when
    ' run from within the Visual Studio environment
    Console.WriteLine("ENTER to continue...")
    Console.ReadLine()
  End Sub
End Module

If the code compiles, when you try to run it, do you get an error? If you get an error, does it indicate: "The DataAdapter.SelectCommand property needs to be initialized."

Hmm. Strange. It looks like the SelectCommand is being initialized right there in the OracleDataAdapter constructor: da = New OracleDataAdapter("select * from emp", con)

A quick check in the Oracle Data Provider for .NET documentation shows that the constructor for OracleDataAdapter can definitely take a string which represents the select command text and a connection object. Yet you may get an error stating that the SelectCommand property needs to be initialized.

If this code compiled fine and yet you get a runtime error, here's a pointer - add the following to the top of the source code file:

' Enable Option Strict checking
Option Strict On

(You can also change this via the project property page on the Compile tab)

Did anything change in the source code? In particular this part of the source code:

' set the insert command from the command builder
da.InsertCommand = cb.GetInsertCommand(True)

If Option Strict is not enabled, which happens to be the default, VB will try to perform an implicit conversion from one data type to another data type with no compile-time warning. However, at runtime the conversion may fail. In addition, if you consult the ODP.NET documentation you will see that there is no OracleCommandBuilder.GetInsertCommand that takes a Boolean. However, OracleCommandBuilder inherits from System.Data.Common.DbCommandBuilder which does have an overloaded GetInsertCommand that takes a Boolean.

So, in the case of the (incorrect) code above, DbCommandBuilder.GetInsertCommand(Boolean) is the method that was to be invoked not the OracleCommandBuilder.GetInsertCommand. This is the cause of the runtime error and explains the complaint about the select command not being initialized. If you try to compile the code with Option Strict enabled you should receive: "Option Strict On disallows implicit conversions from 'System.Data.Common.DbCommand' to 'Oracle.DataAccess.Client.OracleCommand'."

From my perspective it seems like a good idea to always enable Option Strict.