Click here to Skip to main content
Click here to Skip to main content

Paradox database native .NET reader

By , 17 Mar 2011
 

Introduction

If you are working on software which accesses a Paradox database, you are probably using BDE (Borland Database Engine). This article shows how to use Paradox database files and read some data from them directly. You can also use the primary index to find specific records you need. This could be useful if BDE doesn't cooperate with you or there is another reason why you cannot or don't want to use BDE.

Background

Every developer sometimes has to deal with some kind of black-box - piece of technology which is not opened, and can be accessed only from some external interface, but there is a problem - the black-box is quite mysterious to us - one time it works, another time it doesn't, and whatever you do, it seems to be having its own moods. And then I always wish it would be opened so I could debug it, trace into it, and look at its teeth. Are you also a developer who has Reflector between quick links? Then you will probably understand why I started to search for some .NET native Paradox database reader. I couldn't find any, but I have found some Paradox format specifications, so I decided to write one myself and make it public for others who could use it. Many thanks to Randy Beck and Kevin Mitchell for sharing the Paradox internal structure, and because their materials are online, I will focus my efforts to describing my own code.

Paradox splits data to files using database objects - every table has its own file, and every index too. Data files and indexes have very similar structure, so we can handle them both in one class - ParadoxFile. BLOB values are also stored in their own files, but I haven't implemented the structure.

  • ParadoxFile - base class working with common structures from Paradox data files and indexes
  • ParadoxFile.DataBlock - represents one block of data
  • ParadoxFile.FieldInfo - data type of the field
  • ParadoxFile.V4Hdr - structure which is present only in certain Paradox files/versions
  • ParadoxTable - represents table data file
  • ParadoxPrimaryKey - represents table index
  • ParadoxFileType - enum with all file types
  • ParadoxFieldType - enum with all data types
  • ParadoxRecord - represents a data record
  • ParadoxDataReader - standard IDataReader implementation

The index file is, in fact, simply a table with indexes of datablocks and related indexed values. You can read it directly, but I also created a mechanism which browses through the index tree for you and picks up the desired record. You will have nothing more to do but specify a condition which you would like to apply on your data.

  • ParadoxCondition - base class for conditions which can be used for searching in index data
  • ParadoxCondition nested classes - various condition implementations
  • ParadoxCompareOperator - enum with supported compare operators (==, !=, <, <=, >, >=)

Class diagram

Using the code

One way to go is just open a table and start enumerating. This is done by the following piece of code. We will create a ParadoxTable object and use the Enumerate method to traverse through records. Data is retrieved synchronously as needed, so if we stop reading after a few records, no redundant read operations are taken.

var table = new ParadoxTable(dbPath, "zakazky");
var recIndex = 1;
foreach (var rec in table.Enumerate())
{
    Console.WriteLine("Record #{0}", recIndex++);
    for (int i=0; i<table.FieldCount; i++)
    {
        Console.WriteLine("    {0} = {1}", table.FieldNames[i], rec.DataValues[i]);
    }
    if (recIndex > 10) break;
}

There are, of course, scenarios where this simple method will not suffice. Sometimes we also have to read some data in the middle of a large database, so we need to use an index to minimize disk operations. In the next sample, we will use a primary key index to find records with key values in the range between 1750 and 1760. At first, the index file has to be opened, then we will create a condition composed from two compare operations. We can browse data by calling the Enumerate method on the index. In this case, we will use the ParadoxDataReader class with the the IDatareader implementation so we don't need to rewrite a lot of code if we have used BDE before.

var index = new ParadoxPrimaryKey(table, Path.Combine(dbPath, "zakazky.PX"));
var condition =
    new ParadoxCondition.LogicalAnd(
        new ParadoxCondition.Compare(
            ParadoxCompareOperator.GreaterOrEqual, 1750, 0, 0),
        new ParadoxCondition.Compare(
            ParadoxCompareOperator.LessOrEqual, 1760, 0, 0));
var qry = index.Enumerate(condition);
var rdr = new ParadoxDataReader(table, qry);
recIndex = 1;
while (rdr.Read())
{
    Console.WriteLine("Record #{0}", recIndex++);
    for (int i = 0; i < rdr.FieldCount; i++)
    {
        Console.WriteLine("    {0} = {1}", rdr.GetName(i), rdr[i]);
    }
}

Limitations

  • read-only solution
  • not all data types are supported
  • no SQL, LINQ, or other complex queries
  • you should not work with live database during writing

History

  • 1.0 - Initial version
  • 1.1 - Updated Number data type support (thanks, Tonki)
  • 1.2 - Additional data type support (thanks to Mark Kuin, Christopher Erker, and Tonki again)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Petr Bříza
Software Developer (Senior)
Czech Republic Czech Republic
Member
Freelance .NET developer from Ostrava, Czech republic. Mission of my life is to find a question for the answer 42, but in the meantime I try to use .NET and surrounding technologies in behalf of mankind. I hope I bring more good than damage Smile | :)

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
BugBugs or inconsistent data? asking for help. [modified]memberjonnyqqq21 May '12 - 3:16 
Hi there,
 
I am implementing a synchronization tool that reads records from a Paradox 7 database and adds them to an Oracle database. Your Paradox reader has been a great help so far, as I did not find any other way to access the paradox data.
With one table of the Paradox database I have some problems.
a big table with more than 600000 entries is read fine except there are 20 rows in between where there is an error like this:
 
