Monthly Archives: October 2006

Using a collection as parameter for a stored procedure

Sometimes you want to select rows where a value is in a specific collection. Here’s an example that show how you can select all the rows in the TEST table with an id of 1, 2 or 3. First we create an SQL type to contain a list of numbers:

CREATE TYPE LIST_NUMBER AS TABLE OF NUMBER(10);
/

Next thing to do is add a custom type and function header to the package specification:

PACKAGE TIMVW.TESTPACKAGE AS

TYPE CRSR_REF IS REF CURSOR;
TYPE ARR_IDS IS TABLE OF TEST.TEST_ID%Type INDEX BY BINARY_INTEGER;

PROCEDURE GET_TESTS
(
 P_IDS        IN        ARR_IDS,
 P_CURSOR  OUT     CRSR_REF
);
END;

And offcourse we have to implement the function in the body:

PROCEDURE GET_TESTS
(
 P_IDS       IN        ARR_IDS,
 P_CURSOR OUT     CRSR_REF
)

AS

V_IDS LIST_NUMBER := LIST_NUMBER();

BEGIN

V_IDS.EXTEND(P_IDS.COUNT);
FOR i IN P_IDS.FIRST .. P_IDS.LAST LOOP
  V_IDS(i) := P_IDS(i);
END LOOP;

OPEN       P_CURSOR FOR
SELECT
              TEST.TEST_ID,
              TEST.NAME,
              TEST.TYPE_CODE
FROM
              TEST
WHERE
              TEST_ID IN (SELECT * FROM TABLE(V_IDS))
ORDER BY
              TEST.TEST_ID ASC;
END;

Now that we have done all this we can consume the function from our client code:

using (OracleConnection conn = new OracleConnection("User Id=u;password=p;Data Source=ORCL"))
{
 conn.Open();

 OracleCommand command = conn.CreateCommand();
 command.CommandType = CommandType.StoredProcedure;

 command.CommandText = "TIMVW.TESTPACKAGE.GET_TESTS";
 command.Parameters.Add("P_IDS", OracleDbType.Int32, new int[] { 1, 2, 3 }, ParameterDirection.Input);
 command.Parameters["P_IDS"].CollectionType = OracleCollectionType.PLSQLAssociativeArray;
 command.Parameters.Add("P_CURSOR", OracleDbType.RefCursor, ParameterDirection.Output);

 OracleDataReader reader = command.ExecuteReader();
 while (reader.Read())
 {
  Console.WriteLine("test id: " + reader.GetDecimal(0));
 }

 Console.Write("{0}Press any key to continue...", Environment.NewLine);
 Console.ReadKey();
}

Searching made easy

Very often i have to write queries that return all the rows where one or more columns match a specific value. If i add for every column the condition ‘P_COLUMN IS NULL OR COLUMN = P_COLUMN’ to the WHERE clause i only have to write one query. Here’s an example of such a query:

PROCEDURE FIND_TESTS
(
 P_ID              IN        TEST.ID%Type,
 P_TITLE         IN        TEST.TITLE%Type,
 P_TYPE_CODE IN        TEST.TYPE_CODE%Type,
 P_CURSOR      OUT     CURSOR REF
)

AS

BEGIN

OPEN
              P_CURSOR FOR
SELECT
              ID,
              TITLE,
              TYPE_CODE
FROM
              TEST
WHERE
              (P_ID IS NULL OR ID = P_ID)
              AND (P_TITLE IS NULL OR TITLE LIKE P_TITLE)
              AND (P_TYPE_CODE IS NULL OR TYPE_CODE LIKE P_TYPE_CODE)
ORDER BY
              ID ASC;
END;

A couple of examples how you can use this query:

using (OracleConnection conn = new OracleConnection("User Id=u;password=p;Data Source=ORCL"))
{
 conn.Open();

 OracleCommand command = conn.CreateCommand();
 command.CommandText = "TIMVW.MYPACKAGE.FIND_TESTS";
 command.CommandType = CommandType.StoredProcedure;

 // select all tests of type_code "book" that have a title that starts with "Myst"
 command.Parameters.Add("P_ID", OracleDbType.Int32, 10, DBNull.Value, ParameterDirection.Input);
 command.Parameters.Add("P_TITLE", OracleDbType.Varchar2, 20, "Myst%", ParameterDirection.Input);
 command.Parameters.Add("P_TYPE_CODE", OracleDbType.Varchar2, 20, "book", ParameterDirection.Input);
 command.Parameters.Add("P_CURSOR", OracleDbType.RefCursor, ParameterDirection.Output);

 // select the test with id 1
 //command.Parameters.Add("P_ID", OracleDbType.Int32, 10, 1, ParameterDirection.Input);
 //command.Parameters.Add("P_TITLE", OracleDbType.Varchar2, 20, DBNull.Value, ParameterDirection.Input);
 //command.Parameters.Add("P_TYPE_CODE", OracleDbType.Varchar2, 20, DBNull.Value, ParameterDirection.Input);
 //command.Parameters.Add("P_CURSOR", OracleDbType.RefCursor, ParameterDirection.Output);

 OracleDataReader reader = command.ExecuteReader();
 while (reader.Read())
 {
  Console.WriteLine("test id: " + reader.GetDecimal(0));
 }

 Console.Write("{0}Press any key to continue...", Environment.NewLine);
 Console.ReadKey();
}

