SynOleDB: OpenSource Unit for direct access to any database via OleDB
By A.Bouchez on 2011, Friday July 1, 09:09 - mORMot Framework - Permalink
That's it, our SynOleDB unit seems alive and running well.
OLE DB (Object Linking and Embedding, Database, sometimes written as OLEDB or OLE-DB) is an API designed by Microsoft for accessing data from a variety of sources in a uniform manner. It was designed as a higher-level replacement for, and successor to, ODBC, extending its feature set to support a wider variety of non-relational databases, such as object databases and spreadsheets that do not necessarily implement SQL.
SynOleDB unit implementation has been made with several points in
mind:
- Tested with SQL Server 2008 R2 and Oracle 11g providers from Microsoft and Oracle;
- Ability to be truly Unicode, even with pre-Unicode version of Delphi (like Delphi 7 or 2007);
- Could access any local or remote Database, from any version of Delphi, since it doesn't use the DB.pas unit or any related part of the VCL (even the Delphi 7 personal or the Turbo Explorer editions), just for free;
- Handle NULL or BLOB content for parameters and results;
- Avoid most memory copy or unnecessary allocation: we tried to access the data directly from the retrieved data buffer, just as given from OleDB;
- Was therefore designed to achieve the best performance possible: most time is spent in OleDB: the code layer added to the OleDB customer is very thin;
- True OOP architecture, to be used with any OleDB provider (allowing custom parameters or such), and even without OleDB (in the future, direct access to any DB client could be used);
- Could be safely used in a multi-threaded application/server (with one
TOleDBConnectionper thread); - Allow parameter bindings of requests, with fast access to any parameter or
column name (thanks to
TDynArrayHashed); - Late binding of column values in Delphi code;
- Direct JSON content creation, with no temporary data copy nor allocation;
- Designed to be used with our
mORMot ORM, but could be used stand-alone (a full Delphi 7 client
executable is just about 200 KB), or even in any existing Delphi application,
thanks to a
TQuery-like wrapper.
Providers
Just a small list of available OleDB
providers:
- Active Directory Service
- Advantage
- AS/400 (from IBM)
- AS/400 and VSAM (from Microsoft)
- Commerce Server
- DB2
- DTS Packages
- Exchange
- Excel
- Internet Publishing
- Index Server
- Microsoft Jet
- Microsoft Project
- MySQL
- ODBC Databases
- OLAP Services
- Oracle (from Microsoft)
- Oracle (from Oracle)
- Pervasive
- Simple Provider
- SQLBase
- SQL Server
- SQL Server via SQLXMLOLEDB
- Sybase Adaptive Server Anywhere
- Sybase Adaptive Server Enterprise
- Text Files
- UniData and UniVerse
- Visual FoxPro
Sample code
Here is some working sample program, using our SynOleDB unit to
connect to a local Microsoft SQL Server 2008 R2 Express edition, which will
write a file with the JSON representation of the Person.Address
table of the sample database AdventureWorks2008R2:
program TestOleDB;
{$APPTYPE CONSOLE}
uses
SysUtils,
Classes,
SynCommons,
SynOleDB;
var Props: TOleDBConnectionProperties;
Conn: TSQLDBConnection;
Query: TSQLDBStatement;
F: TFileStream;
begin
with OleDBSynLogClass.Family do begin
Level := LOG_VERBOSE;
AutoFlushTimeOut := 10;
end;
Props := TOleDBMSSQLConnectionProperties.Create('.\\SQLEXPRESS','AdventureWorks2008R2','','');
try
//Props.ConnectionStringDialogExecute;
Conn := Props.NewConnection;
try
Conn.Connect;
Query := Conn.NewStatement;
try
Query.Execute('select * from Person.Address',true,[]);
F := TFileStream.Create(ChangeFileExt(paramstr(0),'.json'),fmCreate);
try
Query.FetchAllToJSON(F,false);
finally
F.Free;
end;
finally
Query.Free;
end;
finally
Conn.Free;
end;
finally
Props.Free;
end;
end.
Note that you can call Props.ConnectionStringDialogExecute to
display the ADO/OleDB connection dialog box, to let the user set the
appropriate database settings.
You can specify parameters, bound to the request, as such:
Query.Execute('select * from Person.Customer where Name like ?',true,['B%']);
Or using direct Bind*() methods.
TQuery-like wrapper
There is also a TQuery class defined, which is able to mimic
basic TQuery VCL methods:
Q := TQuery.Create(aSQLDBConnection);
try
Q.SQL.Clear; // optional
Q.SQL.Add('select * from DOMAIN.TABLE');
Q.SQL.Add(' WHERE ID_DETAIL=:detail;');
Q.ParamByName('DETAIL').AsString := '123420020100000430015';
Q.Open;
Q.First; // optional
while not Q.Eof do begin
assert(Q.FieldByName('id_detail').AsString='123420020100000430015');
Q.Next;
end;
Q.Close; // optional
finally
Q.Free;
end;
This class can mimic basic TQuery VCL methods, but won't need
any BDE installed, and will be faster for field and parameters access than the
standard TDataSet based implementation; in fact, OleDB replaces
the BDE or the DBExpress layer!
- Since there is no underlying TDataSet, you can't have read and
write access, or use the visual DB components of the VCL: it's limited to
direct emulation of low-level SQL as in the above code, with one-direction
retrieval (e.g. the Edit, Post, Append, Cancel, Prior, Locate,
Lookup methods do not exist within this class);
- This class is Unicode-ready even before Delphi 2009 (via the
TQueryValue AsWideString method), will natively handle
Int64/TBytes field or parameter data, and will have less overhead
than the standard DB components of the VCL;
- You should better use TSQLDBStatement instead of this wrapper,
but having such code-compatible TQuery replacement could make
easier some existing code upgrade (e.g. to avoid deploying the deprecated BDE,
generate smaller executable, access any OleDB database without paying a big
fee, avoid rewriting a lot of existing code lines of a big application...).
The magic of late binding
We implemented late binding access of column values, via a custom variant time. It uses the internal mechanism used for Ole Automation, here to access column content as if column names where native object properties.
The resulting Delphi code to write is just clear and obvious:
Props := TOleDBMSSQLConnectionProperties.Create('.\\SQLEXPRESS','AdventureWorks2008R2','','');
procedure TestISQLDBRowsVariant;
var Customer: Variant;
begin
with Props.Execute('select * from Sales.Customer where AccountNumber like ?',
['AW000001%'],@Customer) do
while Step do
assert(Copy(Customer.AccountNumber,1,8)='AW000001');
end;
Note that Props.Execute returns an ISQLDBRows
interface, so the code above will initialize (or reuse an existing) thread-safe
connection (OleDB uses a per-thread model), initialize a statement, execute it,
access the rows via the Step method and
the Customer variant, retrieving the column value via a
direct Customer.AccountNumber statement.
The above code is perfectly safe, and all memory will be release with the
reference count garbage-collector feature of the ISQLDBRows
interface. You don't have to add any try..finally Free end
statements in your code.
This is the magic of late-binding and interfaces in Delphi.
The low-level Variants unit is (optionally) hacked to use a
dedicated direct call of our properties. In practice, this late binding is as
fast using a standard property like Column['ColumnName'] than
using the variant variation Customer.ColumnName. This speed
increase has also been shared with the late-binding access of values for our
Big
Table unit.
Works from Delphi 6 up to XE.
Licensed under a MPL/GPL/LGPL license, as part of our framework.
Feedback and comments are welcome on our forum.