A surprising amount of business still runs on xBase files. GoldMine CRM keeps its contacts in dBASE tables. So do older accounting and inventory systems, Siemens STEP 7 projects and every GIS shapefile. The programs that write them are often decades old, and they still work, which is exactly why nobody has replaced them.
The trouble starts when a modern system needs the same data. Microsoft's Visual FoxPro OLE DB and Jet dBASE drivers are 32-bit only and no longer developed, so every .NET app that talks to these files ends up pinned to an x86 build and a driver nobody maintains. Reading the files is the easy part. The hard part is writing to them while the old program is still running, without breaking its indexes or its locks.
That is the problem Thunder.Dbf solves. It's a free, MIT-licensed C# library for .NET 10 that reads and writes dBASE, FoxPro, Visual FoxPro and Clipper tables, memos and indexes in plain managed code, on Windows, Linux and macOS.
Sharing files with a program from 1995
Old xBase programs don't use a database server. Each copy of the app opens the same files on a shared drive and coordinates through byte-range locks and the index files themselves. Anything else that writes to those files has to play by the same rules, or the old app finds a corrupt index the next morning.
Thunder.Dbf takes the same record and header locks dBASE takes, updates every index tag on every write, and leaves fields you didn't change byte for byte as they were. It opens read-only unless you ask otherwise, so pointing it at live data to look around is safe:
dotnet add package Thunder.Dbf
using Thunder.Dbf;
using var db = DbfDatabase.Open(@"\\server\goldmine\data");
Console.Write(db.Check()); // what's readable, writable, locked or damaged
foreach (var record in db["CONTACT1"].ReadAll())
{
if (!record.IsDeleted)
Console.WriteLine($"{record["COMPANY"]}, {record["CITY"]}");
}
// Find a record through the app's own .mdx index, like dBASE's SEEK
var contacts = db["CONTACT1"];
using var index = contacts.OpenProductionIndex()!;
var acme = contacts.Seek(index["CONTCOMP"], "ACME");
Values come back as ordinary .NET types (string, decimal, DateOnly, bool). To write, open with new DbfDatabaseOptions { ReadOnly = false }.
Proving it against the real programs
Unit tests prove a library agrees with itself. The question that matters is whether the old program still trusts the files afterward. So we spent a day running Thunder.Dbf against the software it actually shares files with: Visual FoxPro 9 SP2, the VFP OLE DB 9.0 driver, the Jet 4.0 and ACE 16 Access drivers, and Harbour 3.0, the open-source Clipper. After each write, Visual FoxPro itself walked every index tag and looked up every key. The results below are from the build going into the next release.
First, the two programs took turns. Over twenty rounds, Thunder.Dbf and Visual FoxPro alternated on the same tables with random appends, updates, deletes and recalls, then wrote at the same time. That came to 616,640 writes and 39.4 million index entries and records checked, and every one agreed.
Many programs on one table
Next, a simulated office: four Visual FoxPro programs, ten OLE DB connections and Thunder.Dbf (three programs and three threads) writing the same table for 60 seconds at a time, adding records, changing keys, deleting and recalling. We ran it on four kinds of table, on a local disk and over a network share. Every time, every record added was counted, Visual FoxPro found every key, and every index matched a fresh rebuild. The same held with Clipper programs (Harbour) on their own index format.
Other languages
FoxPro tables in other languages use other code pages, and an index built in the wrong one is useless to the old program. So we had Visual FoxPro, set to each code page, build tables of random text using every character the code page has: Western (1252, 850 and 437), Central European (852 and 1250), Cyrillic (1251 and 866), Greek (1253), Turkish (1254), Baltic (1257) and Japanese (932).
In all eleven, Thunder.Dbf's index keys were byte-for-byte identical to VFP's, whether it rebuilt VFP's indexes or created its own. That includes case-insensitive UPPER() indexes, down to the handful of letters where VFP's capitals differ from the standard ones. After Thunder.Dbf added and changed 800 records, Visual FoxPro found every record under every key, and its own REINDEX agreed.
How the other drivers compare
Thunder.Dbf matches Visual FoxPro on everything both were tested on. The other drivers got the same treatment, and they didn't all hold up: Jet corrupts its own index when two programs write at once, Harbour lets FoxPro's candidate-key tags go stale, and ACE fails under load. Those limits are in Thunder.Dbf's documentation.
| Behaviour | Thunder.Dbf | VFP 9 | Harbour 3.0 | Jet / ACE | Evidence |
|---|---|---|---|---|---|
| Keeps each tag's root on its page while others have the table open | Yes | Yes | Yes | Not tested | Checked with VFP sessions holding the table open |
| Writes the same keys, in the same order, as VFP's own REINDEX | Yes | Yes | Not tested | Not tested | 527,000 entries on one table and 130,000 on another equal after 20 rounds |
Index keys match VFP's in other code pages, including UPPER() indexes | Yes | Yes | Not tested | Not tested | Byte for byte in eleven code pages, from Western and Central European to Cyrillic, Greek, Turkish, Baltic and Japanese |
| Keeps VFP CANDIDATE tags up to date | Yes | Yes | No | Not tested | Harbour left a tag at 16 entries while the table grew to 100,130 records |
| Several of its own writers on one table stay consistent | Yes | Yes | Yes | No | Jet: two processes left 4,228 keys for 4,227 records |
| Writes safely while VFP has the same table open | Yes | Yes | Partly | No | Harbour keeps roots but not CANDIDATE tags |
| Keeps FOR-filtered tags | Yes | Yes | Not tested | No | Jet left a FOR tag at 1,518 entries against 4,684 in a rebuild |
| After a writer is killed mid-change, nothing is silently wrong | Yes | No | Not tested | Not tested | Thunder.Dbf 0 in 120 kills; Visual FoxPro left a wrong index with no warning 47 times in 120 |
| PACK, REINDEX and INDEX ON wait for the table to themselves | Yes | Yes | Yes | Not tested | Thunder.Dbf refuses while VFP has the table open (8 of 8 cases) |
| Holds up under heavy load without driver errors | Yes | Yes | Yes | Partly | ACE 16 failed a few thousand single-row inserts in |
Creating FoxPro indexes
Thunder.Dbf also creates FoxPro .cdx tags, including FOR, UNIQUE, CANDIDATE and DESCENDING ones. All 16 tag kinds tested came out byte-identical to the tags Visual FoxPro builds itself, and they held up in a full office run alongside VFP.
Crashes, power cuts and damaged files
The harshest test for a shared file is a writer dying halfway through a change, the way a program does when someone closes a laptop lid or the power drops. What matters is not whether damage happens, since no file format can promise that, but whether anyone finds out before the old program starts trusting a broken index.
A writer killed mid-write
We killed each writer at a random moment, 60 times, 30 on each of two tables. Then Visual FoxPro judged the table with its own tools: it opened it, looked up every record by every key, and compared each index with its own REINDEX.
Visual FoxPro and its OLE DB driver left a wrong index with no warning 51 times in 180. That means a record's key was missing from the index or still had its old value, so VFP's own SEEK couldn't find the record, and nothing would tell anyone until someone ran REINDEX. Thunder.Dbf never did. Every time something was wrong it said so, and refused to write on until it was fixed.
| Writer, 60 kills each | Clean | Damage reported (refused until fixed) | Index left wrong, no warning | Won't open (VFP error 2091) |
|---|---|---|---|---|
| Visual FoxPro 9, killed mid-INSERT | 32 | 0 | 26 | 2 |
| Visual FoxPro 9, killed mid-REPLACE | 39 | 0 | 21 | 0 |
| VFP OLE DB driver | 55 | 0 | 4 | 1 |
| Thunder.Dbf, default settings | 39 | 21 | 0 | 0 |
| Thunder.Dbf, locked bulk load | 4 | 56, every one recovered by a rebuild | 0 | 0 |
A power cut
A killed program still leaves the operating system to finish its writes. A power cut doesn't. We switched off a virtual machine, hard, at a random moment while Thunder.Dbf was writing, 50 times in each mode. On its default settings it never lost a write it had confirmed, and in no mode did it leave damage it didn't report.
| Setting, 50 power cuts each | Confirmed writes lost | After the restart | Left wrong without a word |
|---|---|---|---|
| Default | 0 in 50 (the last confirmed write was there all 50 times) | 35 clean; 15 damage found and reported | 0 |
| Flushes off (full speed) | The last few seconds of writes, as documented | 48 clean; 2 damage found and reported | 0 |
| Locked bulk load | Only the batch in progress; every finished batch kept, 50 of 50 | 47 damage found and reported; 3 clean (the cut fell between batches) | 0 |
“Confirmed” means every write Thunder.Dbf had reported as done before the power went, and after each cut we looked up the last of those writes in the file itself. After a power cut, the default leaves at most an extra index entry, never a missing one, and Thunder.Dbf finds it on open. Visual FoxPro doesn't: looking at the same files after the default-mode cuts, it called 15 of them clean when its own REINDEX disagreed, and couldn't open 3.
Damaged files
Files on old shared drives get damaged in ways no test plans for. We fed Thunder.Dbf 200,000 deliberately corrupted tables and indexes. In the final 100,000, every damaged file got a plain error that explains what's wrong, not a raw .NET exception.
How we tested: on 31 October 2026, on one Windows 11 PC, with Visual FoxPro 9 SP2, the VFP OLE DB 9.0 driver and Thunder.Dbf main: the kill tests on 8310b1c (the bulk-load row on a836679), the power cuts on 8ef7cd8 (the flushes-off row on 8310b1c) and the code pages on b848387. Each writer was killed 0.1 to 2.5 seconds into writing. “Damage reported” means Thunder.Dbf named the problem and refused to write until a rebuild; it doesn't count as silent. Power cuts were Hyper-V's hard Turn Off of an Ubuntu 24.04 virtual machine with a standard ext4 disk, 0.3 to 3 seconds into writing. Turn Off loses everything the machine hadn't yet written to its disk; a physical disk's own write cache isn't part of the test, and on default settings Thunder.Dbf flushes through it. Bulk loads ran in batches of 200 changes. The office runs on a local disk used main; the network-share runs (SMB, on the same PC) used an earlier build of the same write path. The damaged-file tests ran on a Mac.
How fast it is
Most teams that come to Thunder.Dbf are leaving the Visual FoxPro OLE DB driver, and many still have Visual FoxPro itself writing the same files. So we timed all three on one Windows 11 PC, on the same tables, doing the same work.
Reading
Reading is where the gap is widest. Thunder.Dbf found records by key 49 times faster than the OLE DB driver, about 76,900 lookups a second against 1,580, and read a whole 10,000-row table 2.8 times faster.
Writing
Writing is closer, and it depends on how many index tags each write has to keep current. With nothing forced to disk, which is how Visual FoxPro and its driver always write, Thunder.Dbf added records 45% faster than the OLE DB driver on a table with 2 index tags, and 12% faster than Visual FoxPro itself. On a table with 7 tags it ran about 8% behind the driver, and Visual FoxPro was faster still, at 21,053 records a second to Thunder.Dbf's 15,267.
Bulk loads and migrations usually lock the table first. Locked, Thunder.Dbf beat Visual FoxPro's FLOCK() by 21% on the 2-tag table and came within about 5% of it on the 7-tag one.
FLOCK().Why the default is slower
Out of the box, Thunder.Dbf forces every write to disk, in the order that makes the crash results above possible. Visual FoxPro and its driver don't, which is why the comparisons above run Thunder.Dbf with flushes off. Forced to disk, Thunder.Dbf added 1,096 records a second on the 2-tag table and 1,352 on the 7-tag one, against 1,479 and 2,307 for Visual FoxPro with FLUSH FORCE.
The developer picks per table, and the default is the safe end. Full speed is one setting away (FlushWritesToDisk = false).
| Setting | If a write is cut off | Records a second (2 tags / 7 tags) |
|---|---|---|
| Default | Damage from a killed process or a power cut is always found and reported. No confirmed write was lost in 50 power cuts. | 1,096 / 1,352 |
| Default plus the write journal | A cut-off write is finished exactly by the next one. | 550 / 607 |
| Flushes off | Damage is still always reported, but a power cut can lose the last few seconds of writes, as it can with Visual FoxPro. | 21,053 / 15,267 |
| Locked bulk load | Only the batch in progress can be lost; it's found and refused until a rebuild. Other users wait for the batch to finish. | 24,390 / 21,277 |
All the numbers
| What the app does | Thunder.Dbf | VFP OLE DB driver | Visual FoxPro 9 |
|---|---|---|---|
| Add records, nothing forced to disk, 2-tag table | 21,053 | 14,493 | 18,868 |
| Same, 7-tag table | 15,267 | 16,667 | 21,053 |
| Add records with the table locked (bulk loads, migrations), 2-tag table | 24,390 | – | 20,202 (FLOCK) |
| Same, 7-tag table | 21,277 | – | 22,472 (FLOCK) |
| Add records, every one forced to disk (Thunder.Dbf's default), 2-tag table | 1,096 | – | 1,479 (FLUSH FORCE) |
| Same, 7-tag table | 1,352 | – | 2,307 (FLUSH FORCE) |
| Look up a record by key, per second | 76,923 | 1,580 | – |
| Read every record of a 10,000-row table, rows per second | 666,667 | 238,095 | – |
Records added per second unless noted; the fastest in each row is in bold.
Thunder.Dbf also runs on macOS, where Visual FoxPro and its driver don't. On a MacBook Pro with an Apple M4 Pro, it added up to 27,027 records a second on the 2-tag table with nothing forced to disk, and 19,801 a second in a locked bulk load on the 7-tag table. It found about 150,000 records a second by key and read about 800,000 rows a second.
How we measured: on 31 October 2026, on one Windows 11 PC with a local SSD. Thunder.Dbf main (8310b1c) on .NET 10, 64-bit; the VFP OLE DB 9.0 driver through System.Data.OleDb, 32-bit; Visual FoxPro 9 SP2. Both .NET programs ran fully compiled (DOTNET_TieredCompilation=0), as a long-running app does; Visual FoxPro is native. The tables were calls.dbf (2 index tags, with memos) and TEST10K.DBF (7 index tags, 10,000 rows), as fresh copies every run. Each run did an untimed warm-up of 200 appends, then 2,000 timed appends, and each figure is the median of 3 runs. “Nothing forced to disk” means Thunder.Dbf's FlushWritesToDisk = false, the way Visual FoxPro and its driver always write. A Defender exclusion made no difference (within 4%).
From reading the data to moving off it
Most teams don't stop at reading. The same package covers the usual next steps, from typed queries to a full migration where the old app and the new database stay in step until cut-over.
| You want to | Use |
|---|---|
| Query with C# classes | table.AsQueryable<Contact>(), a LINQ provider that seeks through .mdx tags when it gives the same answer as a scan; query.Explain() shows which |
| Keep existing ADO.NET code | DbfConnection, DbfCommand and DbfDataReader, with parameterized SQL, in place of the 32-bit VFP OLE DB or Jet driver |
| Move to SQL Server, PostgreSQL or SQLite | DbfMigrator.Migrate(...) in Thunder.Dbf.Sql copies a whole folder |
| Migrate gradually | DbfBridge.Sync() keeps a live DBF table and its SQL twin in step, both ways, until cut-over |
| Shape a CSV into a DBF, or export | DbfImport.FromCsv and DbfExport.ToCsv |
| Maintain tables | CreateIndexTag (dBASE's INDEX ON) and Pack() (PACK) |
| Script it | thunder-dbf check, info, import, export, migrate |
| Put a web app on top | Thunder.Dbf.Server: a JSON API with hashed API keys or OpenID Connect, per-table and per-field permissions, a write journal, backups and an OpenAPI schema per table |
Install the tool with dotnet tool install -g thunder-dbf, and the SQL package with dotnet add package Thunder.Dbf.Sql.
Why we built it
Thunder.Dbf came out of the work we do for clients: keeping an old system running while the business moves to something new. That kind of move only works if the old and new systems can share data safely in the meantime, and that is a data-layer problem before it is anything else. If you're facing one, see our legacy .NET modernization piece or get in touch.
Reference
At a glance
| Name | Thunder.Dbf |
| Made by | ProjectThunder |
| License | MIT, free for commercial use |
| Current version | 0.1.0, released October 31, 2026 |
| Runtime | .NET 10, pure C#, no native dependencies |
| Platforms | Windows, Linux, macOS (64-bit) |
| NuGet packages | Thunder.Dbf (engine), Thunder.Dbf.Sql (SQL migration and sync), thunder-dbf (command-line tool) |
| Web service | Thunder.Dbf.Server, a self-contained download on each GitHub release |
| Source | github.com/pthunder/Thunder.Dbf |
Supported formats
| File | Read | Write |
|---|---|---|
dBASE III and IV tables, .dbt memos | Yes | Yes |
FoxPro 2 and Visual FoxPro tables, .fpt memos | Yes | Yes |
| Visual FoxPro integer, currency, double, datetime, null and autoincrement fields | Yes | Yes |
dBASE IV .mdx indexes | Yes | Yes, every tag kept exact |
dBASE III .ndx indexes | Yes | Yes |
Clipper .ntx indexes | Yes | Yes, under Clipper's own index lock |
FoxPro and Visual FoxPro .cdx and .idx indexes | Yes | Preview, opt in with MaintainFoxProIndexes |
| Code pages | Over 60, including DOS 437 and 850 and Windows 125x | Same |
dBASE II and dBASE 7 tables are refused with a clear error rather than misread. The test suite checks every index against files written by dBASE III Plus, dBASE IV, FoxPro 2, Visual FoxPro, X# and a real Siemens STEP 7 project, on Windows, Linux and macOS.
Frequently asked questions
How do I read a DBF file in C#?
Install Thunder.Dbf from NuGet, call DbfDatabase.Open(folder), and loop over db["TABLE"].ReadAll(). It works on .NET 10 on Windows, Linux and macOS, with no drivers to install.
How do I replace the Visual FoxPro OLE DB driver or the Jet dBASE driver?
Use Thunder.Dbf's ADO.NET provider (DbfConnection, DbfCommand, DbfDataReader). It runs in a normal 64-bit process, so the app no longer has to be built for x86.
Can my legacy dBASE or FoxPro app keep running while .NET writes to its files?
Yes, on Windows and Linux. Thunder.Dbf takes the same byte-range locks dBASE takes and keeps .mdx, .ndx and .ntx indexes exact. macOS can't enforce those locks, so there it's for read-only or single-user use.
Can I read GoldMine CRM data in .NET?
Yes. GoldMine stores contacts in dBASE tables such as CONTACT1.DBF with .mdx indexes, which Thunder.Dbf reads, seeks and writes. Its tests have been run against real GoldMine data.
How do I convert DBF files to SQL Server, PostgreSQL or SQLite?
Use DbfMigrator.Migrate from the Thunder.Dbf.Sql package, or run thunder-dbf migrate from the command line. To keep both sides live during a move, use DbfBridge.Sync().
Can Visual FoxPro, OLE DB and Thunder.Dbf users write the same table at once?
Yes. In 60-second runs with 4 Visual FoxPro processes, 10 VFP OLE DB connections and 6 Thunder.Dbf writers on one table, locally and over an SMB share, every append was counted and Visual FoxPro found every index key. One run took 650,136 appends with nothing lost.
Is Thunder.Dbf faster than the Visual FoxPro OLE DB driver?
For reading, by a wide margin: on one Windows 11 PC it found records by key 49 times faster and read a whole table 2.8 times faster. For writing, with nothing forced to disk as the driver always runs, it was 45% faster on a table with 2 index tags and about 8% slower on one with 7. By default Thunder.Dbf forces every write to disk, which is slower and survives a crash; that is a per-table setting.
What happens if a program crashes or the power fails while Thunder.Dbf is writing?
On its default settings, Thunder.Dbf never lost a write it had confirmed in 50 hard power cuts, and in 120 killed writes it never left an index wrong without reporting it. When it finds damage it says so and refuses to write until the index is rebuilt. In the same kill tests, Visual FoxPro and its OLE DB driver left a wrong index with no warning 51 times in 180.
Does Thunder.Dbf work with Central European, Cyrillic, Greek or Japanese FoxPro tables?
Yes. In eleven code pages (Western 1252, 850 and 437, Central European 852 and 1250, Cyrillic 1251 and 866, Greek 1253, Turkish 1254, Baltic 1257 and Japanese 932) its index keys are byte-for-byte identical to Visual FoxPro's, including case-insensitive UPPER() indexes.
Is it free?
Yes. It's MIT-licensed, including for commercial use. ProjectThunder offers help with modernizing or migrating legacy systems.
Sources
- Thunder.Dbf on GitHub: source, documentation and samples
- Thunder.Dbf on NuGet
- Thunder.Dbf.Sql on NuGet
- thunder-dbf tool on NuGet
- Release 0.1.0
- Getting started guide