Updating the Internet Options / Lan Settings

A while ago i wrote that i had created two .reg files to update my Internet Options / Lan Settings (Automating the configuration of Internet Options / Lan Settings). Yesterday i build a little Windows Service that automates this completely. With NotifyAddrChange i am notified whenever a change occurs in the table that maps IPv4 addresses to interfaces:

[DllImport("iphlpapi.dll", CharSet = CharSet.Ansi)]
private static extern int NotifyAddrChange(ref IntPtr handle, IntPtr overlapped);

The main loop of the service looks like this:

while (this.isRunning)
{
 IntPtr handle = IntPtr.Zero;
 NotifyAddrChange(ref handle, IntPtr.Zero);
 UpdateRegistry();
}

Whenever i’m connected to the LAN at work i want to use a proxy. Here’s the code that takes care of this:

private static void UpdateRegistry()
{
 RegistryKey registryKey = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Internet Settings", true);
 if (IsInWorkLan())
 {
  registryKey.SetValue("AutoConfigURL", "http://123.456.789.0");
 }
 else
 {
  registryKey.DeleteValue("AutoConfigURL", false);
 }
}

Figuring out whether i’m connected to the LAN at work is pretty simple. As soon as i have an IPAddress that looks like 192.168.X.Y i’m connected. Here’s how i translated this into code:

private static bool IsInWorkLan()
{
 foreach (IPAddress ipAddress in Dns.GetHostAddresses(Dns.GetHostName()))
 {
  if (IsInWorkLan(ipAddress))
  {
   return true;
  }
 }

 return false;
}

private static bool IsInWorkLan(IPAddress ipAddress)
{
 byte[] bytes = ipAddress.GetAddressBytes();
 if ((int)bytes[0] == 192 && (int)bytes[1] == 168)
 {
  return true;
 }

 return false;
}

Screenshot of DataGridViews

The problem with a regular screenshot is that you only get to see a part of the DataGridViews. Here’s an example of a typical form:

image of datagridview with scrollbars

Here is a snippet that makes a screenshot of the complete DataGridViews:

List<bitmap> bitmaps = new List<bitmap>();
Size size = new Size();

for (int i = 1; i < 4; ++i)
{
 // lookup the datagridview
 DataGridView dataGridView = (DataGridView)this.Controls["dataGridView" + i];

 // maximize the datagridview size (choosing between current and preferred)
 Size oldSize = dataGridView.Size;
 Size newSize = dataGridView.PreferredSize;

 if (dataGridView.Size.Width > newSize.Width)
 {
  newSize.Width = dataGridView.Size.Width;
 }

 if (dataGridView.Size.Height > newSize.Height)
 {
  newSize.Height = dataGridView.Size.Height;
 }

 dataGridView.Size = newSize;

 // draw the datagridview into a bitmap
 Bitmap bitmap = new Bitmap(dataGridView.Width, dataGridView.Height);
 dataGridView.DrawToBitmap(bitmap, new Rectangle(0, 0, dataGridView.Width, dataGridView.Height));

 // restore the datagridview to it's original size
 dataGridView.Size = oldSize;

 bitmaps.Add(bitmap);

 // update total bitmap size
 if (newSize.Width > size.Width)
 {
  size.Width = newSize.Width;
 }
 size.Height += newSize.Height;
}

// copy all the bitmaps into one large bitmap
Bitmap bitmapComplete = new Bitmap(size.Width, size.Height);
Graphics g = Graphics.FromImage(bitmapComplete);

int height = 0;
for (int i = 1; i < 4; ++i)
{
 g.DrawImageUnscaled(bitmaps[i - 1], 0, height);
 height += bitmaps[i - 1].Height;
}

// bitmapComplete is ready for use
// eg: pictureBox1.Image = bitmapComplete
// eg: bitmapComplete.Save(@"C:\screenshot.jpg", ImageFormat.Jpeg);



image of generated screenshot

Accessing ConnectionStrings from App.config in a Console Application Project

Earlier today i added a configuration file to my Console Applicaton Project (Add Item -> Application Configuration File). Via ConfigurationSettings i could only access the AppSettings. It took me a while to figure out that i had to add a reference to System.Configuration.dll. Once that was done i could access the ConnectionStrings via ConfigurationManager.

From UnixTime to DateTime and back

Here are a couple of functions that allow you to convert from UnixTime to DateTime and back:

public class Util {
 private static DateTime UnixTime
 {
  get { return new DateTime(1970, 1, 1); }
 }

 public static DateTime FromUnixTime( double unixTime )
 {
  return UnixTime.AddSeconds( unixTime );
 }

 public static double ToUnixTime( DateTime dateTime )
 {
  TimeSpan timeSpan = dateTime - UnixTime;
  return timeSpan.TotalSeconds;
 }
}