oder
Stand: 09.11.2001, Beta 2, Autor: Christian Rodemeyer
Warum ist der DB Zugriff in der ZW so wie er ist? Ein Rückblick:

Wie können die Forderungen der ZW mit .NET gelöst werden?
Der OleDb Data Provider erscheint als der natürlichste Ansatz, da er



#include <CODBC.h>
using System.Data.OleDb;
COConnection* m_pDB; // Deklaration // Connect m_pDB = new COConnection(m_dsn, m_usr, m_pwd, m_qal); // Disconnect delete m_pDB; m_pDB = NULL;
private OleDbConnection m_db; // Deklaration
[...]
// Connect
m_db = new OleDbConnection();
m_db.ConnectionString = "Provider=" + m_provider.Text + ";"
+ "Data Source=" + m_dataSource.Text + ";"
+ "User ID=" + m_usr.Text + ";"
+ "Password=" + m_pwd.Text + ";";
m_db.Open();
// Disconnect
m_db.Close();COStatement stmt(m_pDB);
stmt.Execute("create index #DemoTableIndex on #DemoTable(StringIndexed, IntIndexed)");
// oder on the fly
COStatement(m_pDB).Execute("drop table #DemoTable");
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = m_db;
cmd.CommandText = "create index DemoTableIndex on DemoTable(StringIndexed, IntIndexed)";
cmd.Dispose();
// on the fly ist *nicht* möglich, da Dispose() niemals aufgerufen wird!
// führt zu einem Resourcenleck in der Datenbank
new OleDbCommand("drop table DemoTable", m_db).ExecuteNonQuery(); Die wohl häufigste DB-Aufgabe des Programmierer besteht im Zusammenbauen eines SQL-Befehls. CODBC bietet mit der COBuilder Klasse ein Möglichkeit, SQL-Befehle übersichtlich zu formatieren und C++ Datentypen bequem in SQL/ODBC Datentypen umzuwandeln:
CExTime t = CExTime::GetCurrentTime();
CString s = "D'oh";
COBuilder SQL;
SQL << "select Id, StringValue, BoolValue "
"from #DemoTable "
"where TimeValue < " << t << " and "
"StringIndexed = " << COEscape(s);
SQL enthält nun:
select Id, StringValue, BoolValue
from #DemoTable
where TimeValue < {ts '2001-11-09 14:52:12'} and
StringIndexed = 'D''oh'.NET bietet keine besondere Möglichkeit zum Zusammenbauen von SQL Befehlen. Man ist auf die Bordmittel des Frameworks angewiesen, was recht mühsam sein kann.
DateTime t = DateTime.Now;
string s = "D''oh"; // Fürs Escapen ist ein Automatismus dringend erforderlich
string SQL;
SQL = "select Id, StringValue, BoolValue "
+ "from DemoTable "
+ "where TimeValue < {ts '" + t.ToString("s").Replace('T', ' ') + "'} and "
+ "StringIndexed = '" + s + "'";
// Alternative: t.ToString("yyyy'-'MM'-'dd HH':'mm':'ss")
// Achtung: t.ToString("yyyy-MM-dd HH:mm:ss") wäre falsch, weil ':' durch
// lokalisierten Uhrzeittrenner ersetzt wird. '-' funktioniert nur durch Zufall,
// weil '-' (noch) kein Format Character istLPCSTR SQL = "select Id, TimeValue, StringValue, BoolValue, IntValue from #DemoTable";
// Die bequeme Variante
for (COAdhocQuery q(m_pDB, SQL; q; ++q); // Über alle Zeilen iterieren
{
// Zugriff auf die Spaltenwerte
int gIntValue = q["Id" ].AsInt();
CExTime gTimeValue = q["TimeValue" ].AsExTime();
CString gStringValue = q["StringValue"].AsString();
bool gBoolValue = q["BoolValue" ].AsBool();
if (g["IntValue"].IsNull()) ++nNulls; // NULL Values abprüfen
}
// Die performance orientierte Lösung (fast doppelt so schnell)
struct RDemoTable // eine Struktur muss definiert werden
{
COInt id;
COString<128> stringValue;
COTimestamp timeValue;
COBool boolValue;
COInt intValue;
};
void CDemoDlg::OnSelectQuery()
{
for (COQuery<RDemoTable> q(m_pDB, SQL); q; ++q) // for each row
{
int gIntValue = q->id;
CExTime gTimeValue = q->timeValue;
CString gStringValue = q->stringValue;
bool gBoolValue = q->boolValue;
if (q->intValue.IsNull()) ++nNulls;
}
}
string SQL = "select Id, TimeValue, StringValue, BoolValue, IntValue from DemoTable";
m_cmd.CommandText = SQL;
OleDbDataReader reader = m_cmd.ExecuteReader();
while (reader.Read()) // for each row
{
int iValue = Convert.ToInt32 (reader["Id"]);
DateTime dtValue = Convert.ToDateTime(reader["TimeValue"]);
string sValue = Convert.ToString (reader["StringValue"]);
bool bValue = Convert.ToBoolean (reader["BoolValue"]);
if (reader["IntValue"] == DBNull.Value) ++ nNulls;
// typisierter Zugriff ist nur über Spaltennummern möglich!
// hoffentlich ändert sich das noch in der Release Version
iValue = reader.GetInt32(4);
sValue = reader.GetString(2);
}
reader.Close(); // wichtig, da kein out of scope destruktor in C#Häufig möchte man nur kurze SQL Befehle wie ein "select count(*)" absetzen. Dafür gibt es in CODBC und .NET Abkürzungen, GetSimpleQuery() und ExecuteScalar()
int min = COStatement(m_pDB).GetSimpleQueryInt("select min(Id) from #DemoTable");
m_cmd.CommandText = "select min(Id) from DemoTable"; object minId = m_cmd.ExecuteScalar(); return (minId == DBNull.Value) ? 0: Convert.ToInt32(minId);
COBuilder SQL;
COStatement stmt(m_pDB);
for (int i = m_nedInsertRows; i--;)
{
SQL.Reset(); // Befehl zusammenbauen
SQL << "insert into #DemoTable(Id, StringValue, TimeValue, IntValue, BoolValue, StringIndexed, IntIndexed)"
<< "values ("
<< ++id << ", ";
if (GetRandomBool(1)) SQL << "NULL, ";
else SQL << COEscape(GetRandomString(128)) << ", ";
if (GetRandomBool(1)) SQL << "NULL, ";
else SQL << GetRandomTime() << ", ";
if (GetRandomBool(1)) SQL << "NULL";
else SQL << GetRandomInt();
SQL << ", " << (GetRandomBool() ? 1 : 0)
<< ", " << COEscape(GetRandomString(64))
<< ", " << GetRandomInt()
<< ")";
stmt.Execute(SQL); // Befehl absenden und ausführen
}
for (int i = 0; i < numRows; ++i)
{
string SQL = "insert into DemoTable(Id, StringValue, TimeValue, IntValue, BoolValue, StringIndexed, IntIndexed)"
+ "values (" + (++id).ToString() + ", ";
if (GetRandomBool(1)) SQL += "NULL, ";
else SQL += GetRandomString(128) + ", ";
if (GetRandomBool(1)) SQL += "NULL, ";
else SQL += GetRandomTime() + ", ";
if (GetRandomBool(1)) SQL += "NULL";
else SQL += GetRandomInt();
SQL += ", " + (GetRandomBool() ? '1' : '0')
+ ", " + GetRandomString(64)
+ ", " + GetRandomInt()
+ ")";
m_cmd.CommandText = SQL;
m_cmd.ExecuteNonQuery(); // Befehl absenden und ausführen
}static LPCSTR SQL_InsertDemoTable =
"insert into #DemoTable "
"(Id, StringValue, TimeValue, IntValue, BoolValue, StringIndexed, IntIndexed) "
"values (?, ?, ?, ?, ?, ?, ?) ";
// struct mit COObject - Attributen definieren, welches reihenenfolgemäßig
// zu den Fragezeichen im SQL Befehl passt.
// Das erste Attribut matcht das erste Fragezeichen, usw.
struct RDemoTable
{
COInt id;
COString<128> stringValue;
COTimestamp timeValue;
COInt intValue;
COBool boolValue;
COString<64> stringIndexed;
COInt intIndexed;
};
void CDemoDlg::OnInsertAction()
{
COAction inserter(m_pDB, SQL_InsertDemoTable);
// Alle Attribute/Parameter setzen
inserter->id = GetNextId();
inserter->stringValue . SetNull(); // NULL
inserter->timeValue = GetRandomTime(); // CExTime oder COleDateTime
inserter->intValue = GetRandomInt();
inserter->boolValue = GetRandomBool();
inserter->stringIndexed = GetRandomString(64); // kein Escapen notwendig
inserter->intIndexed = GetRandomInt();
inserter.Execute(); // Befehl ausführen
} CODBC
if (q->intValue.IsNull()) {...}
if (q["IntValue"].IsNull() {...}
updater->intValue.SetNull();.NET
if (reader["IntValue"] == DBNull.Value) {...}try
{
COStatement stmt(pDB);
stmt.Execute("Syntax error ...");
}
catch (COError* pErr)
{
pErr->ReportError(); // Standard MFC Fehlerbehandlung
pErr->Delete();
}
try
{
m_cmd.CommandText = "Syntax error ...";
m_cmd.ExecuteNonQuery();
}
catch (OleDbException err)
{
MessageBox.Show(this, err.ToString(), "OleDb.NET Demo");
}
m_pDB->SetAutoCommit(false);
try
{
COStatement stmt(m_pDB);
stmt.Execute(...);
stmt.Execute(...);
m_pDB->Commit(); // <-- Commit
}
catch (COError* pErr)
{
m_pDB->Rollback(); // <-- Rollback
ReportError(pErr);
}OleDbTransaction trans = m_db.BeginTransaction(); // OleDbConnection ist Wurzel aller Transaktionen
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = m_db;
cmd.Transaction = trans; // Explizite Angabe des Transaktionsobjekts notwendig
try
{
cmd.ExecuteNonQuery();
[...]
cmd.ExecuteNonQuery();
trans.Commit();
}
catch (OleDbException err)
{
trans.Rollback();
[...]
}
cmd.Dispose(); // Nicht vergessen, sonst gibt's ein Resourcenleck
Beide Programme liefen auf dem Visual-Studio-7 Testrechner (400MHz, 256MB Speicher) welcher über einen 100MBit Anschluss mit einem Oracle 8.0.5 auf dem S4 verbunden war. Um zufällige Messstörungen zu verringern, wurde jede Aktion vier mal wiederholt und der Mittelwert der letzten drei Messungen eingetragen. Gemessen wurde jeweils die bequeme Variante (Beq) und wenn verfügbar eine performance optimierte Variante (Opt). Die schnellste Variante wurde dann in einer Transaktion ausgeführt (Trans). Alle Zeitangaben in Millisekunden.
Laufzeit
| Messung | CODBC Ms Oracle | OleDb.NET Oracle | OleDb.NET Ms Oracle | CODBC Ms SQLServer | OleDb.NET Ms SQLServer | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Sätze | Beq. | Opt. | Trans. | Beq. | Opt. | Trans. | Beq. | Opt. | Trans. | Beq. | Opt. | Trans. | Beq. | Opt. | Trans. | |
| Insert | 500 | 6.394 | 6.350 | 793 | 8.032 | - | 2.049 | 8.488 | - | 2.620 | 4.075 | 3.911 | 444 | 4.062 | - | 894 |
| 1.000 | 13.825 | 12.373 | 2.609 | 18.529 | - | 5.083 | 17.341 | - | 6.122 | 8.428 | 8.289 | 781 | 8.368 | - | 1.725 | |
| 2.000 | 29.004 | 25.475 | 5.139 | 32.770 | - | 10.174 | 34.539 | - | 12.611 | 16.818 | 15.730 | 1.514 | 16.290 | - | 3.401 | |
| Update | 500 | 7.435 | 5.724 | 700 | 8.021 | - | 3.151 | 9.119 | - | 3.284 | 4.296 | 3.864 | 421 | 4.319 | - | 1.238 |
| 1.000 | 16.593 | 11.420 | 3.023 | 15.198 | - | 4.713 | 18.229 | - | 6.402 | 8.587 | 8.371 | 876 | 9.109 | - | 1.845 | |
| 2.000 | 32.244 | 22.927 | 5.016 | 33.544 | - | 9.757 | 32.853 | - | 12.518 | 17.732 | 16.564 | 1.669 | 18.189 | - | 3.565 | |
| Delete | 500 | 6.937 | 675 | 276 | 7.714 | - | 1.588 | 8.168 | - | 2.289 | 3.908 | 657 | 324 | 4.683 | - | 667 |
| 1.000 | 12.746 | 1.211 | 548 | 20.923 | - | 3.822 | 16.286 | - | 5.220 | 7.857 | 1.381 | 562 | 8.809 | - | 1.225 | |
| 2.000 | 24.980 | 2.320 | 1.113 | 32.429 | - | 6.933 | 32.813 | - | 9.794 | 14.640 | 1.589 | 1.125 | 16.577 | - | 3.051 | |
| Select | 1.000 | 83 | 63 | 63 | 280 | 223 | 240 | 156 | 103 | 113 | 54 | 33 | 33 | 106 | 86 | 96 |
| 5.000 | 372 | 279 | 274 | 1.385 | 1.078 | 1.108 | 747 | 453 | 474 | 266 | 158 | 159 | 510 | 407 | 420 | |
| 10.000 | 738 | 537 | 538 | 2.757 | 2.169 | 2.173 | 1.475 | 911 | 931 | 531 | 316 | 322 | 1.018 | 811 | 824 | |
| Connect | 904 | - | - | 0 | - | - | 0 | - | - | 28 | - | - | 0 | - | - | |
| Memory Usage | 9.652kB | 20.480kB | 19.668kB | 4.552kB | 17.156kB | |||||||||||
OraOleDB.Oracle, msdaora, sqloledb
Fazit: Bereits die Beta2 Version des VisualStudio.NET ist für Oracle DB Applikationen voll entwicklungstauglich, falls man Besitzer eines schnellen Rechners (>500MHz) ist.
Subjektive Anmerkung: Die Entwicklung der C# Demo ging deutlich flotter voran, als die C++ Demo. Vor allem die vielfältige Intellisense Unterstützung in jeder Situation, der geniale ObjectBrowser und die endlich durchgängig funktionierender ClassView sparen viel Zeit (z.B. das Nachschlagen von Funktionsdefinitionen in der Hilfe). Auch die Kompilationszeiten sind bei C# geringer (keine 20MB Precompiled Headers :-).
Die Quellen und ausführbare Exe's der beiden Demoprogramme gibt es hier: