Paradox.Data.ParadoxClient
1.0.0.26270
dotnet add package Paradox.Data.ParadoxClient --version 1.0.0.26270
NuGet\Install-Package Paradox.Data.ParadoxClient -Version 1.0.0.26270
<PackageReference Include="Paradox.Data.ParadoxClient" Version="1.0.0.26270" />
<PackageVersion Include="Paradox.Data.ParadoxClient" Version="1.0.0.26270" />
<PackageReference Include="Paradox.Data.ParadoxClient" />
paket add Paradox.Data.ParadoxClient --version 1.0.0.26270
#r "nuget: Paradox.Data.ParadoxClient, 1.0.0.26270"
#:package Paradox.Data.ParadoxClient@1.0.0.26270
#addin nuget:?package=Paradox.Data.ParadoxClient&version=1.0.0.26270
#tool nuget:?package=Paradox.Data.ParadoxClient&version=1.0.0.26270
Paradox.Data.ParadoxClient
A fully managed ADO.NET data provider for Paradox tables, shaped after Microsoft.Data.SqlClient.
No BDE, no ODBC, no native code: the .DB, .MB, .PX and .PXI files are read and written directly.
Targets classical .net frameworks, netstandard2.0 and netstandard2.1.
using Paradox.Data.ParadoxClient;
using var cn = new ParadoxConnection(@"Data Source=C:\Data\Tables");
cn.Open();
using var cmd = new ParadoxCommand(
@"SELECT c.Name, SUM(o.Total) AS Spend
FROM Customers c INNER JOIN Orders o ON o.CustNo = c.CustNo
WHERE c.State = @st
GROUP BY c.Name
ORDER BY Spend DESC", cn);
cmd.Parameters.AddWithValue("@st", "CA");
using var r = cmd.ExecuteReader();
while (r.Read())
{
Console.WriteLine($"{r.GetString(0)}: {r.GetDecimal(1):C}");
}
The data source is a directory
Paradox has no server and no database file. Data Source names a folder; every .DB in it is a
table, the way a Paradox working directory is a database.
| Keyword | Default | Meaning |
|---|---|---|
Data Source |
— | The directory holding the tables. Required. Synonyms: DBQ, Directory, Database. |
Password |
— | The password of an encrypted table. Synonym: PWD. |
Read Only |
false |
Open every table for reading only. |
Code Page |
0 |
Decode text with this code page. 0 uses the one in the table's header, falling back to 1252. |
Block Size |
2 |
Block size in KB, 1–32, for tables this connection creates. |
Locking |
Shared |
None, Shared, ReadOnlyShared or Exclusive. |
Command Timeout |
30 |
Default ParadoxCommand.CommandTimeout, in seconds. |
Journal |
true |
Write an undo journal inside a transaction. Turning it off makes rollback impossible. |
Ignore Password |
false |
Open an encrypted table without checking the password. See Encryption below. |
What the SQL supports
Queries — SELECT with columns, *, aliases, DISTINCT, TOP n / LIMIT n;
INNER, LEFT, RIGHT and CROSS JOIN, and the comma form; WHERE with AND OR NOT,
comparisons, BETWEEN, IN, LIKE … ESCAPE, IS [NOT] NULL; GROUP BY … HAVING;
COUNT/SUM/AVG/MIN/MAX, including COUNT(DISTINCT x); ORDER BY by expression, alias or
position; UNION [ALL]; arithmetic, ||, CASE, CAST, and a library of scalar functions;
@name, :name and ? parameters.
Changes — INSERT … VALUES (several rows at once) and INSERT … SELECT;
UPDATE … SET … WHERE; DELETE FROM … WHERE.
Structure — CREATE TABLE (with IF NOT EXISTS), DROP TABLE,
ALTER TABLE ADD | DROP | ALTER COLUMN, CREATE INDEX, DROP INDEX.
Deliberately out of scope: subqueries, EXISTS, CTEs, window functions, views, stored
procedures, and cost-based join reordering. Join order follows the order you wrote.
NULL follows SQL's three-valued logic: WHERE Credit = NULL matches nothing, and NOT (x > 1)
does not bring back the rows where x is NULL.
Types
| Paradox | SQL names accepted | .NET |
|---|---|---|
| Alpha | ALPHA(n), CHAR(n), VARCHAR(n) |
string |
| Short | SHORT, SMALLINT |
short |
| Long | LONG, INT, INTEGER |
int |
| Autoincrement | AUTOINC, IDENTITY, COUNTER |
int |
| Money | MONEY, CURRENCY |
decimal |
| Number | NUMBER, FLOAT, DOUBLE |
double |
| BCD | BCD, DECIMAL(p,s), NUMERIC(p,s) |
decimal |
| Logical | LOGICAL, BOOL, BIT |
bool |
| Date | DATE |
DateTime |
| Time | TIME |
TimeSpan |
| Timestamp | TIMESTAMP, DATETIME |
DateTime |
| Memo, Formatted memo | MEMO(n), TEXT, FORMATTEDMEMO |
string |
| Binary, OLE, Graphic | BLOB(n), OLE, GRAPHIC |
byte[] |
| Bytes | BYTES(n) |
byte[] |
A DECIMAL(p,s) becomes a BCD with s decimal places. ALPHA and BYTES need an explicit length.
Empty is NULL. Paradox marks a NULL field by zeroing its bytes, which means an empty Alpha and a NULL Alpha are the same thing on disk, and a numeric column cannot hold the one value whose encoding is all zeros. That is the format, not a choice this provider made.
Transactions
Paradox has no transactions. BeginTransaction gives you one anyway, built on an undo journal: the
files a statement is about to change are copied aside as .pxj first, and Rollback puts them back.
A journal still on disk when a connection opens is the trace of a session that died mid-write, and is
rolled back before anything else happens.
Only IsolationLevel.Serializable is offered, because that is what taking the files for yourself
amounts to. Journalling copies whole files, so a transaction over a very large table costs a copy of
it; Journal=false turns that off and gives up rollback with it.
Indexes
CREATE INDEX records the index in a .PXI sidecar of this provider's own, and the planner then
uses it: an equality on the indexed columns becomes an index seek instead of a scan. The primary key
declared in the table's own header is used the same way, with no sidecar needed.
ParadoxCommand.ExplainPlan() shows which was chosen.
This provider does not write Paradox's own .Xnn/.Ynn secondary index files. Their record
layout is the least documented part of the format, and a file that Paradox misreads is worse than no
file at all. Existing ones are reported by GetSchema("Indexes") and otherwise left alone; the .DB
is never touched by any of this, so a .PXI can be deleted at any time and costs only speed.
After any write to a keyed table, the header's index out of date flag is set, which is how Paradox
is told to rebuild its own .PX. This provider ignores a stale .PX rather than trusting it.
Encryption
An encrypted table is decrypted from a key held in its own header, so the password is not needed to
read the data — it is only the check Paradox performs before allowing access. This provider performs
the same check, and Ignore Password=true skips it.
The password hash is the one piece of the scheme that has not been confirmed against a real encrypted table, which is why the escape hatch exists. It cannot corrupt anything: a wrong hash can only refuse a correct password.
Concurrency
Locking maps onto file sharing, and that is all it does. This provider does not implement Borland's
PDOXUSRS.NET protocol, so it cannot coordinate with a running BDE application writing the same
tables. Reading alongside one is fine; writing alongside one is not supported.
Verifying against your own files
Every constant in the file format here is reverse-engineered, and the unit tests exercise it by round-tripping through this provider's own reader and writer. That catches a self-inconsistent implementation but not a shared misreading of the format: if the reader and the writer are wrong in the same way, they agree with each other perfectly and both are wrong.
Only a file this provider did not write can settle that, so there is an integration suite that runs against real Paradox tables. It finds nothing on a machine with no Paradox data, and says so in the test output rather than passing quietly.
To run it against your data, point it at the directory and run the tests:
set PARADOX_FIXTURES=C:\Legacy\Accounts;D:\Archive\Paradox
dotnet test
Semicolon-separated, searched recursively. Files that only share the .DB extension — SQLite
databases, thumbnail caches — are recognised and skipped. Originals are never written to: every
discovered table is opened read only, and the two tests that need to write copy the table and its
companions to a temporary directory first, so pointing this at live data is safe.
Alternatively drop the files into Paradox.Data.ParadoxClient.Tests/Fixtures, which has a README covering the details.
What it checks, and which open question each one closes:
| Check | What a failure means |
|---|---|
| The field widths add up to the record size the file declares | a field type's width is wrong |
| Walking the block chain finds the record count the header states | the chain links or per-block record count are misread |
| Every value is of the type its field declares | a field offset has slipped |
| Dates land between 1500 and 2200 | B2: the date epoch is wrong |
| Alpha fields decode to text without control characters | B5: the code page or the field offsets are wrong |
| A keyed table comes back in key order | the numeric sign flip is wrong |
| Opening for writing and changing nothing leaves every byte alone | the write path disturbs files it should not |
| The schema and rows survive being rewritten by this provider | the writer cannot express a shape real tables take |
Stated values in expectations.json match |
a value is read consistently but wrongly |
The last one is the only check that can catch a value being read consistently but wrongly, because
it is the only one whose answers did not come from this provider. Put the row counts, column types
and a few known cell values — taken from Paradox itself — into an expectations.json beside the
fixtures.
On a CI job that is supposed to have the data, set PARADOX_REQUIRE_FIXTURES=1 so that finding none
fails the build instead of passing.
Licence
MIT.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net35 is compatible. net40 is compatible. net403 was computed. net45 is compatible. net451 was computed. net452 was computed. net46 was computed. net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 3.5
- Net4x.AsyncBridge (>= 1.5.0.26262)
- Net4x.BaseTypes (>= 1.3.1.26262)
-
.NETFramework 4.0
- Net4x.AsyncBridge (>= 1.5.0.26262)
- Net4x.BaseTypes (>= 1.3.1.26262)
-
.NETFramework 4.5
- Net4x.AsyncBridge (>= 1.5.0.26262)
- Net4x.BaseTypes (>= 1.3.1.26262)
-
.NETStandard 2.0
- Net4x.AsyncBridge (>= 1.5.0.26262)
- Net4x.BaseTypes (>= 1.3.1.26262)
- System.Memory (>= 4.5.5)
- System.Text.Encoding.CodePages (>= 8.0.0)
-
.NETStandard 2.1
- Net4x.AsyncBridge (>= 1.5.0.26262)
- Net4x.BaseTypes (>= 1.3.1.26262)
- System.Text.Encoding.CodePages (>= 8.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Paradox.Data.ParadoxClient:
| Package | Downloads |
|---|---|
|
Net4x.DapperLibrary.Paradox
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0.26270 | 181 | 9/27/2026 |