Monday, May 26, 2008
Socket Send and Receive
Socket.Send method
Send method sends data from your buffer to a connected Socket. When you call the Send method it returns number of bytes which were „sent“. But it doesn't mean that the bytes were already received by the other side, it only means that the data were stored in a socket buffer and the socket will be trying to send them. If the socket buffer is full a WouldBlock error occurs. You should wait for a while a try to send the data again.Following method sends size bytes stored in the buffer from the offset position. If the operation lasts more than timeout milliseconds it throws an exception.
public static void Send(Socket socket, byte[] buffer, int offset, int size, int timeout)
{
int startTickCount = Environment.TickCount;
int sent = 0; // how many bytes is already sent
do {
if (Environment.TickCount > startTickCount + timeout)
throw new Exception("Timeout.");
try {
sent += socket.Send(buffer, offset + sent, size - sent, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
// socket buffer is probably full, wait and try again
Thread.Sleep(30);
}
else
throw ex; // any serious error occurr
}
} while (size > sent);
To call the Send method use following code snippet (suppose the static Send method is defined in SocketEx class). TCP/IP socket can be obtained using TcpClient class. Use TcpClient.Client property to get the underlying Socket (this property is public since .NET Framework 2.0).
Socket socket = tcpClient.Client;
string str = "Hello world!";
try
{ // sends the text with timeout 10s
SocketEx.Send(socket, Encoding.UTF8.GetBytes(str), 0, str.Length, 10000);
}
catch (Exception ex) { /* ... */ }
Socket.Receive method
Receive method receives data from a bound Socket to your buffer. The method returns number of received bytes. If the socket buffer is empty a WouldBlock error occurs. You should try to receive the data later.
Following method tries to receive size bytes into the buffer to the offset position. If the operation lasts more than timeout milliseconds it throws an exception.
public static void Receive(Socket socket, byte[] buffer, int offset, int size, int timeout)
{
int startTickCount = Environment.TickCount;
int received = 0; // how many bytes is already received
do {
if (Environment.TickCount > startTickCount + timeout)
throw new Exception("Timeout.");
try {
received += socket.Receive(buffer, offset + received, size - received, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
// socket buffer is probably empty, wait and try again
Thread.Sleep(30);
}
else
throw ex; // any serious error occurr
}
} while (size > received);
Call the Receive method using code such this:
Socket socket = tcpClient.Client;
byte[] buffer = new byte[12]; // length of the text "Hello world!"
try
{ // receive data with timeout 10s
SocketEx.Receive(socket, buffer, 0, buffer.Length, 10000);
string str = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
}
catch (Exception ex) { /* ... */ }
/resource from http://www.csharp-examples.net/
String Format for DateTime
Custom DateTime Formatting
There are following custom format specifiers y (year), M (month), d (day), h (hour 12), H (hour 24), m (minute), s (second), f (second fraction), F (second fraction, trailing zeroes are trimmed), t (P.M or A.M) and z (time zone).
Following examples demonstrate how are the format specifiers rewritten to the output.
// create date time 2008-03-09 16:05:07.123
DateTime dt = new DateTime(2008, 3, 9, 16, 5, 7, 123);
String.Format("{0:y yy yyy yyyy}", dt); // "8 08 008 2008" year
String.Format("{0:M MM MMM MMMM}", dt); // "3 03 Mar March" month
String.Format("{0:d dd ddd dddd}", dt); // "9 09 Sun Sunday" day
String.Format("{0:h hh H HH}", dt); // "4 04 16 16" hour 12/24
String.Format("{0:m mm}", dt); // "5 05" minute
String.Format("{0:s ss}", dt); // "7 07" second
String.Format("{0:f ff fff ffff}", dt); // "1 12 123 1230" sec.fraction
String.Format("{0:F FF FFF FFFF}", dt); // "1 12 123 123" without zeroes
String.Format("{0:t tt}", dt); // "P PM" A.M. or P.M.
String.Format("{0:z zz zzz}", dt); // "-6 -06 -06:00" time zone
You can use also date separator / (slash) and time sepatator : (colon). These characters will be rewritten to characters defined in the current DateTimeFormatInfo.DateSeparator and DateTimeFormatInfo.TimeSeparator.
// date separator in german culture is "." (so "/" changes to ".")Here are some examples of custom date and time formatting:
String.Format("{0:d/M/yyyy HH:mm:ss}", dt); // "9/3/2008 16:05:07" - english (en-US)
String.Format("{0:d/M/yyyy HH:mm:ss}", dt); // "9.3.2008 16:05:07" - german (de-DE)
// month/day numbers without/with leading zeroes
String.Format("{0:M/d/yyyy}", dt); // "3/9/2008"
String.Format("{0:MM/dd/yyyy}", dt); // "03/09/2008"
// day/month names
String.Format("{0:ddd, MMM d, yyyy}", dt); // "Sun, Mar 9, 2008"
String.Format("{0:dddd, MMMM d, yyyy}", dt); // "Sunday, March 9, 2008"
// two/four digit year
String.Format("{0:MM/dd/yy}", dt); // "03/09/08"
String.Format("{0:MM/dd/yyyy}", dt); // "03/09/2008"
Standard DateTime Formatting
In DateTimeFormatInfo there are defined standard patterns for the current culture. For example property ShortTimePattern is string that contains value h:mm tt for en-US culture and value HH:mm for de-DE culture.
Following table shows patterns defined in DateTimeFormatInfo and their values for en-US culture. First column contains format specifiers for the String.Format method.
Specifier | DateTimeFormatInfo property | Pattern value (for en-US culture) |
---|---|---|
t | ShortTimePattern | h:mm tt |
d | ShortDatePattern | M/d/yyyy |
T | LongTimePattern | h:mm:ss tt |
D | LongDatePattern | dddd, MMMM dd, yyyy |
f | (combination of D and t ) | dddd, MMMM dd, yyyy h:mm tt |
F | FullDateTimePattern | dddd, MMMM dd, yyyy h:mm:ss tt |
g | (combination of d and t ) | M/d/yyyy h:mm tt |
G | (combination of d and T ) | M/d/yyyy h:mm:ss tt |
m , M | MonthDayPattern | MMMM dd |
y , Y | YearMonthPattern | MMMM, yyyy |
r , R | RFC1123Pattern | ddd, dd MMM yyyy HH':'mm':'ss 'GMT' (*) |
s | SortableDateTimePattern | yyyy'-'MM'-'dd'T'HH':'mm':'ss (*) |
u | UniversalSortableDateTimePattern | yyyy'-'MM'-'dd HH':'mm':'ss'Z' (*) |
(*) = culture independent |
Following examples show usage of standard format specifiers in String.Format method and the resulting output.
String.Format("{0:t}", dt); // "4:05 PM" ShortTime/resource from http://www.csharp-examples.net/
String.Format("{0:d}", dt); // "3/9/2008" ShortDate
String.Format("{0:T}", dt); // "4:05:07 PM" LongTime
String.Format("{0:D}", dt); // "Sunday, March 09, 2008" LongDate
String.Format("{0:f}", dt); // "Sunday, March 09, 2008 4:05 PM" LongDate+ShortTime
String.Format("{0:F}", dt); // "Sunday, March 09, 2008 4:05:07 PM" FullDateTime
String.Format("{0:g}", dt); // "3/9/2008 4:05 PM" ShortDate+ShortTime
String.Format("{0:G}", dt); // "3/9/2008 4:05:07 PM" ShortDate+LongTime
String.Format("{0:m}", dt); // "March 09" MonthDay
String.Format("{0:y}", dt); // "March, 2008" YearMonth
String.Format("{0:r}", dt); // "Sun, 09 Mar 2008 16:05:07 GMT" RFC1123
String.Format("{0:s}", dt); // "2008-03-09T16:05:07" SortableDateTime
String.Format("{0:u}", dt); // "2008-03-09 16:05:07Z" UniversalSortableDateTime
DataView RowFilter Syntax
Column names
If a column name contains any of these special characters ~ ( ) # \ / = > < + - * % & | ^ ' " [ ], you must enclose the column name within square brackets [ ]. If a column name contains right bracket ] or backslash \, escape it with backslash (\] or \\).
dataView.RowFilter = "id = 10"; // no special character in column name "id"
dataView.RowFilter = "$id = 10"; // no special character in column name "$id"
dataView.RowFilter = "[#id] = 10"; // special character "#" in column name "#id"
dataView.RowFilter = "[[id\]] = 10"; // special characters in column name "[id]"
Literals
String values are enclosed within single quotes ' '. If the string contains single quote ', the quote must be doubled.
dataView.RowFilter = "Name = 'John'" // string value
dataView.RowFilter = "Name = 'John ''A'''" // string with single quotes "John 'A'"
dataView.RowFilter = String.Format("Name = '{0}'", "John 'A'".Replace("'", "''"));
Number values are not enclosed within any characters. The values should be the same as is the result of int.ToString()
or float.ToString()
method for invariant or English culture.
dataView.RowFilter = "Year = 2008" // integer value
dataView.RowFilter = "Price = 1199.9" // float value
dataView.RowFilter = String.Format(CultureInfo.InvariantCulture.NumberFormat,
"Price = {0}", 1199.9f);
Date values are enclosed within sharp characters #
#
. The date format is the same as is the result of DateTime.ToString()
method for invariant or English culture.
dataView.RowFilter = "Date = #12/31/2008#" // date value (time is 00:00:00)
dataView.RowFilter = "Date = #2008-12-31#" // also this format is supported
dataView.RowFilter = "Date = #12/31/2008 16:44:58#" // date and time value
dataView.RowFilter = String.Format(CultureInfo.InvariantCulture.DateTimeFormat,
"Date = #{0}#", new DateTime(2008, 12, 31, 16, 44, 58));
Alternatively you can enclose all values within single quotes '
'
. It means you can use string values for numbers or date time values. In this case the current culture is used to convert the string to the specific value.
dataView.RowFilter = "Date = '12/31/2008 16:44:58'" // if current culture is English
dataView.RowFilter = "Date = '31.12.2008 16:44:58'" // if current culture is German
dataView.RowFilter = "Price = '1199.90'" // if current culture is English
dataView.RowFilter = "Price = '1199,90'" // if current culture is German
Comparison operators
Equal, not equal, less, greater operators are used to include only values that suit to a comparison expression. You can use these operators = <> < <= > >=.
Note: String comparison is culture-sensitive, it uses CultureInfo from DataTable.Locale property of related table (dataView.Table.Locale
). If the property is not explicitly set, its default value is DataSet.Locale (and its default value is current system culture Thread.CurrentThread.CurrentCulture).
dataView.RowFilter = "Num = 10" // number is equal to 10
dataView.RowFilter = "Date < #1/1/2008#" // date is less than 1/1/2008
dataView.RowFilter = "Name <> 'John'" // string is not equal to 'John'
dataView.RowFilter = "Name >= 'Jo'" // string comparison
Operator IN is used to include only values from the list. You can use the operator for all data types, such as numbers or strings.
dataView.RowFilter = "Id IN (1, 2, 3)" // integer values
dataView.RowFilter = "Price IN (1.0, 9.9, 11.5)" // float values
dataView.RowFilter = "Name IN ('John', 'Jim', 'Tom')" // string values
dataView.RowFilter = "Date IN (#12/31/2008#, #1/1/2009#)" // date time values
dataView.RowFilter = "Id NOT IN (1, 2, 3)" // values not from the list
Operator LIKE is used to include only values that match a pattern with wildcards. Wildcard character is *
or %
, it can be at the beginning of a pattern '*value'
, at the end 'value*'
, or at both '*value*'
. Wildcard in the middle of a patern 'va*lue'
is not allowed.
dataView.RowFilter = "Name LIKE 'j*'" // values that start with 'j'
dataView.RowFilter = "Name LIKE '%jo%'" // values that contain 'jo'
dataView.RowFilter = "Name NOT LIKE 'j*'" // values that don't start with 'j'
If a pattern in a LIKE clause contains any of these special characters *
%
[
]
, those characters must be escaped in brackets [
]
like this [*]
, [%]
, [[]
or []]
.
dataView.RowFilter = "Name LIKE '[*]*'" // values that starts with '*'
dataView.RowFilter = "Name LIKE '[[]*'" // values that starts with '['
The following method escapes a text value for usage in a LIKE clause.
public static string EscapeLikeValue(string valueWithoutWildcards)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < class="keyword">char c = valueWithoutWildcards[i];
if (c == '*' || c == '%' || c == '[' || c == ']')
sb.Append("[").Append(c).Append("]");
else if (c == '\'')
sb.Append("''");
else
sb.Append(c);
}
return sb.ToString();
}
// select all that starts with the value string (in this case with "*")
string value = "*";
// the dataView.RowFilter will be: "Name LIKE '[*]*'"
dataView.RowFilter = String.Format("Name LIKE '{0}*'", EscapeLikeValue(value));
Boolean operators
Boolean operators AND, OR and NOT are used to concatenate expressions. Operator NOT has precedence over AND operator and it has precedence over OR operator.
// operator AND has precedence over OR operator, parenthesis are needed
dataView.RowFilter = "City = 'Tokyo' AND (Age <> 60)";
// following examples do the same
dataView.RowFilter = "City <> 'Tokyo' AND City <> 'Paris'";
dataView.RowFilter = "NOT City = 'Tokyo' AND NOT City = 'Paris'";
dataView.RowFilter = "NOT (City = 'Tokyo' OR City = 'Paris')";
dataView.RowFilter = "City NOT IN ('Tokyo', 'Paris')";
Arithmetic and string operators
Arithmetic operators are addition +, subtraction -, multiplication *, division / and modulus %.
dataView.RowFilter = "MotherAge - Age <>; // people with young mother
dataView.RowFilter = "Age % 10 = 0"; // people with decennial birthday
There is also one string operator concatenation +.Parent-Child Relation Referencing
A parent table can be referenced in an expression using parent column name with Parent. prefix. A column in a child table can be referenced using child column name with Child. prefix.
The reference to the child column must be in an aggregate function because child relationships may return multiple rows. For example expression SUM(Child.Price)
returns sum of all prices in child table related to the row in parent table.
If a table has more than one child relation, the prefix must contain relation name. For example expression Child(OrdersToItemsRelation).Price
references to column Price in child table using relation named OrdersToItemsRelation.
Aggregate Functions
There are supported following aggregate functions SUM, COUNT, MI, MAX, AVG (average), STDEV (statistical standard deviation) and VAR (statistical variance).
This example shows aggregate function performed on a single table.
// select people with above-average salaryFollowing example shows aggregate functions performed on two tables which have parent-child relation. Suppose there are tables Orders and Items with the parent-child relation.
dataView.RowFilter = "Salary > AVG(Salary)";
// select orders which have more than 5 items
dataView.RowFilter = "COUNT(Child.IdOrder) > 5";
// select orders which total price (sum of items prices) is greater or equal $500
dataView.RowFilter = "SUM(Child.Price) >= 500";
Functions
There are also supported following functions. Detailed description can be found here DataColumn.Expression.
CONVERT
– converts particular expression to a specified .NET Framework typeLEN
– gets the length of a stringISNULL
– checks an expression and either returns the checked expression or a replacement valueIIF
– gets one of two values depending on the result of a logical expressionTRIM
– removes all leading and trailing blank characters like \r, \n, \t, ‚ ‘SUBSTRING
– gets a sub-string of a specified length, starting at a specified point in the string
Monday, May 19, 2008
Run Oracle Stored Procedures From C#
Note: This article assumes a basic knowledge of Oracle stored procedures and how to create and access them.
While a developer can write practically anything in C#, there are times when it would be nice to have the ability to alter a process without recompiling and redeploying an application. Let's say, for instance, that we are coding for an insurance business and an insured's premium is based upon certain classification codes. Of course, these classification codes are always changing from year to year and how we determine the premium classification is determined on these codes. The easiest way to solve this problem is implement the processing logic within the C# code. Another way is to build a cross-reference table within a database so that when we feed it certain parameters as keys, the query returns the cross-referenced value. One more way is to implement a stored procedure with the logic for a particular business rule. This article will explore the two ways that do not require altering C# code: a cross reference table and a stored procedure. This article will also show how it is much more advantageous to use a stored procedure for rules that could change regularly.
A Business Rule
What do we mean by a business rule? A business rule is a term to reference how input data gets processed based upon the way a company operates. In other words, for certain types of data there are specific rules thatapply in their usage for intended outputs. Before there were such things as stored procedures and stored functions, we would program our rule logic right inside the application code. This means that when a business changes (as they do constantly), we have to make modifications to the source code, recompile, test, debug, and re-deploy. Not only that, modern enterprise information systems are so complex that we could make a programming change in one program without knowing the effects of that change on other programs. The appeal of stored procedures is that we can encapsulate a rules-based processing code-snippet into a single database object.
Lets say that we are working on an insurance claims processing application and we need to classify subscribers into certain divisions based upon some input criteria. Our rule logic in pseudocode is this:
If code1 = 17 Then If code2 = 03 or (code2 >= 1A and code2 <= 1Z) Then division = "Division 1" Else division = "Division 2" End If Else If code1 = 47 Then If code2 = 03 or (code2 >= 1A and code2 <= 1Z) Then division = "Division 3" Else division = "Division 4" End If Else If code2 = 03 or (code2 >= 1A and code2 <= 1Z) Then division = "Division 5" Else division = "Division 6" End If End If End If |
In the above pseudocode segment, the rule is this: You are given two codes named code1 and code2. By passing code1 and code2 through the logic above, you will be returned a division, "Division 1" through "Division 6." Your task is to implement this business rule into program code and within a software application. Your choices are to use an "exploded table" model or implement via an Oracle stored procedure.
The Exploded Table Model
What do we mean by an "Exploded Table Model?" We basically mean rule implementation through a cross-reference table. For a minute, take a look at the above example and try to create a cross-reference table with a combination of code1 and code2 as the primary key. We will just take the case of code1 = 17 for example. Our "exploded" cross-reference table would look like the following:
Key | Division |
Default | Division 2 |
1703 | Division 1 |
171A | Division 1 |
171B | Division 1 |
171C | Division 1 |
171D | Division 1 |
... | Division 1 |
171Z | Division 1 |
For the sake of space, we only listed "..." in the first column for key values 171E through 171Y. However, we see that we would need 27 rows to handle the rule of code1 = 17. In order to use this table, we would build a key in our C# program of a concatenation of code1 plus code2 and pass it to our retrieval algorithm as a key in order to return the division name.
Now this would work fine once you have it all set up. And remember that we must have a row for each individual case which would be simple mathematics to determine how many rows we need for all possible conditions. All is well until one day we have to make a change. Lets say that the policy totally changed on how to process input codes 1 and 2 or the division names changed. Someone would have to go into the table and change each row affected by the new rule. Wouldn't it be much easier to sort of "script" the rule by code and you now only have to change the logic in one place. This is where an Oracle stored procedure really comes in handy.
The Oracle Stored Procedure
The other alternative is to create an Oracle stored procedure. We could implement our stored procedure with the following syntax:
CREATE OR REPLACE PROCEDURE ASSIGNDV(CODE1 IN VARCHAR, CODE2 IN VARCHAR, DIV OUT VARCHAR) IS BEGIN IF CODE1 = '17' THEN IF CODE2 = '03' OR (CODE2 >= '1A' AND CODE2 <= '1Z') THEN DV := 'Division 1'; ELSE DV := 'Division 2'; END IF; ELSIF CODE1 = '47' THEN IF CODE2 = '03' OR (CODE2 >= '1A' AND CODE2 <= '1Z') THEN DV := 'Division 3'; ELSE DV := 'Division 4'; END IF: ELSIF CODE2 = '03' OR (CODE2 >= '1A' AND CODE2 <= '1Z') THEN DV := 'Division 5'; ELSE DV := 'Division 6'; END IF; END; |
Now, you actually have the business rule "scripted" as an object inside the Oracle database. There is no need to pre-build a result table and if there are any changes to the rule, we only have to change the logic inside the procedure. For the purposes of this example, we name the stored procedure ASSIGNDV.
C# Code Implementation
Just how do we call an Oracle stored procedure from C#? First of all, you need a few prerequisites. For this example, we are running on an Oracle 9i database. There is an important download that you must install prior to implementation of C# code to call Oracle stored procedures. You need the Oracle Developer Tools suite which includes the Oracle Data Provider for .NET. This download can be found at:
http://www.oracle.com/technology/software/tech/dotnet/odt_index.html
Now to implement the code: The first thing we want to do is use the Oracle.DataAccess.Client dll:
using Oracle.DataAccess.Client;
Then, we create a connection object of type OracleConnection, open the connection, and declare an OracleCommand object using the stored procedure name as an input argument. One of the properties we want to set in the OracleCommand object cmd is the CommandType which will be CommandType.StoredProcedure.
OracleConnection conn = new OracleConnection( "Persist Security Info=False;User ID=SCOTT;Password=TIGER;Data Source=MYSERVER;"); conn.Open(); OracleCommand cmd = new OracleCommand("ASSIGNDV",conn); cmd.CommandType = CommandType.StoredProcedure; |
Now we want to declare the input and output parameters to and from the stored procedure. We use a class of type OracleParameter to do this. The arguments to the constructor for OracleParameter are the parameter name and the Oracle database type (OracleDbType). As a property to our parameter objects, we give the direction of input or output (ParameterDirection.Input, ParameterDirection.Output). Finally, we execute the stored procedure through the ExecuteNonQuery method on the cmd object and close the connection. The return value from the stored procedure can be found in the "dv" parameter of the cmd object prm3 if all is successful.
OracleParameter prm1 = new OracleParameter("Code1",OracleDbType.Varchar2); prm1.Direction = ParameterDirection.Input; prm1.Value = sCode1; cmd.Parameters.Add(prm1); OracleParameter prm2 = new OracleParameter("Code2",OracleDbType.Varchar2); prm2.Direction = ParameterDirection.Input; prm2.Value = sCode2; cmd.Parameters.Add(prm2); OracleParameter prm3 = new OracleParameter("dv",OracleDbType.Varchar2,10); |
Summary
What is really nice about Oracle stored procedures is that they are compiled objects. Their code does not have to be recompiled at runtime for each call. Therefore they can be about as fast as compiled C# code. One must remember that the stored procedures are part of a database and their performance is subjective to it. Now, if the rule to determine DV in the above example changes, we only have to go into the stored procedure and change that. There is no need to check out code, make the change, and recompile. Our application programs can stay more in a fixed state.
Instant Oracle using C#
Introduction
The idea behind this article was prompted because I found only one article that deals with C# and Oracle on this site (which is unrelated to my needs) and I haven't been able to find any articles anywhere else on the internet regarding this specific topic & platform.
In order to properly use the information contained in this article I am going to assume the following:
- You have at least a basic understanding of C# and have written code in it or some other language such as C++ or Java.
- You have a basic understanding of writing SQL commands.
- Have an Oracle database to connect to.
- If your database is at your place of work, a copy of tsanames.ora provided by the DB admin or whomever. (And hopefully permission to access the database!)
So without any further introduction, let me get into a little background.
Background
There is an Oracle database at the company I work for that contains customer case information which I wanted to access in order to query information from. I had, in the past, created an MFC application and used Oracle Objects for OLE to connect to the database in order to run my queries. While this worked, it required an insane amount of files to be installed along with my application as well as some registry entries. I really hated having to distribute all the extra files and complications along with it, but had no choice at the time. To put it simply, it required about 590 files totalling in the area of 40MB. Not exactly what I had in mind, but the documentation I had on how to use it wasn't very clear. And I don't think there's an article to date on Code Project on how to properly use it and what the client requires to have installed on his/her machine. Perhaps someone will take up the challenge.
In any case, now that I am gravitating towards using C#, I wanted to reattempt a few things I have done with Oracle but leaving as little a footprint as possible on the clients computer. Just a few months prior to this article being written I came across Oracle Instant Client (http://www.oracle.com/technology/tech/oci/instantclient/index.html). This seemed like just what I was looking for. I spent the next few days trying to figure out how to use it with MFC. I can't recall the exact amount of time but I can say this, it was far easier to implement with C# than C++, at least in my opinion.
Oracle Instant Client uses OCI (Oracle call-level interface) for accessing Oracle databases.
What is the Oracle Call Interface?
The Oracle Call Interface (OCI) is an application programming interface (API) that allows applications written in C to interact with one or more Oracle Servers. OCI gives your programs the capability to perform the full range of database operations that are possible with Oracle9i database, including SQL statement processing and object manipulation.
What you need?
You will need to create a free account on Oracles site (below) and agree to their terms to be able to download the client.
Download Oracle Instant Client for Microsoft Windows (32-bit) here. There are other platforms available and a 64-bit version for Windows, but I have't looked at the contents of any of those and they are outside the scope of this document anyhow.
There are two versions you can choose from. They are: Basic & Basic-Lite. I recommend getting the basic lite version, unless you need to support more than the English language.
OCCI requires only four dynamic link libraries to be loaded by the dynamic loader of the operating system. When this article was written, it is using the 10.2 version.
They are as follows:
- OCI Instant Client Data Shared Library
- oraociicus10.dll (Basic-Lite version)
- oraociei10.dll (Basic version)
-
- Client Code Library
- oci.dll
- Security Library
- orannzsbb10.dll
- OCCI Library
- oraocci10.dll
The main difference between the two Instant Client packages is the size of the OCI Instant Client Data Shared Library files. The lite version is roughly 17MB whereas the basic version is almost 90MB since it contains more than just the English version.
Once you have these files, simply copy them into the same directory as your executable. You could possibly put them in another folder as long as your environmental variables are set to point to its path, but I found it easiest to do it this way. After all, it is only 4 files.
The only other required file you will need to have is tsanames.ora which is is simply a text file that looks similar to this:
myserver.server.com =
(DESCRIPTION =
(ADDRESS = (PROTOCOL= TCP)(Host= myserver.server.com)(Port= yourPort#))
(CONNECT_DATA = (SID = yourSID)) )
This will be different for everyone but I am posting the sample so you know what to expect in this file if you are new to this subject. Also, you can expect to find multiple entries in this file so don't be surprised if there is more than one set.
Connection String Parameters
Parameter Definition Description Example Server or Data Source TNS Name or Network Address of the Oracle instance to connect Server=TESTDB User ID name of Oracle database user User ID=myUserID Password password for Oracle database user Password=myPassword Integrated Security To connect using external authentication or not. Valid values for Integrated Security are: YES or TRUE to use external authentication. NO or FALSE to not use external authentication. The default is false. Integrated Security=true Min Pool Size Specifies the minimum number of connections in the connection pool. Default is 0. Min Pool Size=0 Max Pool Size Specifies the maximum number of connections in the connection pool. Default is 100. Max Pool Size=100 Pooling Specifies whether or not to use connection pooling. Valid values are TRUE or YES to use connection pooling or FALSE or NOT to not use connection pooling. Default is TRUE. Pooling=true
- If you use Integrated Security, make sure you have a user created externally. Also, make sure you know what you are doing if you use external authentication - there are security implications. Read Oracle Database Advanced Security Administrator's Guide for more info about external authentication.
Code Example - Connecting to Oracle and running a simple query
Once you have the above, the rest is easy.
Create a new C# application. For this example lets keep it simple and create it as a console application.
Be sure to include a reference to System.Data.OracleClient.dll and place the following at the top of your code along with all other using
statements:
using System.Data.OracleClient;
This is a standard library provided by Microsoft. No voodoo witchcraft or additional Oracle library references required. More information about this library can be found here.
The following section of code should be all you need to get yourself started. This is simply an exercise in connecting to the database and running a simple SELECT query to return some data. The purposes of this article is to establish a connection to Oracle installing as little as possible on a users machine. You won't be seeing anything more complicated than that. We can save the rest for another article.
We will start by creating two methods: static private string GetConnectionString()
and static private void ConnectAndQuery()
. I won't be going into any specific details regarding any of the code provided. There's plently of documentation available to explain what can be done with System.Data.OracleClient if you want more information.
// This really didn't need to be in its own method, but it makes it easier
// to make changes if you want to try different things such as
// promting the user for credentials, etc.
static private string GetConnectionString()
{
// To avoid storing the connection string in your code,
// you can retrieve it from a configuration file.
return "Data Source=myserver.server.com;Persist Security Info=True;User ID=myUserID;Password=myPassword;Unicode=True";
}
// This will open the connection and query the database
static private void ConnectAndQuery()
{
string connectionString = GetConnectionString();
using (OracleConnection connection = new OracleConnection())
{
connection.ConnectionString = connectionString;
connection.Open();
Console.WriteLine("State: {0}", connection.State);
Console.WriteLine("ConnectionString: {0}", connection.ConnectionString);
OracleCommand command = connection.CreateCommand();
string sql = "SELECT * FROM MYTABLE";
command.CommandText = sql;
OracleDataReader reader = command.ExecuteReader();
while (reader.Read())
{
string myField = (string)reader["MYFIELD"];
Console.WriteLine(myField);
}
}
}
I will assume you can make the necessary modifications to the connection string and your query. The code should otherwise be self-explanitory.
All that remains is a call to ConnectAndQuery()
from Main.
Errors you may run into at runtime:
Error:
Unhandled Exception: System.Data.OracleClient.OracleException: ORA-12154: TNS:could not resolve the connect identifier specified
Cause:
- Your connection string is invalid.
- Missing the tsanames.ora file.
Resolution:
- Fix the connection string making sure the server name and/or credentials are correct.
- Make sure the tsanames.ora file is present in the application path and contains valid data.
Error:
Unhandled Exception: System.Exception: OCIEnvNlsCreate failed with return code - 1 but error message text was not available.
Cause:
- One or both of the required Oracle Instant Client DLL's are missing from the applications path.
- There is no 'PATH=' environmental variable set that point to these files should they not reside in the applications path.
Resolution:
- Copy the DLL's into the applications path or modify your PATH= to include the directory where these files reside.
Error:
Unhandled Exception: System.Data.OracleClient.OracleException: ORA-12705: Cannot access NLS data files or invalid environment specified
Cause:
- You have Oracle or Oracle develoment tools installed locally (or on the machine running the application).
Resolution:
- Check to see if [HKLM/Software/Oracle] exists. Chances are it does.
- Within the Oracle key look to see if NLS_LANG exists.
- If it does, do one of the following: Rename to NLS_LANG.OLD or delete it entirely. Providing a valid language such as AMERICAN_AMERICA.WE8MSWIN1252 would also resolve the issue. Single-byte character sets include
US7ASCII
,WE8DEC
,WE8MSWIN1252
, andWE8ISO8859P1
. Unicode character sets includeUTF8
,AL16UTF16
, andAL32UTF8
.
Perfomance Issues
I used both Oracle Developer Tools for Visual Studio .NET and Oracle Instant Client to experiment with. I did not noticably see any performance differences during these tests although there may be some depending on what you are trying to do. For my purposes, there would be little gained by using the developer tools since it requires a more complex install and no noticable performance gain. If anyone has any experiences they can share, please do.
Conclusion
I hope this will help someone who needs to establish a connection to Oracle with their application and wishes to distribute it without any complicated client installs and with only a small footprint on the clients machine. Please feel free to leave comments and/or questions. If you have anything negative to say, please leave an explanation why. Otherwise no one will learn from it.
Sunday, May 18, 2008
Using Microsoft .NET and C# with Oracle 9i
Prerequisites
Let’s face it -- neither Microsoft nor Oracle really wants to see their two flagship products work together. Microsoft would rather see a programmer use C# with SQL Server, Access, or just a plain XML data file before connecting to an Oracle data source. Oracle, on the other hand, has committed firmly to the J2EE development system for major development involving their databases.
However, Oracle does provide a software tool called ODP.NET that allows connectivity between the .NET languages and an Oracle database. This interface is a set of tools that allows the creation of .NET objects that connect directly to Oracle databases. This, at the very least, allows applications to connect to and make use of the power and capability of an Oracle database. It includes support for regular SQL queries and updates, stored procedures, and reading data from an Oracle record set into a .NET DataSet object, among other things. This article will cover the basics of connecting to and performing simple queries to an Oracle database using this set of objects.
First we’ll look at the set-up required to perform these tasks. If you are going to work with ODP.NET in ASP.NET applications, you will, obviously, need a web server with IIS and the .NET Framework installed and running. I will not be covering the steps needed to set up an IIS Web Application, as I will be focusing on actually working with the database.
If you are only working on a stand-alone application, you will just need the .NET Framework installed. In both cases, you will probably want some sort of development environment to allow easy editing of .NET code. Next, you will need to install the ODP.NET data provider on the web server if you are using ASP.NET or on your local machine if you are writing a stand-alone app. Also, each computer that will access the database as a client will need the Oracle client software installed. ODP.NET is a client-side library, and will need to be installed with your application if you are thinking of distributing your application widely. One of the nice things about ODP.NET is that it doesn’t require any extra configuration of the Oracle server. You can download the ODP.NET driver from the Oracle website at:
http://otn.oracle.com/software/tech/windows/odpnet/index.html
The first important thing to recognize about ODP.NET for Oracle 9i is that it contains two namespaces, first Oracle.DataAccess.Client. This contains the actual working classes for connecting to and acting on the Oracle database. The second namespace is Oracle.DataAccess.Types. This namespace has all the classes and methods required when working with specific Oracle data types. In this article, we won’t deal with the Types namespace at all. There are several classes to take note of in Oracle.DataAccess.Client and these are:
OracleConnection -- The basic class for connecting to a database
OracleCommand -- A class that represents a database command, either a text query or a stored procedure
OracleDataAdapter -- This class allows the programmer to pipe a returned Oracle record-set into a .NET DataSet
OracleParameter -- This class represents all the attributes and information in a stored procedure parameter
OracleDataReader -- This class represents a simple, read only data set, useful for quickly getting small, simple results, or data you will not want to change
Each of these classes will become very important in the future when we look a connecting to and working with an Oracle database.
Connecting to a DatabaseThe process for connecting to an Oracle database is very straightforward, especially for anyone who has worked with the .NET database connection classes for SQL Server. Oracle basically copied the same class structure that Microsoft used in the SQL Server connection classes already present in .NET. This makes moving over from SQL Server to Oracle relatively easy. It also means that a lot of examples describing database interaction with SQL Server can easily be modified to work with Oracle. In any case, here is a quick overview of the steps required to connect to an Oracle database.
When working with a database, we must create an OracleConnection object to retain all information for communicating with the database. First, we import the required assemblies.
using Oracle.DataAccess.Client;
using System.Data;
Next, we create the connection string with the user credentials information. In this case, the Oracle schema is user1 the password is mypass and the Oracle database name either in the tnsnames file or in the Oracle Internet Directory is db.
String connString = “User id=user1;Password=mypass;source=db”;
Now, the connection object, conn, is created using the connection string.
OracleConnection conn = new OracleConnection(connString);
The last step is to begin communication with the database using the Open() method.
conn.Open();
This creates a connection to a database and opens it. The most important part is making sure the connection string is correct. Each different type of database uses a different connection string and the differences in syntax can be a problem if you don’t pay close attention to them.
Creating a CommandIn this section, we look at creating the main workhorse class and its attributes and capabilities. The OracleCommand class has two main functions. First, you can give it a simple SQL query string and execute that command on the database. Secondly, you can use the OracleCommand object to execute a stored procedure on the database.
The query string is the simplest use of this command and we will look at it first.
First, create the command by using the factory method CreateCommand() method on the OracleConnection object.
OracleCommand cmd = conn.CreateCommand();
This creates a command object attached to the conn connection. Alternatively, the command object can be created using its own constructor and then set the Connection field later:
OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
After creating the command, we must set the CommandText attribute to give the object the actual string containing the SQL query it will run on the database. The code below also sets the CommandType attribute, which selects whether CommandText is a text command or the name of a stored procedure (this defaults to Text):
String query = “select * from users”;
cmd.CommandText = query;
cmd.CommandType = CommandType.Text;
The last step to complete is to run the command on the database and catch the results in an OracleDataReader object.
OracleDataReader reader = cmd.ExecuteReader();
This executes the command on the database and puts the results in reader. There is an important distinction between queries that read data and those that only update data. When your command reads data, you use the ExecuteReader() method to get this data back. However, when all your command does is insert or update data with nothing meaningful to return, you would use the ExecuteNonQuery() method which we will cover later.
The other mode that OracleCommand can operate in is for executing a stored procedure. This process works somewhat differently than when executing a straight text command.
Everything is the same as above until we set the command text string. In this case, the CommandText attribute is set to the name of the stored procedure. In the following case, this is a stored procedure to get back users in a certain range of user_id’s, called GetUsers. Notice also how the following code sets the CommandType attribute to StoredProcedure:
cmd.CommandText = “GetUsers”;
cmd.CommandType = CommandType.StoredProcedure;
After setting the stored procedure name and command type, we must add parameters to the OracleCommand object. This involves creating multiple OracleParameter objects and adding them to the OracleCommand’s Parameter member. This is done in the following manner:
cmd.Parameters.add(new OracleParameter(“start_user”,OracleDbType.Int, 202));
This code creates a parameter to the stored procedure GetUsers called start_user which is of type Int and has the value 202. This object is then added to the OracleCommand’s Parameters collection that stores all of the different parameters to the stored procedure. There are several things to note here. First of all, when creating multiple parameters, the number of parameters must match the number of parameters expected by the stored procedure. It is also a good idea to match up order so that parameter objects are added to the Parameters collection in the same order that they appear in the stored procedure definition to make debugging easier.
After adding the second parameter, ExecuteReader is called to get the results from the database.
cmd.Parameters.add(new OracleParameter(“end_user”, OracleDbType.Int, 210));
OracleDataReader reader = cmd.ExecuteReader();
This has shown the basics of reading data out of the database. The next step is to take that data and turn it into a .NET DataSet that can be bound to a data driven control or iterated over and changed to later update the database.
Creating a DataSet from and Oracle DatabaseThe first step to creating a DataSet from an Oracle database is to make use of a new secondary class, called the OracleDataAdapter. This class basically takes the data as it comes back from Oracle and parses it into a .NET DataSet. Another useful characteristic of this class is that it takes the place of an OracleCommand object. The OracleDataAdapter contains the command string and parameter objects required to execute a command (either text or stored procedure) on the database. There are several ways to create an OracleDataAdapter object. However, the one shown below uses an OracleConnection object and a string to become the Select command.
OracleDataAdapter adapter = new OracleDataAdapter(conn, “select * from users”);
DataSet set = new DataSet();
adapter.fill(set, “users”);
After creating the OracleDataAdapter we create a DataSet object to take the results of the select statement. Then, we use the fill method to populate a DataTable object named “users” inside the DataSet with the data returned from the select statement. Now, you can do all the fun, nifty things you could always do with a DataSet, such as binding it to data driven controls, modifying the data and putting it back into the database. The OracleDataAdapter also contains data members to hold insert, update, and delete commands, so the object can even automate maintaining data in the database in the same state as the DataSet in memory. However, those functions are beyond the purview of this article.
Final Words
Overall, the ODP.NET class framework follows many of the normal conventions that any database programmer finds in other such frameworks. This makes is relatively easy to learn and pick up on for the experienced database programmer, and even for the novice, the classes are laid out in a logical way that makes understanding each ones function easy.
DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware. |
(http://www.aspfree.com/c/a/.NET/Using-Microsoft-dot-NET-and-C-sharp-with-Oracle-9i/)
The History Of Programming Language
The first programming languages predate the modern computer. From the first, the languages were codes.
During a nine-month period in 1842-1843, Ada Lovelace translated Italian mathematician Luigi Menabrea's memoir on Charles Babbage's newest proposed machine, the Analytical Engine. With the article, she appended a set of notes which specified in complete detail a method for calculating Bernoulli numbers with the Engine, recognized by some historians as the world's first computer program. But some biographers debate the extent of her original contributions versus those of her husband.
The Jacquard loom used holes in punched cards to represent sewing loom arm movements in order to generate decorative patterns automatically.
Herman Hollerith realized that he could encode information on punch cards when he observed that railroad train conductors would encode the appearance of the ticket holders on the train tickets using the position of punched holes on the tickets. Hollerith then proceeded to encode the 1890 census data on punch cards.
The first computer codes were specialized for the applications. In the first decades of the twentieth century, numerical calculations were based on decimal numbers. Eventually it was realized that logic could be represented with numbers, as well as with words. For example, Alonzo Church was able to express the lambda calculus in a formulaic way. The Turing machine was an abstraction of the operation of a tape-marking machine, for example, in use at the telephone companies. However, unlike the lambda calculus, Turing's code does not serve well as a basis for higher-level languages — its principal use is in rigorous analyses of algorithmic complexity.
Like many "firsts" in history, the first modern programming language is hard to identify. From the start, the restrictions of the hardware defined the language. Punch cards allowed 80 columns, but some of the columns had to be used for a sorting number on each card. Fortran included some keywords which were the same as English words, such as "IF", "GOTO" (go to) and "CONTINUE". The use of a magnetic drum for memory meant that computer programs also had to be interleaved with the rotations of the drum. Thus the programs were more hardware dependent than today.
To some people the answer depends on how much power and human-readability is required before the status of "programming language" is granted. Jacquard looms and Charles Babbage's Difference Engine both had simple, extremely limited languages for describing the actions that these machines should perform. One can even regard the punch holes on a player piano scroll as a limited domain-specific programming language, albeit not designed for human consumption.
The 1940s
In the 1940s the first recognizably modern, electrically powered computers were created. The limited speed and memory capacity forced programmers to write hand tuned assembly language programs. It was soon discovered that programming in assembly language required a great deal of intellectual effort and was error-prone.
In 1948, Konrad Zuse published a paper about his programming language Plankalkьl. However, it was not implemented in his time and his original contributions were isolated from other developments.
Some important languages that were developed in this period include:
1943 - Plankalkьl (Konrad Zuse)
1943 - ENIAC coding system
1949 - C-10
The 1950s and 1960s
In the 1950s the first three modern programming languages whose descendants are still in widespread use today were designed:
FORTRAN, the "FORmula TRANslator, invented by John W. Backus et al.;
LISP, the "LISt Processor", invented by John McCarthy et al.;
COBOL, the COmmon Business Oriented Language, created by the Short Range Committee, heavily influenced by Grace Hopper.
Another milestone in the late 1950s was the publication, by a committee of American and European computer scientists, of "a new language for algorithms"; the Algol 60 Report (the "ALGOrithmic Language"). This report consolidated many ideas circulating at the time and featured two key innovations:
The use of Backus-Naur Form (BNF) for describing the language's syntax. Nearly all subsequent programming languages have used a variant of BNF to describe the context-free portion of their syntax.
The introduction of lexical scoping for names in arbitrarily nested scopes.
Algol 60 was particularly influential in the design of later languages, some of which soon became more popular. The Burroughs large systems were designed to be programmed in an extended subset of Algol.
Some important languages that were developed in this period include:
1951 - Regional Assembly Language
1952 - Autocode
1954 - FORTRAN
1955 - FLOW-MATIC (forerunner to COBOL)
1957 - COMTRAN (forerunner to COBOL)
1958 - LISP
1958 - ALGOL 58
1959 - COBOL
1962 - APL
1962 - Simula
1964 - BASIC
1964 - PL/I
1967-1978: establishing fundamental paradigms
The period from the late 1960s to the late 1970s brought a major flowering of programming languages. Most of the major language paradigms now in use were invented in this period:
Simula, invented in the late 1960s by Nygaard and Dahl as a superset of Algol 60, was the first language designed to support object-oriented programming.
Smalltalk (mid 1970s) provided a complete ground-up design of an object-oriented language.
C, an early systems programming language, was developed by Dennis Ritchie and Ken Thompson at Bell Labs between 1969 and 1973.
Prolog, designed in 1972 by Colmerauer, Roussel, and Kowalski, was the first logic programming language.
ML built a polymorphic type system (invented by Robin Milner in 1973) on top of Lisp, pioneering statically typed functional programming languages.
Each of these languages spawned an entire family of descendants, and most modern languages count at least one of them in their ancestry.
The 1960s and 1970s also saw considerable debate over the merits of "structured programming", which essentially meant programming without the use of GOTO. This debate was closely related to language design: some languages did not include GOTO, which forced structured programming on the programmer. Although the debate raged hotly at the time, nearly all programmers now agree that, even in languages that provide GOTO, it is bad style to use it except in rare circumstances. As a result, later generations of language designers have found the structured programming debate tedious and even bewildering.
Some important languages that were developed in this period include:
1970 - Pascal
1970 - Forth
1972 - C
1972 - Smalltalk
1972 - Prolog
1973 - ML
1978 - SQL
The 1980s: consolidation, modules, performance
The 1980s were years of relative consolidation. C++ combined object-oriented and systems programming. The United States government standardized Ada, a systems programming language intended for use by defense contractors. In Japan and elsewhere, vast sums were spent investigating so-called "fifth generation" languages that incorporated logic programming constructs. The functional languages community moved to standardize ML and Lisp. Rather than inventing new paradigms, all of these movements elaborated upon the ideas invented in the previous decade.
However, one important new trend in language design was an increased focus on programming for large-scale systems through the use of modules, or large-scale organizational units of code. Modula, Ada, and ML all developed notable module systems in the 1980s. Module systems were often wedded to generic programming constructs---generics being, in essence, parameterized modules (see also parametric polymorphism).
Although major new paradigms for programming languages did not appear, many researchers expanded on the ideas of prior languages and adapted them to new contexts. For example, the languages of the Argus and Emerald systems adapted object-oriented programming to distributed systems.
The 1980s also brought advances in programming language implementation. The RISC movement in computer architecture postulated that hardware should be designed for compilers rather than for human assembly programmers. Aided by processor speed improvements that enabled increasingly aggressive compilation techniques, the RISC movement sparked greater interest in compilation technology for high-level languages.
Language technology continued along these lines well into the 1990s.
Some important languages that were developed in this period include:
1983 - Ada
1983 - C++
1985 - Eiffel
1987 - Perl
1989 - FL (Backus)
The 1990s: the Internet age
The rapid growth of the Internet in the mid-1990s was the next major historic event in programming languages. By opening up a radically new platform for computer systems, the Internet created an opportunity for new languages to be adopted. In particular, the Java programming language rose to popularity because of its early integration with the Netscape Navigator web browser, and various scripting languages achieved widespread use in developing customized applications for web servers. Neither of these developments represented much fundamental novelty in language design; for example, the design of Java was a more conservative version of ideas explored many years earlier in the Smalltalk community, but the widespread adoption of languages that supported features like garbage collection and strong static typing was a major change in programming practice.
Some important languages that were developed in this period include:
1990 - Haskell
1990 - Python
1991 - Java
1993 - Ruby
1995 - PHP
2000 - C#
Current trends
Programming language evolution continues, in both industry and research. Some current directions:
Mechanisms for adding security and reliability verification to the language: extended static checking, information flow control, static thread safety.
Alternative mechanisms for modularity: mixins, delegates, aspects.
Component-oriented software development.
Metaprogramming, reflection or access to the abstract syntax tree
Increased emphasis on distribution and mobility.
Integration with databases, including XML and relational databases.
Open Source as a developmental philosophy for languages, including the GNU compiler collection and recent languages such as Python, Ruby, and Squeak.
Support for Unicode so that source code (program text) is not restricted to those characters contained in the ASCII character set; allowing, for example, use of non-Latin-based scripts or extended punctuation.