ERROR in pLogData: processParadoxQuery failed: System.InvalidCastException: Specified cast is not valid.
at ParadoxReader.ParadoxPrimaryKey.d__b.MoveNext() in C:\Users\####\Desktop\visualstudio\ParadoxReader\Index.cs:line 47
at ParadoxReader.ParadoxPrimaryKey.d__b.MoveNext() in C:\Users\####\Desktop\visualstudio\ParadoxReader\Index.cs:line 48
at ParadoxReader.ParadoxDataReader.Read() in C:\###\ParadoxReader\Impl.cs:line 192
at ParadoxSync.ParadoxTableOracle.processParadoxQuery(ParadoxDataReader rdr) in
################

 
I am wondering if either you, Petr, or someone else could help me investigating this further. I could give away a copy of the database, too.
 
Warm regards,
Johannes

modified 21 May '12 - 9:44.

GeneralMy vote of 5memberAndez200426 Apr '12 - 0:15 
My Vote of 5 - the first .NET application I have seen to actually get some data out of my Paradox databases.
GeneralRe: My vote of 5memberAndez200426 Apr '12 - 0:59 
Although it does not work on all of my Paradox tables.
QuestionHow to identify which fields are part of Primary key? [modified]memberfastcom12322 Feb '12 - 11:22 
if to extend this function:
 
        private void ReadHeader()
        {
.....
 
            for (int i = 0; i < 0x64; i++)
            {
                if (r.ReadChar() == 'D')
                {
                    break;
                }
            }
            r.ReadBytes(8);     // DBWINUS0
            var indexNameBuff = r.ReadBytes(16);    // name of index
            indexName = Encoding.ASCII.GetString(indexNameBuff, 0, Array.FindIndex(indexNameBuff, b => b == 0));
        }
 
Then indexName can have the name of secondary index

modified 13 Mar '12 - 10:17.

QuestionReading encrypted table datamemberGroffy17 Jan '12 - 4:25 
From an old delphi project I need to read encrypted paradox 7 tables. Any idea how to implement encoding algorithm for a known password?
 
With best regards - Ulrich
QuestionEncoding issue (Argument Exception: Char Buffer too small)memberBen Schowe15 Sep '11 - 5:46 
Hi,
 
I had an issue with the codepage / character encoding used in my Paradox DB and could fix it.
I was trying to open a DB with german umlauts and special characters (ß,ö,ü,ä etc.). When trying to so the
while ((ch = r.ReadChar()) != '\x00') fldNameBuff.Append(ch);
command in DB.cs (around line 240 in ReadHEader()) threw an ArgumentException from somewhere deep down.
 
I could fix this by providing the BinaryReader() constructor inside the ParadoxFile() constructor with the encoding of the DB I used.
It looks like this:
public ParadoxFile(Stream stream)
{
    this.stream = stream;
    this.reader = new BinaryReader(stream,Encoding.GetEncoding("Windows-1252"));
    stream.Position = 0;
    this.ReadHeader();
}
 
Perhaps somebody needs this.
 
And now for the praise Wink | ;-)
This is real great work of yours. I spent 3-4 hours fighting against connection strings and googling for a solution for reading my Paradox DB without OleDB in .NET or delivering Borland DLLs. You saved my day.
Thanks a lot!
 
Ben
GeneralBugs when using indexes to readmemberJoao Teixeira Soares11 Apr '11 - 19:18 
First of all, thank you for the great work!
 
I’m trying to read a table using its index file (.PX) and find some problems (perhaps some bugs).
 
1. I wondered why the field ParadoxCondition.Compare.IndexFieldIndex is never used. So, I think that the array indexes in the lines 96 and 101 of the file Index.cs are not DataFieldIndex but IndexFieldIndex instead.
 
2. My table has a SortOrder = 0x4C (? not documented). In this SortOrder the word “cwClaTit” comes before “cwacerel”, i.e. “Z” comes before “a”. So the method used to compare the value to search with the value in the index table does not work. I change the Comparer.Default.Compare(val1, this.Value) with CultureInfo.InvariantCulture.CompareInfo .Compare(val1.ToString(), this.Value.ToString(), CompareOptions.Ordinal) and it works as expected. I think that this part of the code must be specific to each possible SortOrder.
GeneralBug in Method ParadoxFile.GetStringmemberMember 12328762 Feb '11 - 23:45 
Thanks, great work!
But I found one bug
GetString method currently seeks end of string by finding first zero byte from start of the string.
In my tests it worked incorrectly finding end of string far to long after its end.
I changed this method in my copy with this:
public string GetString(byte[] data, int from, int len)
{
string res= Encoding.GetEncoding(866).GetString(data, from, len);
return res;
}
 
and its usage changed to:
val = this.block.file.GetString(this.block.data, (int)buff.Position, dataSize);
GeneralRe: Bug in Method ParadoxFile.GetStringmemberGuldil3 Feb '11 - 9:39 
I have to put this on your "fix" or i'm getting |||||||||||||||||||||| in my string blob
string res= Encoding.GetEncoding(0).GetString(data, from, len);
int i = res.IndexOf('\0');
if (i > 0){
   res = res.Substring(0,i);
}
return res;
 
I have to change to GetEncoding(0) because i'm losing accent of my french data !
 
thanks to all for this great library.
GeneralRe: Bug in Method ParadoxFile.GetStringmemberMember 12328763 Feb '11 - 20:44 
Thanks,
it's working!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 17 Mar 2011
Article Copyright 2010 by Petr Bříza
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid