FastDBF源代码地址:https://github.com/SocialExplorer/FastDBF

第一步在解决方案中新建一个类库的项目:取名为SocialExplorer.FastDBF

第二步:引入FASTDBF的源文件  源代码可以通过github地址下载引入

源文件:DbfColumn.cs

///
/// Author: Ahmed Lacevic
/// Date: 12/1/2007
///
/// Revision History:
/// -----------------------------------
/// Author:
/// Date:
/// Desc: using System;
using System.Collections.Generic;
using System.Text; namespace SocialExplorer.IO.FastDBF
{ /// <summary>
/// This class represents a DBF Column.
/// </summary>
///
/// <remarks>
/// Note that certain properties can not be modified after creation of the object.
/// This is because we are locking the header object after creation of a data row,
/// and columns are part of the header so either we have to have a lock field for each column,
/// or make it so that certain properties such as length can only be set during creation of a column.
/// Otherwise a user of this object could modify a column that belongs to a locked header and thus corrupt the DBF file.
/// </remarks>
public class DbfColumn : ICloneable
{ /*
(FoxPro/FoxBase) Double integer *NOT* a memo field
G General (dBASE V: like Memo) OLE Objects in MS Windows versions
P Picture (FoxPro) Like Memo fields, but not for text processing.
Y Currency (FoxPro)
T DateTime (FoxPro)
I Integer Length: 4 byte little endian integer (FoxPro)
*/ /// <summary>
/// Great information on DBF located here:
/// http://www.clicketyclick.dk/databases/xbase/format/data_types.html
/// http://www.clicketyclick.dk/databases/xbase/format/dbf.html
/// </summary>
public enum DbfColumnType
{ /// <summary>
/// Character less than 254 length
/// ASCII text less than 254 characters long in dBASE.
///
/// Character fields can be up to 32 KB long (in Clipper and FoxPro) using decimal
/// count as high byte in field length. It's possible to use up to 64KB long fields
/// by reading length as unsigned.
///
/// </summary>
Character = , /// <summary>
/// Number Length: less than 18
/// ASCII text up till 18 characters long (include sign and decimal point).
///
/// Valid characters:
/// "0" - "9" and "-". Number fields can be up to 20 characters long in FoxPro and Clipper.
/// </summary>
/// <remarks>
/// We are not enforcing this 18 char limit.
/// </remarks>
Number = , /// <summary>
/// L Logical Length: 1 Boolean/byte (8 bit)
///
/// Legal values:
/// ? Not initialised (default)
/// Y,y Yes
/// N,n No
/// F,f False
/// T,t True
/// Logical fields are always displayed using T/F/?. Some sources claims
/// that space (ASCII 20h) is valid for not initialised. Space may occur, but is not defined.
/// </summary>
Boolean = , /// <summary>
/// D Date Length: 8 Date in format YYYYMMDD. A date like 0000-00- 00 is *NOT* valid.
/// </summary>
Date = , /// <summary>
/// M Memo Length: 10 Pointer to ASCII text field in memo file 10 digits representing a pointer to a DBT block (default is blanks).
/// </summary>
Memo = , /// <summary>
/// B Binary (dBASE V) Like Memo fields, but not for text processing.
/// </summary>
Binary = , /// <summary>
/// I Integer Length: 4 byte little endian integer (FoxPro)
/// </summary>
Integer = , } /// <summary>
/// Column (field) name
/// </summary>
private string mName; /// <summary>
/// Field Type (Char, number, boolean, date, memo, binary)
/// </summary>
private DbfColumnType mType; /// <summary>
/// Offset from the start of the record
/// </summary>
internal int mDataAddress; /// <summary>
/// Length of the data in bytes; some rules apply which are in the spec (read more above).
/// </summary>
private int mLength; /// <summary>
/// Decimal precision count, or number of digits afer decimal point. This applies to Number types only.
/// </summary>
private int mDecimalCount; /// <summary>
/// Full spec constructor sets all relevant fields.
/// </summary>
/// <param name="sName"></param>
/// <param name="type"></param>
/// <param name="nLength"></param>
/// <param name="nDecimals"></param>
public DbfColumn(string sName, DbfColumnType type, int nLength, int nDecimals)
{ Name = sName;
mType = type;
mLength = nLength; if(type == DbfColumnType.Number)
mDecimalCount = nDecimals;
else
mDecimalCount = ; //perform some simple integrity checks...
//------------------------------------------- //decimal precision:
//we could also fix the length property with a statement like this: mLength = mDecimalCount + 2;
//lyq修改源码取消判断
//if (mDecimalCount > 0 && mLength - mDecimalCount <= 1)
// throw new Exception("Decimal precision can not be larger than the length of the field."); if(mType == DbfColumnType.Integer)
mLength = ; if(mType == DbfColumnType.Binary)
mLength = ; if(mType == DbfColumnType.Date)
mLength = ; //Dates are exactly yyyyMMdd if(mType == DbfColumnType.Memo)
mLength = ; //Length: 10 Pointer to ASCII text field in memo file. pointer to a DBT block. if(mType == DbfColumnType.Boolean)
mLength = ; //field length:
if (mLength <= )
throw new Exception("Invalid field length specified. Field length can not be zero or less than zero.");
else if (type != DbfColumnType.Character && type != DbfColumnType.Binary && mLength > )
throw new Exception("Invalid field length specified. For numbers it should be within 20 digits, but we allow up to 255. For Char and binary types, length up to 65,535 is allowed. For maximum compatibility use up to 255.");
else if((type == DbfColumnType.Character || type == DbfColumnType.Binary) && mLength > )
throw new Exception("Invalid field length specified. For Char and binary types, length up to 65535 is supported. For maximum compatibility use up to 255."); } /// <summary>
/// Create a new column fully specifying all properties.
/// </summary>
/// <param name="sName">column name</param>
/// <param name="type">type of field</param>
/// <param name="nLength">field length including decimal places and decimal point if any</param>
/// <param name="nDecimals">decimal places</param>
/// <param name="nDataAddress">offset from start of record</param>
internal DbfColumn(string sName, DbfColumnType type, int nLength, int nDecimals, int nDataAddress): this(sName, type, nLength, nDecimals)
{ mDataAddress = nDataAddress; } public DbfColumn(string sName, DbfColumnType type): this(sName, type, , )
{
if(type == DbfColumnType.Number || type == DbfColumnType.Character )
throw new Exception("For number and character field types you must specify Length and Decimal Precision."); } /// <summary>
/// Field Name.
/// </summary>
public string Name
{
get
{
return mName;
} set
{
//name:
if (string.IsNullOrEmpty(value))
throw new Exception("Field names must be at least one char long and can not be null."); if (value.Length > )
throw new Exception("Field names can not be longer than 11 chars."); mName = value; } } /// <summary>
/// Field Type (C N L D or M).
/// </summary>
public DbfColumnType ColumnType
{
get
{
return mType;
}
} /// <summary>
/// Returns column type as a char, (as written in the DBF column header)
/// N=number, C=char, B=binary, L=boolean, D=date, I=integer, M=memo
/// </summary>
public char ColumnTypeChar
{
get
{
switch(mType)
{
case DbfColumnType.Number:
return 'N'; case DbfColumnType.Character:
return 'C'; case DbfColumnType.Binary:
return 'B'; case DbfColumnType.Boolean:
return 'L'; case DbfColumnType.Date:
return 'D'; case DbfColumnType.Integer:
return 'I'; case DbfColumnType.Memo:
return 'M'; } throw new Exception("Unrecognized field type!"); }
} /// <summary>
/// Field Data Address offset from the start of the record.
/// </summary>
public int DataAddress
{
get
{
return mDataAddress;
}
} /// <summary>
/// Length of the data in bytes.
/// </summary>
public int Length
{
get
{
return mLength;
}
} /// <summary>
/// Field decimal count in Binary, indicating where the decimal is.
/// </summary>
public int DecimalCount
{
get
{
return mDecimalCount;
} } /// <summary>
/// Returns corresponding dbf field type given a .net Type.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static DbfColumnType GetDbaseType(Type type)
{ if (type == typeof(string))
return DbfColumnType.Character;
else if (type == typeof(double) || type == typeof(float))
return DbfColumnType.Number;
else if (type == typeof(bool))
return DbfColumnType.Boolean;
else if (type == typeof(DateTime))
return DbfColumnType.Date; throw new NotSupportedException(String.Format("{0} does not have a corresponding dbase type.", type.Name)); } public static DbfColumnType GetDbaseType(char c)
{
switch(c.ToString().ToUpper())
{
case "C": return DbfColumnType.Character;
case "N": return DbfColumnType.Number;
case "B": return DbfColumnType.Binary;
case "L": return DbfColumnType.Boolean;
case "D": return DbfColumnType.Date;
case "I": return DbfColumnType.Integer;
case "M": return DbfColumnType.Memo;
} throw new NotSupportedException(String.Format("{0} does not have a corresponding dbase type.", c)); } /// <summary>
/// Returns shp file Shape Field.
/// </summary>
/// <returns></returns>
public static DbfColumn ShapeField()
{
return new DbfColumn("Geometry", DbfColumnType.Binary); } /// <summary>
/// Returns Shp file ID field.
/// </summary>
/// <returns></returns>
public static DbfColumn IdField()
{
return new DbfColumn("Row", DbfColumnType.Integer); } public object Clone()
{
return this.MemberwiseClone();
}
}
}

源文件:DbfDataTruncateException.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization; namespace SocialExplorer.IO.FastDBF
{ public class DbfDataTruncateException: Exception
{ public DbfDataTruncateException(string smessage): base(smessage)
{
} public DbfDataTruncateException(string smessage, Exception innerException)
: base(smessage, innerException)
{
} public DbfDataTruncateException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
} }
}

源文件:DbfFile.cs

///
/// Author: Ahmed Lacevic
/// Date: 12/1/2007
/// Desc: This class represents a DBF file. You can create, open, update and save DBF files using this class and supporting classes.
/// Also, this class supports reading/writing from/to an internet forward only type of stream!
///
/// Revision History:
/// -----------------------------------
/// Author:
/// Date:
/// Desc: using System;
using System.Collections.Generic;
using System.Text;
using System.IO; namespace SocialExplorer.IO.FastDBF
{ /// <summary>
/// This class represents a DBF file. You can create new, open, update and save DBF files using this class and supporting classes.
/// Also, this class supports reading/writing from/to an internet forward only type of stream!
/// </summary>
/// <remarks>
/// TODO: add end of file byte '0x1A' !!!
/// We don't relly on that byte at all, and everything works with or without that byte, but it should be there by spec.
/// </remarks>
public class DbfFile
{ /// <summary>
/// Helps read/write dbf file header information.
/// </summary>
protected DbfHeader mHeader; /// <summary>
/// flag that indicates whether the header was written or not...
/// </summary>
protected bool mHeaderWritten = false; /// <summary>
/// Streams to read and write to the DBF file.
/// </summary>
protected Stream mDbfFile = null;
protected BinaryReader mDbfFileReader = null;
protected BinaryWriter mDbfFileWriter = null; private Encoding encoding = Encoding.ASCII; /// <summary>
/// File that was opened, if one was opened at all.
/// </summary>
protected string mFileName = ""; /// <summary>
/// Number of records read using ReadNext() methods only. This applies only when we are using a forward-only stream.
/// mRecordsReadCount is used to keep track of record index. With a seek enabled stream,
/// we can always calculate index using stream position.
/// </summary>
protected int mRecordsReadCount = ; /// <summary>
/// keep these values handy so we don't call functions on every read.
/// </summary>
protected bool mIsForwardOnly = false;
protected bool mIsReadOnly = false; [Obsolete]
public DbfFile()
: this(Encoding.ASCII)
{
} public DbfFile(Encoding encoding)
{
this.encoding = encoding;
mHeader = new DbfHeader(encoding);
} /// <summary>
/// Open a DBF from a FileStream. This can be a file or an internet connection stream. Make sure that it is positioned at start of DBF file.
/// Reading a DBF over the internet we can not determine size of the file, so we support HasMore(), ReadNext() interface.
/// RecordCount information in header can not be trusted always, since some packages store 0 there.
/// </summary>
/// <param name="ofs"></param>
public void Open(Stream ofs)
{
if (mDbfFile != null)
Close(); mDbfFile = ofs;
mDbfFileReader = null;
mDbfFileWriter = null; if (mDbfFile.CanRead)
mDbfFileReader = new BinaryReader(mDbfFile, encoding); if (mDbfFile.CanWrite)
mDbfFileWriter = new BinaryWriter(mDbfFile, encoding); //reset position
mRecordsReadCount = ; //assume header is not written
mHeaderWritten = false; //read the header
if (ofs.CanRead)
{
//try to read the header...
try
{
mHeader.Read(mDbfFileReader);
mHeaderWritten = true; }
catch (EndOfStreamException)
{
//could not read header, file is empty
mHeader = new DbfHeader(encoding);
mHeaderWritten = false;
} } if (mDbfFile != null)
{
mIsReadOnly = !mDbfFile.CanWrite;
mIsForwardOnly = !mDbfFile.CanSeek;
} } /// <summary>
/// Open a DBF file or create a new one.
/// </summary>
/// <param name="sPath">Full path to the file.</param>
/// <param name="mode"></param>
public void Open(string sPath, FileMode mode, FileAccess access, FileShare share)
{
mFileName = sPath;
Open(File.Open(sPath, mode, access, share));
} /// <summary>
/// Open a DBF file or create a new one.
/// </summary>
/// <param name="sPath">Full path to the file.</param>
/// <param name="mode"></param>
public void Open(string sPath, FileMode mode, FileAccess access)
{
mFileName = sPath;
Open(File.Open(sPath, mode, access));
} /// <summary>
/// Open a DBF file or create a new one.
/// </summary>
/// <param name="sPath">Full path to the file.</param>
/// <param name="mode"></param>
public void Open(string sPath, FileMode mode)
{
mFileName = sPath;
Open(File.Open(sPath, mode));
} /// <summary>
/// Creates a new DBF 4 file. Overwrites if file exists! Use Open() function for more options.
/// </summary>
/// <param name="sPath"></param>
public void Create(string sPath)
{
Open(sPath, FileMode.Create, FileAccess.ReadWrite);
mHeaderWritten = false; } /// <summary>
/// Update header info, flush buffers and close streams. You should always call this method when you are done with a DBF file.
/// </summary>
public void Close()
{ //try to update the header if it has changed
//------------------------------------------
if (mHeader.IsDirty)
WriteHeader(); //Empty header...
//--------------------------------
mHeader = new DbfHeader(encoding);
mHeaderWritten = false; //reset current record index
//--------------------------------
mRecordsReadCount = ; //Close streams...
//--------------------------------
if (mDbfFileWriter != null)
{
mDbfFileWriter.Flush();
mDbfFileWriter.Close();
} if (mDbfFileReader != null)
mDbfFileReader.Close(); if (mDbfFile != null)
mDbfFile.Close(); //set streams to null
//--------------------------------
mDbfFileReader = null;
mDbfFileWriter = null;
mDbfFile = null; mFileName = ""; } /// <summary>
/// Returns true if we can not write to the DBF file stream.
/// </summary>
public bool IsReadOnly
{
get
{
return mIsReadOnly;
/*
if (mDbfFile != null)
return !mDbfFile.CanWrite;
return true;
*/ } } /// <summary>
/// Returns true if we can not seek to different locations within the file, such as internet connections.
/// </summary>
public bool IsForwardOnly
{
get
{
return mIsForwardOnly;
/*
if(mDbfFile!=null)
return !mDbfFile.CanSeek; return false;
*/
}
} /// <summary>
/// Returns the name of the filestream.
/// </summary>
public string FileName
{
get
{
return mFileName;
}
} /// <summary>
/// Read next record and fill data into parameter oFillRecord. Returns true if a record was read, otherwise false.
/// </summary>
/// <param name="oFillRecord"></param>
/// <returns></returns>
public bool ReadNext(DbfRecord oFillRecord)
{ //check if we can fill this record with data. it must match record size specified by header and number of columns.
//we are not checking whether it comes from another DBF file or not, we just need the same structure. Allow flexibility but be safe.
if (oFillRecord.Header != mHeader && (oFillRecord.Header.ColumnCount != mHeader.ColumnCount || oFillRecord.Header.RecordLength != mHeader.RecordLength))
throw new Exception("Record parameter does not have the same size and number of columns as the " +
"header specifies, so we are unable to read a record into oFillRecord. " +
"This is a programming error, have you mixed up DBF file objects?"); //DBF file reader can be null if stream is not readable...
if (mDbfFileReader == null)
throw new Exception("Read stream is null, either you have opened a stream that can not be " +
"read from (a write-only stream) or you have not opened a stream at all."); //read next record...
bool bRead = oFillRecord.Read(mDbfFile); if (bRead)
{
if (mIsForwardOnly)
{
//zero based index! set before incrementing count.
oFillRecord.RecordIndex = mRecordsReadCount;
mRecordsReadCount++;
}
else
oFillRecord.RecordIndex = ((int)((mDbfFile.Position - mHeader.HeaderLength) / mHeader.RecordLength)) - ; } return bRead; } /// <summary>
/// Tries to read a record and returns a new record object or null if nothing was read.
/// </summary>
/// <returns></returns>
public DbfRecord ReadNext()
{
//create a new record and fill it.
DbfRecord orec = new DbfRecord(mHeader); return ReadNext(orec) ? orec : null; } /// <summary>
/// Reads a record specified by index into oFillRecord object. You can use this method
/// to read in and process records without creating and discarding record objects.
/// Note that you should check that your stream is not forward-only! If you have a forward only stream, use ReadNext() functions.
/// </summary>
/// <param name="index">Zero based record index.</param>
/// <param name="oFillRecord">Record object to fill, must have same size and number of fields as thid DBF file header!</param>
/// <remarks>
/// <returns>True if read a record was read, otherwise false. If you read end of file false will be returned and oFillRecord will NOT be modified!</returns>
/// The parameter record (oFillRecord) must match record size specified by the header and number of columns as well.
/// It does not have to come from the same header, but it must match the structure. We are not going as far as to check size of each field.
/// The idea is to be flexible but safe. It's a fine balance, these two are almost always at odds.
/// </remarks>
public bool Read(int index, DbfRecord oFillRecord)
{ //check if we can fill this record with data. it must match record size specified by header and number of columns.
//we are not checking whether it comes from another DBF file or not, we just need the same structure. Allow flexibility but be safe.
if (oFillRecord.Header != mHeader && (oFillRecord.Header.ColumnCount != mHeader.ColumnCount || oFillRecord.Header.RecordLength != mHeader.RecordLength))
throw new Exception("Record parameter does not have the same size and number of columns as the " +
"header specifies, so we are unable to read a record into oFillRecord. " +
"This is a programming error, have you mixed up DBF file objects?"); //DBF file reader can be null if stream is not readable...
if (mDbfFileReader == null)
throw new Exception("ReadStream is null, either you have opened a stream that can not be " +
"read from (a write-only stream) or you have not opened a stream at all."); //move to the specified record, note that an exception will be thrown is stream is not seekable!
//This is ok, since we provide a function to check whether the stream is seekable.
long nSeekToPosition = mHeader.HeaderLength + (index * mHeader.RecordLength); //check whether requested record exists. Subtract 1 from file length (there is a terminating character 1A at the end of the file)
//so if we hit end of file, there are no more records, so return false;
if (index < || mDbfFile.Length - <= nSeekToPosition)
return false; //move to record and read
mDbfFile.Seek(nSeekToPosition, SeekOrigin.Begin); //read the record
bool bRead = oFillRecord.Read(mDbfFile);
if (bRead)
oFillRecord.RecordIndex = index; return bRead; } public bool ReadValue(int rowIndex, int columnIndex, out string result)
{ result = String.Empty; DbfColumn ocol = mHeader[columnIndex]; //move to the specified record, note that an exception will be thrown is stream is not seekable!
//This is ok, since we provide a function to check whether the stream is seekable.
long nSeekToPosition = mHeader.HeaderLength + (rowIndex * mHeader.RecordLength) + ocol.DataAddress; //check whether requested record exists. Subtract 1 from file length (there is a terminating character 1A at the end of the file)
//so if we hit end of file, there are no more records, so return false;
if (rowIndex < || mDbfFile.Length - <= nSeekToPosition)
return false; //move to position and read
mDbfFile.Seek(nSeekToPosition, SeekOrigin.Begin); //read the value
byte[] data = new byte[ocol.Length];
mDbfFile.Read(data, , ocol.Length);
result = new string(encoding.GetChars(data, , ocol.Length)); return true;
} /// <summary>
/// Reads a record specified by index. This method requires the stream to be able to seek to position.
/// If you are using a http stream, or a stream that can not stream, use ReadNext() methods to read in all records.
/// </summary>
/// <param name="index">Zero based index.</param>
/// <returns>Null if record can not be read, otherwise returns a new record.</returns>
public DbfRecord Read(int index)
{
//create a new record and fill it.
DbfRecord orec = new DbfRecord(mHeader); return Read(index, orec) ? orec : null; } /// <summary>
/// Write a record to file. If RecordIndex is present, record will be updated, otherwise a new record will be written.
/// Header will be output first if this is the first record being writen to file.
/// This method does not require stream seek capability to add a new record.
/// </summary>
/// <param name="orec"></param>
public void Write(DbfRecord orec)
{ //if header was never written, write it first, then output the record
if (!mHeaderWritten)
WriteHeader(); //if this is a new record (RecordIndex should be -1 in that case)
if (orec.RecordIndex < )
{
if (mDbfFileWriter.BaseStream.CanSeek)
{
//calculate number of records in file. do not rely on header's RecordCount property since client can change that value.
//also note that some DBF files do not have ending 0x1A byte, so we subtract 1 and round off
//instead of just cast since cast would just drop decimals.
int nNumRecords = (int)Math.Round(((double)(mDbfFile.Length - mHeader.HeaderLength - ) / mHeader.RecordLength));
if (nNumRecords < )
nNumRecords = ; orec.RecordIndex = nNumRecords;
Update(orec);
mHeader.RecordCount++; }
else
{
//we can not position this stream, just write out the new record.
orec.Write(mDbfFile);
mHeader.RecordCount++;
}
}
else
Update(orec); } public void Write(DbfRecord orec, bool bClearRecordAfterWrite)
{ Write(orec); if (bClearRecordAfterWrite)
orec.Clear(); } /// <summary>
/// Update a record. RecordIndex (zero based index) must be more than -1, otherwise an exception is thrown.
/// You can also use Write method which updates a record if it has RecordIndex or adds a new one if RecordIndex == -1.
/// RecordIndex is set automatically when you call any Read() methods on this class.
/// </summary>
/// <param name="orec"></param>
public void Update(DbfRecord orec)
{ //if header was never written, write it first, then output the record
if (!mHeaderWritten)
WriteHeader(); //Check if record has an index
if (orec.RecordIndex < )
throw new Exception("RecordIndex is not set, unable to update record. Set RecordIndex or call Write() method to add a new record to file."); //Check if this record matches record size specified by header and number of columns.
//Client can pass a record from another DBF that is incompatible with this one and that would corrupt the file.
if (orec.Header != mHeader && (orec.Header.ColumnCount != mHeader.ColumnCount || orec.Header.RecordLength != mHeader.RecordLength))
throw new Exception("Record parameter does not have the same size and number of columns as the " +
"header specifies. Writing this record would corrupt the DBF file. " +
"This is a programming error, have you mixed up DBF file objects?"); //DBF file writer can be null if stream is not writable to...
if (mDbfFileWriter == null)
throw new Exception("Write stream is null. Either you have opened a stream that can not be " +
"writen to (a read-only stream) or you have not opened a stream at all."); //move to the specified record, note that an exception will be thrown if stream is not seekable!
//This is ok, since we provide a function to check whether the stream is seekable.
long nSeekToPosition = (long)mHeader.HeaderLength + (long)((long)orec.RecordIndex * (long)mHeader.RecordLength); //check whether we can seek to this position. Subtract 1 from file length (there is a terminating character 1A at the end of the file)
//so if we hit end of file, there are no more records, so return false;
if (mDbfFile.Length < nSeekToPosition)
throw new Exception("Invalid record position. Unable to save record."); //move to record start
mDbfFile.Seek(nSeekToPosition, SeekOrigin.Begin); //write
orec.Write(mDbfFile); } /// <summary>
/// Save header to file. Normally, you do not have to call this method, header is saved
/// automatically and updated when you close the file (if it changed).
/// </summary>
public bool WriteHeader()
{ //update header if possible
//--------------------------------
if (mDbfFileWriter != null)
{
if (mDbfFileWriter.BaseStream.CanSeek)
{
mDbfFileWriter.Seek(, SeekOrigin.Begin);
mHeader.Write(mDbfFileWriter);
mHeaderWritten = true;
return true;
}
else
{
//if stream can not seek, then just write it out and that's it.
if (!mHeaderWritten)
mHeader.Write(mDbfFileWriter); mHeaderWritten = true; }
} return false; } /// <summary>
/// Access DBF header with information on columns. Use this object for faster access to header.
/// Remove one layer of function calls by saving header reference and using it directly to access columns.
/// </summary>
public DbfHeader Header
{
get
{
return mHeader;
}
} }
}

源文件:DbfHeader.cs

///
/// Author: Ahmed Lacevic
/// Date: 12/1/2007
/// Desc:
///
/// Revision History:
/// -----------------------------------
/// Author:
/// Date:
/// Desc: using System;
using System.Collections.Generic;
using System.Text;
using System.IO; namespace SocialExplorer.IO.FastDBF
{ /// <summary>
/// This class represents a DBF IV file header.
/// </summary>
///
/// <remarks>
/// DBF files are really wasteful on space but this legacy format lives on because it's really really simple.
/// It lacks much in features though.
///
///
/// Thanks to Erik Bachmann for providing the DBF file structure information!!
/// http://www.clicketyclick.dk/databases/xbase/format/dbf.html
///
/// _______________________ _______
/// 00h / 0| Version number *1| ^
/// |-----------------------| |
/// 01h / 1| Date of last update | |
/// 02h / 2| YYMMDD *21| |
/// 03h / 3| *14| |
/// |-----------------------| |
/// 04h / 4| Number of records | Record
/// 05h / 5| in data file | header
/// 06h / 6| ( 32 bits ) *14| |
/// 07h / 7| | |
/// |-----------------------| |
/// 08h / 8| Length of header *14| |
/// 09h / 9| structure ( 16 bits ) | |
/// |-----------------------| |
/// 0Ah / 10| Length of each record | |
/// 0Bh / 11| ( 16 bits ) *2 *14| |
/// |-----------------------| |
/// 0Ch / 12| ( Reserved ) *3| |
/// 0Dh / 13| | |
/// |-----------------------| |
/// 0Eh / 14| Incomplete transac.*12| |
/// |-----------------------| |
/// 0Fh / 15| Encryption flag *13| |
/// |-----------------------| |
/// 10h / 16| Free record thread | |
/// 11h / 17| (reserved for LAN | |
/// 12h / 18| only ) | |
/// 13h / 19| | |
/// |-----------------------| |
/// 14h / 20| ( Reserved for | | _ |=======================| ______
/// | multi-user dBASE ) | | / 00h / 0| Field name in ASCII | ^
/// : ( dBASE III+ - ) : | / : (terminated by 00h) : |
/// : : | | | | |
/// 1Bh / 27| | | | 0Ah / 10| | |
/// |-----------------------| | | |-----------------------| For
/// 1Ch / 28| MDX flag (dBASE IV)*14| | | 0Bh / 11| Field type (ASCII) *20| each
/// |-----------------------| | | |-----------------------| field
/// 1Dh / 29| Language driver *5| | / 0Ch / 12| Field data address | |
/// |-----------------------| | / | *6| |
/// 1Eh / 30| ( Reserved ) | | / | (in memory !!!) | |
/// 1Fh / 31| *3| | / 0Fh / 15| (dBASE III+) | |
/// |=======================|__|____/ |-----------------------| | -
/// 20h / 32| | | ^ 10h / 16| Field length *22| | |
/// |- - - - - - - - - - - -| | | |-----------------------| | | *7
/// | *19| | | 11h / 17| Decimal count *23| | |
/// |- - - - - - - - - - - -| | Field |-----------------------| | -
/// | | | Descriptor 12h / 18| ( Reserved for | |
/// :. . . . . . . . . . . .: | |array 13h / 19| multi-user dBASE)*18| |
/// : : | | |-----------------------| |
/// n | |__|__v_ 14h / 20| Work area ID *16| |
/// |-----------------------| | \ |-----------------------| |
/// n+1| Terminator (0Dh) | | \ 15h / 21| ( Reserved for | |
/// |=======================| | \ 16h / 22| multi-user dBASE ) | |
/// m | Database Container | | \ |-----------------------| |
/// : *15: | \ 17h / 23| Flag for SET FIELDS | |
/// : : | | |-----------------------| |
/// / m+263 | | | 18h / 24| ( Reserved ) | |
/// |=======================|__v_ ___ | : : |
/// : : ^ | : : |
/// : : | | : : |
/// : : | | 1Eh / 30| | |
/// | Record structure | | | |-----------------------| |
/// | | | \ 1Fh / 31| Index field flag *8| |
/// | | | \_ |=======================| _v_____
/// | | Records
/// |-----------------------| |
/// | | | _ |=======================| _______
/// | | | / 00h / 0| Record deleted flag *9| ^
/// | | | / |-----------------------| |
/// | | | / | Data *10| One
/// | | | / : (ASCII) *17: record
/// | |____|_____/ | | |
/// : : | | | _v_____
/// : :____|_____ |=======================|
/// : : |
/// | | |
/// | | |
/// | | |
/// | | |
/// | | |
/// |=======================| |
/// |__End_of_File__________| ___v____ End of file ( 1Ah ) *11
///
/// </remarks>
public class DbfHeader : ICloneable
{ /// <summary>
/// Header file descriptor size is 33 bytes (32 bytes + 1 terminator byte), followed by column metadata which is 32 bytes each.
/// </summary>
public const int FileDescriptorSize = ; /// <summary>
/// Field or DBF Column descriptor is 32 bytes long.
/// </summary>
public const int ColumnDescriptorSize = ; //type of the file, must be 03h
private const int mFileType = 0x03; //Date the file was last updated.
private DateTime mUpdateDate; //Number of records in the datafile, 32bit little-endian, unsigned
private uint mNumRecords = ; //Length of the header structure
private ushort mHeaderLength = FileDescriptorSize; //empty header is 33 bytes long. Each column adds 32 bytes. //Length of the records, ushort - unsigned 16 bit integer
private int mRecordLength = ; //start with 1 because the first byte is a delete flag //DBF fields/columns
internal List<DbfColumn> mFields = new List<DbfColumn>(); //indicates whether header columns can be modified!
bool mLocked = false; //keeps column name index for the header, must clear when header columns change.
private Dictionary<string, int> mColumnNameIndex = null; /// <summary>
/// When object is modified dirty flag is set.
/// </summary>
bool mIsDirty = false; /// <summary>
/// mEmptyRecord is an array used to clear record data in CDbf4Record.
/// This is shared by all record objects, used to speed up clearing fields or entire record.
/// <seealso cref="EmptyDataRecord"/>
/// </summary>
private byte[] mEmptyRecord = null; public readonly Encoding encoding = Encoding.ASCII; [Obsolete]
public DbfHeader()
{
} public DbfHeader(Encoding encoding)
{
this.encoding = encoding;
} /// <summary>
/// Specify initial column capacity.
/// </summary>
/// <param name="nInitialFields"></param>
public DbfHeader(int nFieldCapacity)
{
mFields = new List<DbfColumn>(nFieldCapacity); } /// <summary>
/// Gets header length.
/// </summary>
public ushort HeaderLength
{
get
{
return mHeaderLength;
}
} /// <summary>
/// Add a new column to the DBF header.
/// </summary>
/// <param name="oNewCol"></param>
public void AddColumn(DbfColumn oNewCol)
{ //throw exception if the header is locked
if (mLocked)
throw new InvalidOperationException("This header is locked and can not be modified. Modifying the header would result in a corrupt DBF file. You can unlock the header by calling UnLock() method."); //since we are breaking the spec rules about max number of fields, we should at least
//check that the record length stays within a number that can be recorded in the header!
//we have 2 unsigned bytes for record length for a maximum of 65535.
if (mRecordLength + oNewCol.Length > )
throw new ArgumentOutOfRangeException("oNewCol", "Unable to add new column. Adding this column puts the record length over the maximum (which is 65535 bytes)."); //add the column
mFields.Add(oNewCol); //update offset bits, record and header lengths
oNewCol.mDataAddress = mRecordLength;
mRecordLength += oNewCol.Length;
mHeaderLength += ColumnDescriptorSize; //clear empty record
mEmptyRecord = null; //set dirty bit
mIsDirty = true;
mColumnNameIndex = null; } /// <summary>
/// Create and add a new column with specified name and type.
/// </summary>
/// <param name="sName"></param>
/// <param name="type"></param>
public void AddColumn(string sName, DbfColumn.DbfColumnType type)
{
AddColumn(new DbfColumn(sName, type));
} /// <summary>
/// Create and add a new column with specified name, type, length, and decimal precision.
/// </summary>
/// <param name="sName">Field name. Uniqueness is not enforced.</param>
/// <param name="type"></param>
/// <param name="nLength">Length of the field including decimal point and decimal numbers</param>
/// <param name="nDecimals">Number of decimal places to keep.</param>
public void AddColumn(string sName, DbfColumn.DbfColumnType type, int nLength, int nDecimals)
{
AddColumn(new DbfColumn(sName, type, nLength, nDecimals));
} /// <summary>
/// Remove column from header definition.
/// </summary>
/// <param name="nIndex"></param>
public void RemoveColumn(int nIndex)
{
//throw exception if the header is locked
if (mLocked)
throw new InvalidOperationException("This header is locked and can not be modified. Modifying the header would result in a corrupt DBF file. You can unlock the header by calling UnLock() method."); DbfColumn oColRemove = mFields[nIndex];
mFields.RemoveAt(nIndex); oColRemove.mDataAddress = ;
mRecordLength -= oColRemove.Length;
mHeaderLength -= ColumnDescriptorSize; //if you remove a column offset shift for each of the columns
//following the one removed, we need to update those offsets.
int nRemovedColLen = oColRemove.Length;
for (int i = nIndex; i < mFields.Count; i++)
mFields[i].mDataAddress -= nRemovedColLen; //clear the empty record
mEmptyRecord = null; //set dirty bit
mIsDirty = true;
mColumnNameIndex = null; } /// <summary>
/// Look up a column index by name. Note that this is case sensitive, internally it does a lookup using a dictionary.
/// </summary>
/// <param name="sName"></param>
public DbfColumn this[string sName]
{
get
{
int colIndex = FindColumn(sName);
if (colIndex > -)
return mFields[colIndex]; return null; }
} /// <summary>
/// Returns column at specified index. Index is 0 based.
/// </summary>
/// <param name="nIndex">Zero based index.</param>
/// <returns></returns>
public DbfColumn this[int nIndex]
{
get
{
return mFields[nIndex];
}
} /// <summary>
/// Finds a column index by using a fast dictionary lookup-- creates column dictionary on first use. Returns -1 if not found. Note this is case sensitive!
/// </summary>
/// <param name="sName">Column name</param>
/// <returns>column index (0 based) or -1 if not found.</returns>
public int FindColumn(string sName)
{ if (mColumnNameIndex == null)
{
mColumnNameIndex = new Dictionary<string, int>(mFields.Count); //create a new index
for (int i = ; i < mFields.Count; i++)
{
mColumnNameIndex.Add(mFields[i].Name, i);
}
} int columnIndex;
if (mColumnNameIndex.TryGetValue(sName, out columnIndex))
return columnIndex; return -; } /// <summary>
/// Returns an empty data record. This is used to clear columns
/// </summary>
/// <remarks>
/// The reason we put this in the header class is because it allows us to use the CDbf4Record class in two ways.
/// 1. we can create one instance of the record and reuse it to write many records quickly clearing the data array by bitblting to it.
/// 2. we can create many instances of the record (a collection of records) and have only one copy of this empty dataset for all of them.
/// If we had put it in the Record class then we would be taking up twice as much space unnecessarily. The empty record also fits the model
/// and everything is neatly encapsulated and safe.
///
/// </remarks>
protected internal byte[] EmptyDataRecord
{
get { return mEmptyRecord ?? (mEmptyRecord = encoding.GetBytes("".PadLeft(mRecordLength, ' ').ToCharArray())); }
} /// <summary>
/// Returns Number of columns in this dbf header.
/// </summary>
public int ColumnCount
{
get { return mFields.Count; } } /// <summary>
/// Size of one record in bytes. All fields + 1 byte delete flag.
/// </summary>
public int RecordLength
{
get
{
return mRecordLength;
}
} /// <summary>
/// Get/Set number of records in the DBF.
/// </summary>
/// <remarks>
/// The reason we allow client to set RecordCount is beause in certain streams
/// like internet streams we can not update record count as we write out records, we have to set it in advance,
/// so client has to be able to modify this property.
/// </remarks>
public uint RecordCount
{
get
{
return mNumRecords;
} set
{
mNumRecords = value; //set the dirty bit
mIsDirty = true; }
} /// <summary>
/// Get/set whether this header is read only or can be modified. When you create a CDbfRecord
/// object and pass a header to it, CDbfRecord locks the header so that it can not be modified any longer.
/// in order to preserve DBF integrity.
/// </summary>
internal bool Locked
{
get
{
return mLocked;
} set
{
mLocked = value;
} } /// <summary>
/// Use this method with caution. Headers are locked for a reason, to prevent DBF from becoming corrupt.
/// </summary>
public void Unlock()
{
mLocked = false;
} /// <summary>
/// Returns true when this object is modified after read or write.
/// </summary>
public bool IsDirty
{
get
{
return mIsDirty;
} set
{
mIsDirty = value;
}
} /// <summary>
/// Encoding must be ASCII for this binary writer.
/// </summary>
/// <param name="writer"></param>
/// <remarks>
/// See class remarks for DBF file structure.
/// </remarks>
public void Write(BinaryWriter writer)
{ //write the header
// write the output file type.
writer.Write((byte)mFileType); //Update date format is YYMMDD, which is different from the column Date type (YYYYDDMM)
writer.Write((byte)(mUpdateDate.Year - ));
writer.Write((byte)mUpdateDate.Month);
writer.Write((byte)mUpdateDate.Day); // write the number of records in the datafile. (32 bit number, little-endian unsigned)
writer.Write(mNumRecords); // write the length of the header structure.
writer.Write(mHeaderLength); // write the length of a record
writer.Write((ushort)mRecordLength); // write the reserved bytes in the header
for (int i = ; i < ; i++)
writer.Write((byte)); // write all of the header records
byte[] byteReserved = new byte[]; //these are initialized to 0 by default.
foreach (DbfColumn field in mFields)
{
//char[] cname = field.Name.PadRight(11, (char)0).ToCharArray();
byte[] bName = encoding.GetBytes(field.Name);
byte[] cname = new byte[];
Array.ConstrainedCopy(bName, , cname, , bName.Length > ? : bName.Length); writer.Write(cname); // write the field type
writer.Write((char)field.ColumnTypeChar); // write the field data address, offset from the start of the record.
writer.Write(field.DataAddress); // write the length of the field.
// if char field is longer than 255 bytes, then we use the decimal field as part of the field length.
if (field.ColumnType == DbfColumn.DbfColumnType.Character && field.Length > )
{
//treat decimal count as high byte of field length, this extends char field max to 65535
writer.Write((ushort)field.Length); }
else
{
// write the length of the field.
writer.Write((byte)field.Length); // write the decimal count.
writer.Write((byte)field.DecimalCount);
} // write the reserved bytes.
writer.Write(byteReserved); } // write the end of the field definitions marker
writer.Write((byte)0x0D);
writer.Flush(); //clear dirty bit
mIsDirty = false; //lock the header so it can not be modified any longer,
//we could actually postpond this until first record is written!
mLocked = true; } /// <summary>
/// Read header data, make sure the stream is positioned at the start of the file to read the header otherwise you will get an exception.
/// When this function is done the position will be the first record.
/// </summary>
/// <param name="reader"></param>
public void Read(BinaryReader reader)
{ // type of reader.
int nFileType = reader.ReadByte(); if (nFileType != 0x03)
throw new NotSupportedException("Unsupported DBF reader Type " + nFileType); // parse the update date information.
int year = (int)reader.ReadByte();
int month = (int)reader.ReadByte();
int day = (int)reader.ReadByte();
mUpdateDate = new DateTime(year + , month, day); // read the number of records.
mNumRecords = reader.ReadUInt32(); // read the length of the header structure.
mHeaderLength = reader.ReadUInt16(); // read the length of a record
mRecordLength = reader.ReadInt16(); // skip the reserved bytes in the header.
reader.ReadBytes(); // calculate the number of Fields in the header
int nNumFields = (mHeaderLength - FileDescriptorSize) / ColumnDescriptorSize; //offset from start of record, start at 1 because that's the delete flag.
int nDataOffset = ; // read all of the header records
mFields = new List<DbfColumn>(nNumFields);
for (int i = ; i < nNumFields; i++)
{ // read the field name
char[] buffer = new char[];
buffer = reader.ReadChars();
string sFieldName = new string(buffer);
int nullPoint = sFieldName.IndexOf((char));
if (nullPoint != -)
sFieldName = sFieldName.Substring(, nullPoint); //read the field type
char cDbaseType = (char)reader.ReadByte(); // read the field data address, offset from the start of the record.
int nFieldDataAddress = reader.ReadInt32(); //read the field length in bytes
//if field type is char, then read FieldLength and Decimal count as one number to allow char fields to be
//longer than 256 bytes (ASCII char). This is the way Clipper and FoxPro do it, and there is really no downside
//since for char fields decimal count should be zero for other versions that do not support this extended functionality.
//-----------------------------------------------------------------------------------------------------------------------
int nFieldLength = ;
int nDecimals = ;
if (cDbaseType == 'C' || cDbaseType == 'c')
{
//treat decimal count as high byte
nFieldLength = (int)reader.ReadUInt16();
}
else
{
//read field length as an unsigned byte.
nFieldLength = (int)reader.ReadByte(); //read decimal count as one byte
nDecimals = (int)reader.ReadByte(); } //read the reserved bytes.
reader.ReadBytes(); //Create and add field to collection
mFields.Add(new DbfColumn(sFieldName, DbfColumn.GetDbaseType(cDbaseType), nFieldLength, nDecimals, nDataOffset)); // add up address information, you can not trust the address recorded in the DBF file...
nDataOffset += nFieldLength; } // Last byte is a marker for the end of the field definitions.
reader.ReadBytes(); //read any extra header bytes...move to first record
//equivalent to reader.BaseStream.Seek(mHeaderLength, SeekOrigin.Begin) except that we are not using the seek function since
//we need to support streams that can not seek like web connections.
int nExtraReadBytes = mHeaderLength - (FileDescriptorSize + (ColumnDescriptorSize * mFields.Count));
if (nExtraReadBytes > )
reader.ReadBytes(nExtraReadBytes); //if the stream is not forward-only, calculate number of records using file size,
//sometimes the header does not contain the correct record count
//if we are reading the file from the web, we have to use ReadNext() functions anyway so
//Number of records is not so important and we can trust the DBF to have it stored correctly.
if (reader.BaseStream.CanSeek && mNumRecords == )
{
//notice here that we subtract file end byte which is supposed to be 0x1A,
//but some DBF files are incorrectly written without this byte, so we round off to nearest integer.
//that gives a correct result with or without ending byte.
if (mRecordLength > )
mNumRecords = (uint)Math.Round(((double)(reader.BaseStream.Length - mHeaderLength - ) / mRecordLength)); } //lock header since it was read from a file. we don't want it modified because that would corrupt the file.
//user can override this lock if really necessary by calling UnLock() method.
mLocked = true; //clear dirty bit
mIsDirty = false; } public object Clone()
{
return this.MemberwiseClone();
}
}
}

源文件:DbfRecord.cs

///
/// Author: Ahmed Lacevic
/// Date: 12/1/2007
/// Desc:
///
/// Revision History:
/// -----------------------------------
/// Author:
/// Date:
/// Desc: using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Globalization; namespace SocialExplorer.IO.FastDBF
{ /// <summary>
/// Use this class to create a record and write it to a dbf file. You can use one record object to write all records!!
/// It was designed for this kind of use. You can do this by clearing the record of all data
/// (call Clear() method) or setting values to all fields again, then write to dbf file.
/// This eliminates creating and destroying objects and optimizes memory use.
///
/// Once you create a record the header can no longer be modified, since modifying the header would make a corrupt DBF file.
/// </summary>
public class DbfRecord
{ /// <summary>
/// Header provides information on all field types, sizes, precision and other useful information about the DBF.
/// </summary>
private DbfHeader mHeader = null; /// <summary>
/// Dbf data are a mix of ASCII characters and binary, which neatly fit in a byte array.
/// BinaryWriter would esentially perform the same conversion using the same Encoding class.
/// </summary>
private byte[] mData = null; /// <summary>
/// Zero based record index. -1 when not set, new records for example.
/// </summary>
private int mRecordIndex = -; /// <summary>
/// Empty Record array reference used to clear fields quickly (or entire record).
/// </summary>
private readonly byte[] mEmptyRecord = null; /// <summary>
/// Specifies whether we allow strings to be truncated. If false and string is longer than we can fit in the field, an exception is thrown.
/// </summary>
private bool mAllowStringTruncate = true; /// <summary>
/// Specifies whether we allow the decimal portion of numbers to be truncated.
/// If false and decimal digits overflow the field, an exception is thrown.
/// </summary>
private bool mAllowDecimalTruncate = false; /// <summary>
/// Specifies whether we allow the integer portion of numbers to be truncated.
/// If false and integer digits overflow the field, an exception is thrown.
/// </summary>
private bool mAllowIntegerTruncate = false; //array used to clear decimals, we can clear up to 40 decimals which is much more than is allowed under DBF spec anyway.
//Note: 48 is ASCII code for 0.
private static readonly byte[] mDecimalClear = new byte[] {,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,}; //Warning: do not make this one static because that would not be thread safe!! The reason I have
//placed this here is to skip small memory allocation/deallocation which fragments memory in .net.
private int[] mTempIntVal = { }; //Ascii Encoder
private readonly Encoding encoding = Encoding.ASCII; /// <summary>
/// Column Name to Column Index map
/// </summary>
private readonly Dictionary<string, int> mColNameToConIdx = new Dictionary<string, int>(StringComparer.InvariantCulture); /// <summary>
///
/// </summary>
/// <param name="oHeader">Dbf Header will be locked once a record is created
/// since the record size is fixed and if the header was modified it would corrupt the DBF file.</param>
public DbfRecord(DbfHeader oHeader)
{
mHeader = oHeader;
mHeader.Locked = true; //create a buffer to hold all record data. We will reuse this buffer to write all data to the file.
mData = new byte[mHeader.RecordLength];
mEmptyRecord = mHeader.EmptyDataRecord;
encoding = oHeader.encoding; for (int i = ; i < oHeader.mFields.Count; i++)
mColNameToConIdx[oHeader.mFields[i].Name] = i;
} /// <summary>
/// Set string data to a column, if the string is longer than specified column length it will be truncated!
/// If dbf column type is not a string, input will be treated as dbf column
/// type and if longer than length an exception will be thrown.
/// </summary>
/// <param name="nColIndex"></param>
/// <returns></returns>
public string this[int nColIndex]
{ set
{ DbfColumn ocol = mHeader[nColIndex];
DbfColumn.DbfColumnType ocolType = ocol.ColumnType; //
//if an empty value is passed, we just clear the data, and leave it blank.
//note: test have shown that testing for null and checking length is faster than comparing to "" empty str :)
//------------------------------------------------------------------------------------------------------------
if (string.IsNullOrEmpty(value))
{
//this is like NULL data, set it to empty. i looked at SAS DBF output when a null value exists
//and empty data are output. we get the same result, so this looks good.
Buffer.BlockCopy(mEmptyRecord, ocol.DataAddress, mData, ocol.DataAddress, ocol.Length); }
else
{ //set values according to data type:
//-------------------------------------------------------------
if (ocolType == DbfColumn.DbfColumnType.Character)
{
if (!mAllowStringTruncate && value.Length > ocol.Length)
throw new DbfDataTruncateException("Value not set. String truncation would occur and AllowStringTruncate flag is set to false. To supress this exception change AllowStringTruncate to true."); //BlockCopy copies bytes. First clear the previous value, then set the new one.
Buffer.BlockCopy(mEmptyRecord, ocol.DataAddress, mData, ocol.DataAddress, ocol.Length);
encoding.GetBytes(value, , value.Length > ocol.Length ? ocol.Length : value.Length, mData, ocol.DataAddress); }
else if (ocolType == DbfColumn.DbfColumnType.Number)
{ if (ocol.DecimalCount == )
{ //integers
//---------------------------------- //throw an exception if integer overflow would occur
if (!mAllowIntegerTruncate && value.Length > ocol.Length)
throw new DbfDataTruncateException("Value not set. Integer does not fit and would be truncated. AllowIntegerTruncate is set to false. To supress this exception set AllowIntegerTruncate to true, although that is not recomended."); //clear all numbers, set to [space].
//-----------------------------------------------------
Buffer.BlockCopy(mEmptyRecord, , mData, ocol.DataAddress, ocol.Length); //set integer part, CAREFUL not to overflow buffer! (truncate instead)
//-----------------------------------------------------------------------
int nNumLen = value.Length > ocol.Length ? ocol.Length : value.Length;
encoding.GetBytes(value, , nNumLen, mData, (ocol.DataAddress + ocol.Length - nNumLen)); }
else
{ ///TODO: we can improve perfomance here by not using temp char arrays cDec and cNum,
///simply direcly copy from source string using encoding! //break value down into integer and decimal portions
//--------------------------------------------------------------------------
int nidxDecimal = value.IndexOf('.'); //index where the decimal point occurs
char[] cDec = null; //decimal portion of the number
char[] cNum = null; //integer portion if (nidxDecimal > -)
{
cDec = value.Substring(nidxDecimal + ).Trim().ToCharArray();
cNum = value.Substring(, nidxDecimal).ToCharArray(); //throw an exception if decimal overflow would occur
if (!mAllowDecimalTruncate && cDec.Length > ocol.DecimalCount)
throw new DbfDataTruncateException("Value not set. Decimal does not fit and would be truncated. AllowDecimalTruncate is set to false. To supress this exception set AllowDecimalTruncate to true."); }
else
cNum = value.ToCharArray(); //throw an exception if integer overflow would occur
if (!mAllowIntegerTruncate && cNum.Length > ocol.Length - ocol.DecimalCount - )
throw new DbfDataTruncateException("Value not set. Integer does not fit and would be truncated. AllowIntegerTruncate is set to false. To supress this exception set AllowIntegerTruncate to true, although that is not recomended."); //clear all decimals, set to 0.
//-----------------------------------------------------
Buffer.BlockCopy(mDecimalClear, , mData, (ocol.DataAddress + ocol.Length - ocol.DecimalCount), ocol.DecimalCount); //clear all numbers, set to [space].
Buffer.BlockCopy(mEmptyRecord, , mData, ocol.DataAddress, (ocol.Length - ocol.DecimalCount)); //set decimal numbers, CAREFUL not to overflow buffer! (truncate instead)
//-----------------------------------------------------------------------
if (nidxDecimal > -)
{
int nLen = cDec.Length > ocol.DecimalCount ? ocol.DecimalCount : cDec.Length;
encoding.GetBytes(cDec, , nLen, mData, (ocol.DataAddress + ocol.Length - ocol.DecimalCount));
} //set integer part, CAREFUL not to overflow buffer! (truncate instead)
//-----------------------------------------------------------------------
int nNumLen = cNum.Length > ocol.Length - ocol.DecimalCount - ? (ocol.Length - ocol.DecimalCount - ) : cNum.Length;
encoding.GetBytes(cNum, , nNumLen, mData, ocol.DataAddress + ocol.Length - ocol.DecimalCount - nNumLen - ); //set decimal point
//-----------------------------------------------------------------------
mData[ocol.DataAddress + ocol.Length - ocol.DecimalCount - ] = (byte)'.'; } }
else if (ocolType == DbfColumn.DbfColumnType.Integer)
{
//note this is a binary Integer type!
//---------------------------------------------- ///TODO: maybe there is a better way to copy 4 bytes from int to byte array. Some memory function or something.
mTempIntVal[] = Convert.ToInt32(value);
Buffer.BlockCopy(mTempIntVal, , mData, ocol.DataAddress, ); }
else if (ocolType == DbfColumn.DbfColumnType.Memo)
{
//copy 10 digits...
///TODO: implement MEMO throw new NotImplementedException("Memo data type functionality not implemented yet!"); }
else if (ocolType == DbfColumn.DbfColumnType.Boolean)
{
if (String.Compare(value, "true", true) == || String.Compare(value, "", true) == ||
String.Compare(value, "T", true) == || String.Compare(value, "yes", true) == ||
String.Compare(value, "Y", true) == )
mData[ocol.DataAddress] = (byte)'T';
else if (value == " " || value == "?")
mData[ocol.DataAddress] = (byte)'?';
else
mData[ocol.DataAddress] = (byte)'F'; }
else if (ocolType == DbfColumn.DbfColumnType.Date)
{
//try to parse out date value using Date.Parse() function, then set the value
DateTime dateval;
if (DateTime.TryParse(value, out dateval))
{
SetDateValue(nColIndex, dateval);
}
else
throw new InvalidOperationException("Date could not be parsed from source string! Please parse the Date and set the value (you can try using DateTime.Parse() or DateTime.TryParse() functions)."); }
else if (ocolType == DbfColumn.DbfColumnType.Binary)
throw new InvalidOperationException("Can not use string source to set binary data. Use SetBinaryValue() and GetBinaryValue() functions instead."); else
throw new InvalidDataException("Unrecognized data type: " + ocolType.ToString()); } } get
{
DbfColumn ocol = mHeader[nColIndex];
return new string(encoding.GetChars(mData, ocol.DataAddress, ocol.Length)); }
} /// <summary>
/// Set string data to a column, if the string is longer than specified column length it will be truncated!
/// If dbf column type is not a string, input will be treated as dbf column
/// type and if longer than length an exception will be thrown.
/// </summary>
/// <param name="nColName"></param>
/// <returns></returns>
public string this[string nColName]
{
get
{
if (mColNameToConIdx.ContainsKey(nColName))
return this[mColNameToConIdx[nColName]];
throw new InvalidOperationException(string.Format("There's no column with name '{0}'", nColName));
}
set
{
if (mColNameToConIdx.ContainsKey(nColName))
this[mColNameToConIdx[nColName]] = value;
else
throw new InvalidOperationException(string.Format("There's no column with name '{0}'", nColName));
}
} /// <summary>
/// Get date value.
/// </summary>
/// <param name="nColIndex"></param>
/// <returns></returns>
public DateTime GetDateValue(int nColIndex)
{
DbfColumn ocol = mHeader[nColIndex]; if (ocol.ColumnType == DbfColumn.DbfColumnType.Date)
{
string sDateVal = encoding.GetString(mData, ocol.DataAddress, ocol.Length);
return DateTime.ParseExact(sDateVal, "yyyyMMdd", CultureInfo.InvariantCulture); }
else
throw new Exception("Invalid data type. Column '" + ocol.Name + "' is not a date column."); } /// <summary>
/// Get date value.
/// </summary>
/// <param name="nColIndex"></param>
/// <returns></returns>
public void SetDateValue(int nColIndex, DateTime value)
{ DbfColumn ocol = mHeader[nColIndex];
DbfColumn.DbfColumnType ocolType = ocol.ColumnType; if (ocolType == DbfColumn.DbfColumnType.Date)
{ //Format date and set value, date format is like this: yyyyMMdd
//-------------------------------------------------------------
encoding.GetBytes(value.ToString("yyyyMMdd"), , ocol.Length, mData, ocol.DataAddress); }
else
throw new Exception("Invalid data type. Column is of '" + ocol.ColumnType.ToString() + "' type, not date."); } /// <summary>
/// Clears all data in the record.
/// </summary>
public void Clear()
{
Buffer.BlockCopy(mEmptyRecord, , mData, , mEmptyRecord.Length);
mRecordIndex = -; } /// <summary>
/// returns a string representation of this record.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return new string(encoding.GetChars(mData));
} /// <summary>
/// Gets/sets a zero based record index. This information is not directly stored in DBF.
/// It is the location of this record within the DBF.
/// </summary>
/// <remarks>
/// This property is managed from outside this object,
/// CDbfFile object updates it when records are read. The reason we don't set it in the Read()
/// function within this object is that the stream can be forward-only so the Position property
/// is not available and there is no way to figure out what index the record was unless you
/// count how many records were read, and that's exactly what CDbfFile does.
/// </remarks>
public int RecordIndex
{
get
{
return mRecordIndex;
}
set
{
mRecordIndex = value;
}
} /// <summary>
/// Returns/sets flag indicating whether this record was tagged deleted.
/// </summary>
/// <remarks>Use CDbf4File.Compress() function to rewrite dbf removing records flagged as deleted.</remarks>
/// <seealso cref="CDbf4File.Compress() function"/>
public bool IsDeleted
{
get { return mData[] == '*'; }
set { mData[] = value ? (byte)'*' : (byte)' '; }
} /// <summary>
/// Specifies whether strings can be truncated. If false and string is longer than can fit in the field, an exception is thrown.
/// Default is True.
/// </summary>
public bool AllowStringTurncate
{
get { return mAllowStringTruncate; }
set { mAllowStringTruncate = value; }
} /// <summary>
/// Specifies whether to allow the decimal portion of numbers to be truncated.
/// If false and decimal digits overflow the field, an exception is thrown. Default is false.
/// </summary>
public bool AllowDecimalTruncate
{
get { return mAllowDecimalTruncate; }
set { mAllowDecimalTruncate = value; }
} /// <summary>
/// Specifies whether integer portion of numbers can be truncated.
/// If false and integer digits overflow the field, an exception is thrown.
/// Default is False.
/// </summary>
public bool AllowIntegerTruncate
{
get { return mAllowIntegerTruncate; }
set { mAllowIntegerTruncate = value; }
} /// <summary>
/// Returns header object associated with this record.
/// </summary>
public DbfHeader Header
{
get
{
return mHeader;
}
} /// <summary>
/// Get column by index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public DbfColumn Column(int index)
{
return mHeader[index];
} /// <summary>
/// Get column by name.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public DbfColumn Column(string sName)
{
return mHeader[sName];
} /// <summary>
/// Gets column count from header.
/// </summary>
public int ColumnCount
{
get
{
return mHeader.ColumnCount;
}
} /// <summary>
/// Finds a column index by searching sequentially through the list. Case is ignored. Returns -1 if not found.
/// </summary>
/// <param name="sName">Column name.</param>
/// <returns>Column index (0 based) or -1 if not found.</returns>
public int FindColumn(string sName)
{
return mHeader.FindColumn(sName);
} /// <summary>
/// Writes data to stream. Make sure stream is positioned correctly because we simply write out the data to it.
/// </summary>
/// <param name="osw"></param>
protected internal void Write(Stream osw)
{
osw.Write(mData, , mData.Length); } /// <summary>
/// Writes data to stream. Make sure stream is positioned correctly because we simply write out data to it, and clear the record.
/// </summary>
/// <param name="osw"></param>
protected internal void Write(Stream obw, bool bClearRecordAfterWrite)
{
obw.Write(mData, , mData.Length); if (bClearRecordAfterWrite)
Clear(); } /// <summary>
/// Read record from stream. Returns true if record read completely, otherwise returns false.
/// </summary>
/// <param name="obr"></param>
/// <returns></returns>
protected internal bool Read(Stream obr)
{
return obr.Read(mData, , mData.Length) >= mData.Length;
} protected internal string ReadValue(Stream obr, int colIndex)
{
DbfColumn ocol = mHeader[colIndex];
return new string(encoding.GetChars(mData, ocol.DataAddress, ocol.Length)); } }
}

第三步:创建DBF文件

  public void CreateDBF(string dbfPath, string DBFName)
{
var odbf = new DbfFile(Encoding.GetEncoding());
odbf.Open(Path.Combine(dbfPath, DBFName + ".dbf"), FileMode.Create); //创建列头
odbf.Header.AddColumn(new DbfColumn("YHBH", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("SBBH", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("YHMC", DbfColumn.DbfColumnType.Character, , )); odbf.Header.AddColumn(new DbfColumn("YHZZ", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("SBWZ", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("DH", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("YDDH", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("CBBH", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("CBXH", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("YSXZ", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("SBQD", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("BJSL", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("SBZD", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("SJYS", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("CBRQ", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("SBYXZT", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("SFGS", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("CBBZ", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("SBKJ", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("QYL", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("QFJE", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("YHDJ", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("SCCBRQ", DbfColumn.DbfColumnType.Character, , ));
odbf.Header.AddColumn(new DbfColumn("SCSL", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("SCCJD", DbfColumn.DbfColumnType.Number, , ));
odbf.Header.AddColumn(new DbfColumn("ISUPDATE", DbfColumn.DbfColumnType.Character, , ));

第四步:将数据写入DBF

        var orec = new DbfRecord(odbf.Header) { AllowDecimalTruncate = true };

            foreach (var item in WriteDTOlist)
{
orec[] = item.YHBH;
orec[] = item.SBBH;
orec[] = item.YHMC;
orec[] = item.YHZZ;
orec[] = item.SBWZ;
orec[] = item.CBBH;
orec[] = item.CBXH.ToString();
orec[] = item.YSXZ;
orec[] = item.SBQD.ToString();
orec[] = item.BJSL.ToString();
orec[] = item.SBZD.ToString();
orec[] = item.SJYS.ToString();
orec[] = item.CBRQ;
orec[] = item.SBYXZT;
orec[] = item.SFGS;
orec[] = item.CBBZ;
orec[] = item.QYL.ToString();
orec[] = item.YHDJ.ToString();
odbf.Write(orec, true);
}
odbf.Close();

WriteDTOlist: List<WriteDTO> WriteDTOlist = new List<WriteDTO>();

读取DBF,将DBF转化为Datatable

          /// <summary>
/// 从DBF读取文件到DataTable
/// </summary>
/// <param name="fileName">DBF的完整路径:如E:\2222.dbf</param>
/// <returns></returns>
public static DataTable DbfToDataTable(string fileName)
{
try
{
//返回的结果集
DataTable dt = new DataTable();
//获取一个DBF文件对象
DbfFile dbf = new DbfFile(Encoding.Default);
dbf.Open(fileName, FileMode.Open); //创建DataTable的结构(列名)
DbfHeader dh = dbf.Header;
for (int index = ; index < dh.ColumnCount; index++)
{
dt.Columns.Add(dh[index].Name);
} //加载数据到DataTable里
int i = ;
while (dbf.Read(i) != null)
{
//获取一行
DbfRecord record = dbf.Read(i);
//将改行数据放到DataRow里
DataRow dr = dt.NewRow();
Object[] objs = new Object[record.ColumnCount];
for (int index = ; index < record.ColumnCount; index++)
{
objs[index] = record[index];
}
dr.ItemArray = objs;
dt.Rows.Add(dr);
i++;
}
dbf.Close();
return dt;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
} }

WriteDTO模型:

.NET使用FastDBF写入DBF的更多相关文章

  1. JavaDBF:保存行情实时数据到DBF文件

    JavaDBF.jar其实很早都不再更新了,在日新月异的科技圈算得上远古上神的jar包,早该身归混沌了. 但我们的项目要用到,因为之前做的大宗期货交易行情的分析文件依然是dbf文件,没有办法,还得用 ...

  2. Dbf文件操作

    package cn.com.szhtkj.util; import java.io.File; import java.io.IOException; import java.lang.reflec ...

  3. 《Java知识应用》Java读写DBF文件

    1. 准备: Jar包下载:链接: https://pan.baidu.com/s/1Ikxx-vkw5vSDf9SBUQHBCw 提取码: 7h58 复制这段内容后打开百度网盘手机App,操作更方便 ...

  4. Log Buffer

    Log Buffer 一.Log Buffer的引入 Oracle有一个原则:只要是已经提交的数据,就不会丢失,保证数据库的一致性.这该如何实现?事物提交时,直接写入dbf中,效率是极低的.因为直接写 ...

  5. Buffer Cache

    Buffer Cache Buffer Cache是SGA区中专门用于存放从数据文件中读取的的数据块拷贝的区域.Oracle进程如果发现需要访问的数据块已经在buffer cache中,就直接读写内存 ...

  6. 表数据文件DBF的读取和写入操作

    import sys import csv import struct import datetime import decimal import itertools from cStringIO i ...

  7. 关于FastDBF库读写ArcGis dbf文件的小bug

    该库托管于GitHub,地址:https://github.com/SocialExplorer/FastDBF 贡献者应该都是老外,所以…… 1.解析文件头,字段名部分如果有中文命名字段会出错 在D ...

  8. ArcGis dbf读写——挂接Excel到属性表 C#

    ArcMap提供了挂接Excel表格信息到属性表的功能,但是当数据量较大到以万计甚至十万计的时候这个功能就歇菜了,当然,你可以考虑分段挂接.这个挂接功能只是做了一个表关联,属性记录每个字段的信息需要通 ...

  9. Java读取Level-1行情dbf文件极致优化(1)

    最近架构一个项目,实现行情的接入和分发,需要达到极致的低时延特性,这对于证券系统是非常重要的.接入的行情源是可以配置,既可以是Level-1,也可以是Level-2或其他第三方的源.虽然Level-1 ...

随机推荐

  1. 2019-1-29-WPF-设置输入只能英文

    title author date CreateTime categories WPF 设置输入只能英文 lindexi 2019-1-29 15:8:4 +0800 2018-2-13 17:23: ...

  2. P1025 最大完美度

    题目描述 定义一个字符串的完美度为字符串中所有字符的完美度的和. 现在给你一个只含字母的字符串s, 每一个字母的完美度由你进行分配, 可以分配给一个字母[1,26]中的一个数字作为完美度, 但每个字母 ...

  3. mysql(8.0.16)安装及使用注意事项

    1.安装地址:https://dev.mysql.com/downloads/mysql/ 2.在安装路径:D:\mysql\mysql-8.0.16-winx64(安装时的路径,可自己选择)下面新建 ...

  4. 实现页面查看xml或json数据类似控制台效果

    在前端查看xml或者json数据时,实现在类似与控制台中console的效果. 配合Ant Design的Collapse折叠面板进行展示. Collapse组件的地址:https://ant.des ...

  5. CF809D Hitchhiking in the Baltic States

    CF809D Hitchhiking in the Baltic States CF809D 长度为n的序列{xi},n<=3e5,范围在(li,ri)之间,求LIS最长是多长g(i,l)表示前 ...

  6. luoguP2679 子串

    luoguP2679 子串 个人感觉\(noip\)系列中挺好的一道DP题目. 题面有点难理解. 我们设\(f_{i,j,k,0/1}\)表示\(A\)串前\(i\)个字符,匹配\(B\)串前\(j\ ...

  7. Android利用Fiddler进行网络数据抓包,手机抓包工具汇总,使用mono运行filddler

    Fiddler抓包工具 Fiddler抓包工具很好用的,它可以干嘛用呢,举个简单例子,当你浏览网页时,网页中有段视频非常好,但网站又不提供下载,用迅雷下载你又找不到下载地址,这个时候,Fiddler抓 ...

  8. Visual Studio Team Services使用教程【1】:邀请团队成员

    2017.4.23之后建议朋友看下面的帖子 TFS2017 & VSTS 实战(繁体中文视频) Visual Studio Team Services(VSTS)与敏捷开发ALM实战关键报告( ...

  9. sybase的存储过程编写经验和方法

    1.如果用到其他库的Table或View,务必在当前库中建立View来实现跨库操作,最好不要直接使用“databse.dbo.table_name”,因为sp_depends不能显示出该SP所使用的跨 ...

  10. Kubernetes从私有镜像仓库中拉取镜像

    当我们尝试从私有仓库中拉取镜像时,可能会收到这样提示:requested access to the resource is denied Error response from daemon: pu ...