一个项目中必须使用C#读取比较老的DBF文件,并且不清楚该文件属于dBase或FoxPro的哪个版本。俺以前是通过VfpOdbc方式读取DBF记录(一般需要安装VfpOdbc驱动程序),但一直存在顾虑:Microsoft已经不支持VfpOdbc了,其最新的版本是1999年发布的6.*版,存在着与Win7/8的兼容性问题。于是,在网上查找了相关的代码,结果如下:
- CodeProject中的ParseDbf(地址)源代码:该代码存在一些问题,如:中文字段读取问题,中文记录值读取问题。关键的是,读俺项目中的DBF文件时报错,于是舍弃该代码;
- 优快云下载的ReadDbf.cs(地址)源代码:该代码基本上能使用,但实际测试时还是发现了两个严重问题:1)修改DBF表结构后再读该文件报错误;2)没有考虑删除标志(读删除标志错误)。经过仔细分析源代码后解决了这两个问题。
下面是整理重构ReadDbf.cs后的代码,可以读取DBF记录到一个DataTable中,使用方法:
- CSUST.Dbf.TDbfTable dbf = new CSUST.Dbf.TDbfTable(dbf文件名),其中dbf文件是含路径的全文件名
- dbf.Table即是获取的DataTable对象;
- dbf.DbfFields为DBF字段数组。
重构时删除了部分注释,详细的解释说明请参考ReadDbf.cs中的代码。
测试了二十多个DBF文件(Vfp2.5和Vfp6.0的DBF文件),通过与VfpOdbc读取的记录比较,TDbfTable读到的所有记录数据与VfpOdbc读取的一致,包括:字段类型、字段数、记录数、记录的各个字段值等,唯一不同的是一个记录的double值,在VfpOdbf读取的表中存在舍入误差。
需要指出,ParseDbf、ReadDbf和本文介绍的TDbfTable均不能处理备注字段、二进制字段等情况。本着来源是开源的、重构后继续开源的原则发布代码如下,读者如果发现代码中的bug或有更好的解决方法,也请不吝指出。
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace CSUST.Dbf
{
#region TDbfTable更新历史
//-----------------------------------------------------------------------------------------------
// 1) 2013-06-10: (1) 自 ReadDbf.cs 移植过来
// (2) 读记录时, 首先读删除标志字节, 然后读记录. 原代码这个地方忽略了删除标志字节.
// (3) 原代码 MoveNext() 后没有再判是否文件尾, 在读修改结构后的表时报错误.
// 2) 2013-06-11: (1) int.money.decimal.double 直接用 BitConverter 转换函数
// (2) Date类型转换时, 如果字符串为空, 则为1899-12-30日.
// (3) Time类型转换时, 如果天数和毫秒数全为0, 则为1899-12-30日.
// (4) Time类型转换时, 毫秒数存在误差, 有1000以下的整数数, 需要+1秒.
// (5) 读记录值时, 在定位首指针后, 顺序读每个记录数组.
// 3) 2013-06-17 (1) 打开文件后需要关闭文件流, 增加一个 CloseFileStream() 方法
// 4) 2013-06-18 (1) 把 CloseFileStream() 放到 try{}finally{} 结构中
//-----------------------------------------------------------------------------------------------------
#endregion
public class TDbfHeader
{
public const int HeaderSize = 32;
public sbyte Version;
public byte LastModifyYear;
public byte LastModifyMonth;
public byte LastModifyDay;
public int RecordCount;
public ushort HeaderLength;
public ushort RecordLength;
public byte[] Reserved = new byte[16];
public sbyte TableFlag;
public sbyte CodePageFlag;
public byte[] Reserved2 = new byte[2];
}
public class TDbfField
{
public const int FieldSize = 32;
public byte[] NameBytes = new byte[11]; // 字段名称
public byte TypeChar;
public byte Length;
public byte Precision;
public byte[] Reserved = new byte[2];
public sbyte DbaseivID;
public byte[] Reserved2 = new byte[10];
public sbyte ProductionIndex;
public bool IsString
{
get
{
if (TypeChar == 'C')
{
return true;
}
return false;
}
}
public bool IsMoney
{
get
{
if(TypeChar == 'Y')
{
return true;
}
return false;
}
}
public bool IsNumber
{
get
{
if (TypeChar == 'N')
{
return true;
}
return false;
}
}
public bool IsFloat
{
get
{
if (TypeChar == 'F')
{
return true;
}
return false;
}
}
public bool IsDate
{
get
{
if (TypeChar == 'D')
{
return true;
}
return false;
}
}
public bool IsTime
{
get
{
if (TypeChar == 'T')
{
return true;
}
return false;
}
}
public bool IsDouble
{
get
{
if (TypeChar == &