C# WinForms 使用 OleDb 读取 CSV 和 Excel 数据到 DataGridView 实战

发布时间:2026/10/24 8:49:07
C# WinForms 使用 OleDb 读取 CSV 和 Excel 数据到 DataGridView 实战

简介:这份资源面向使用C#进行数据处理的开发者,尤其是需要将CSV或Excel表格数据导入WinForm程序的初学者与中级程序员,解决文件读取、数据绑定与界面展示的常见问题。包内共1个doc文档,约34KB,以文字讲解配合代码片段的形式,梳理了OleDbConnection、OleDbDataAdapter等核心类的用法。内容围绕连接字符串配置展开,涵盖CSV的HDR、FMT、IMEX等扩展属性含义,以及Excel不同版本提供程序的选择差异,并演示如何将读取结果填充到DataTable、再通过DataGridView自动生成列进行展示。读者可从中获得可直接参考的读取模板、连接参数说明与常见格式判断思路,便于快速套用到自己的项目中。目前已有260人学习,适合作为C#文件读取入门的实践参考。

1. 一个 WinForms 小工具,把 CSV 和 Excel 读进 DataGridView

接手过一个内部数据核对的小工具,需求很朴素:把现场导出的 CSV 和 Excel 表格读出来,直接显示在界面上,方便比对。听起来像是个练手 Demo,但真动手写的时候,坑一个接一个——CSV 的编码、Excel 的版本、连接字符串的写法,每一样都能让程序在别人机器上翻车。这份代码就是围绕这个场景写的:一个 C# WinForms 窗体,用 OleDb 把 CSV 和 Excel 文件读成 DataTable,再绑到 DataGridView 上显示。它适合两类人:一是刚接触 C# 数据读取、想找个能跑通的例子照着改的新手;二是手头有类似小工具需求、需要快速验证 OleDb 方案可行性的开发者。代码不长,但把 CSV 和 Excel 两条读取路径都覆盖了,改一改就能用到自己的项目里。

2. 技术选型与整体结构:为什么用 OleDb 而不是别的方案

2.1 这个项目到底在做什么

从代码结构看,整个逻辑都塞在窗体的构造函数里,没有分层,没有单独的帮助类。核心流程就三步:判断文件扩展名是 CSV 还是 Excel,拼出对应的 OleDb 连接字符串,用 OleDbDataAdapter 把数据填进 DataTable,最后绑到 DataGridView。这种写法在小型工具里很常见,优点是直观,缺点是扩展性差——如果后面要加导出、筛选、分页,就得重构。

代码里定义了一个CSVORXLS窗体类,命名空间是WindowsApplication1,引用了System.Data.OleDb。这个引用是关键,OleDb 是 ADO.NET 里访问 OLE DB 数据源的组件,它能把文本文件和 Excel 文件当成“数据库”来查。你不需要装 Office,但需要对应的 OLE DB 提供程序,这一点后面避坑章节会细说。

2.2 为什么选 OleDb 而不是其他方案

读 CSV 和 Excel,C# 里至少有四五种路子:纯文件流自己解析、用第三方库如 CsvHelper 或 EPPlus、用 Excel Interop、用 OleDb。这个项目选了 OleDb,原因很实际——它同时覆盖 CSV 和 Excel,不用引第三方包,代码量少。对于内部小工具,这个取舍是合理的。

但 OleDb 有它的脾气。它本质上是把文件当数据库查,所以你要写 SQL,要指定“表名”。CSV 的表名就是文件名,Excel 的表名是工作表名加$。连接字符串里的Extended Properties决定了它怎么解析文件,参数写错一个,读出来的数据就可能少列、乱码或者直接报错。

下面这张表把两条路径的关键参数列出来,方便对照:

参数CSV 路径Excel 路径
ProviderMicrosoft.Jet.OLEDB.4.0Microsoft.Jet.OLEDB.4.0
Data Source文件所在目录文件完整路径
Extended Propertiestext;HDR=YES;FMT=DelimitedExcel 8.0;HDR=YES;IMEX=1
SQL 表名文件名(含扩展名)[工作表名$]
列名处理HDR=YES 取首行HDR=YES 取首行

注意 CSV 的 Data Source 是目录不是文件,这是最容易写错的地方。代码里用LastIndexOf("\\")截出目录路径,再把文件名拼到 SQL 里,就是这个原因。

2.3 目录结构与代码组织

这个项目没有复杂的目录结构,就是一个标准的 WinForms 工程:

WindowsApplication1/ ├── Program.cs # 入口,启动主窗体 ├── CSVORXLS.cs # 主窗体逻辑,读取和显示都在这 ├── CSVORXLS.Designer.cs # 设计器生成的控件代码 ├── CSVORXLS.resx # 窗体资源 └── Properties/ ├── AssemblyInfo.cs ├── Resources.resx └── Settings.settings

核心逻辑全在CSVORXLS.cs的构造函数里。这种把所有代码写进构造函数的方式,只适合一次性验证。如果你要在此基础上做二次开发,第一件事应该是把读取逻辑抽成一个独立的类,比如FileDataReader,让窗体只负责调用和显示。这样后面加新格式、改连接参数,都不用动界面代码。

3. 核心读取逻辑拆解:CSV 和 Excel 两条路径怎么走

3.1 CSV 读取:连接字符串和 SQL 的写法

CSV 分支的代码不长,但每一行都有讲究。先看完整片段:

string csvDir = @"C:\test1.xls"; // 实际使用时改成你的 CSV 路径 if (csvDir.Trim().ToUpper().EndsWith("CSV")) { int ipos = csvDir.LastIndexOf("\\"); string filePath = csvDir.Substring(0, ipos + 1); string connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + filePath + ";Extended Properties='text;HDR=YES;FMT=Delimited;'"; string commandText = "select * from " + csvDir.Replace(filePath, ""); OleDbConnection olconn = new OleDbConnection(connStr); olconn.Open(); OleDbDataAdapter odp = new OleDbDataAdapter(commandText, olconn); DataTable dt = new DataTable(); odp.Fill(dt); dataGridView1.AutoGenerateColumns = true; dataGridView1.DataSource = dt.DefaultView; }

逐段说明。csvDir.Trim().ToUpper().EndsWith("CSV")是判断扩展名,先去掉首尾空格再转大写,避免大小写和空格导致的误判。LastIndexOf("\\")找到最后一个反斜杠的位置,Substring(0, ipos + 1)截出目录部分,注意+1是为了保留反斜杠。连接字符串里Data Source必须是目录,不是文件本身,这是 OleDb 读文本文件的硬性要求。

Extended Properties里的三个参数:text表示文本文件驱动,HDR=YES表示首行是列名,FMT=Delimited表示用分隔符分隔字段。如果你的 CSV 没有列名,把HDR改成NO,然后在绑定时手动设置列头。commandText里的表名就是文件名,代码用Replace把目录部分去掉,剩下test1.csv这样的文件名。

提示:CSV 文件如果包含中文,建议保存为 UTF-8 带 BOM 或 GB2312,具体取决于系统区域设置。OleDb 文本驱动对编码敏感,读出来乱码多半是编码不匹配。

3.2 Excel 读取:Provider 和表名的坑

Excel 分支的代码结构类似,但参数不同:

else { string connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + csvDir + ";Extended Properties='Excel 8.0;HDR=YES;IMEX=1;';"; string commandText = "select * from [test1$]"; OleDbConnection olconn = new OleDbConnection(connStr); olconn.Open(); OleDbDataAdapter odp = new OleDbDataAdapter(commandText, olconn); DataTable dt = new DataTable(); odp.Fill(dt); dataGridView2.DataSource = dt.DefaultView; }

Excel 的Data Source是文件完整路径,不是目录。Extended Properties里Excel 8.0对应的是 .xls 格式,如果你要读 .xlsx,需要换成Excel 12.0并配合 ACE 提供程序。IMEX=1是关键参数,它让驱动把混合类型的数据当作文本读,避免数字和文本混排时丢数据。

commandText里的[test1$]是工作表名,$不能少,方括号用于处理工作表名里的特殊字符。如果你的工作表叫Sheet1,就写[Sheet1$]。这里代码写死了test1$,实际使用时需要根据文件动态获取工作表名,或者让用户选择。

3.3 数据绑定:DataTable 到 DataGridView

两条路径最后都落到 DataTable,再绑到 DataGridView。AutoGenerateColumns = true让控件根据 DataTable 的列自动生成列,DataSource = dt.DefaultView把数据源设上去。DefaultView返回的是 DataView,支持排序和筛选,比直接绑 DataTable 灵活。

如果你用的是HDR=NO的 CSV,自动生成的列名会是 F1、F2 这种,需要在绑定后手动改:

dataGridView2.DataSource = dt.DefaultView; dataGridView2.Columns[0].HeaderText = "编号"; dataGridView2.Columns[1].HeaderText = "高度"; dataGridView2.Columns[2].HeaderText = "宽度";

代码里注释掉的这几行就是这个用途。另外SortMode = NotSortable可以禁用某列的排序,避免用户点击列头时因为数据类型不一致报错。

4. 避坑与常见问题:OleDb 读文件的五个血泪教训

4.1 报错“未在本地计算机上注册 Microsoft.Jet.OLEDB.4.0”

现象:程序在开发机上跑得好好的,拷到别人机器上直接弹异常,提示提供程序未注册。

原因:Jet 提供程序是 32 位的,如果你的项目编译成 64 位,或者目标机器没装对应的驱动,就会报这个错。

解决:把项目的目标平台改成 x86,或者在 64 位系统上改用Microsoft.ACE.OLEDB.12.0。ACE 提供程序有 32 位和 64 位两个版本,装的时候要和项目平台匹配。我一般会在项目属性里把平台目标固定为 x86,省得后面折腾。

4.2 CSV 读出来只有一列,或者列错位

现象:明明 CSV 里有多列,读出来全挤在第一列,或者列对不上。

原因:分隔符没被正确识别。FMT=Delimited默认用逗号,如果你的文件用分号或制表符,就解析不了。另外如果文件里有引号包裹的字段,处理不当也会错位。

解决:在Extended Properties里加FMT=Delimited(;)指定分隔符,或者用FMT=TabDelimited。如果字段里有引号,确保 CSV 是标准格式,必要时先用文本编辑器检查一遍。

4.3 Excel 读出来少数据,或者数字变成空

现象:Excel 里明明有数据,读出来某些单元格是空的,尤其是数字和文本混排的列。

原因:OleDb 驱动会按前几行推断列类型,如果前几行是数字,后面出现文本,文本就会被当成空值。

解决:在连接字符串里加IMEX=1,强制按文本读取。代码里 Excel 分支已经加了这个参数,CSV 分支如果遇到类似问题也可以加。注意IMEX=1需要配合HDR=YES使用。

4.4 表名写错导致“找不到表”

现象:Excel 文件明明有工作表,SQL 里写[Sheet1$]却报找不到。

原因:工作表名可能不是 Sheet1,或者有空格、特殊字符。CSV 的表名是文件名,如果文件名里有空格或特殊字符,也可能出问题。

解决:先用OleDbConnection.GetOleDbSchemaTable获取工作表列表,再动态拼 SQL。CSV 的话,确保文件名和 SQL 里的表名完全一致,包括扩展名。

4.5 文件被占用,无法读取

现象:程序运行时报“文件正被另一进程使用”。

原因:Excel 文件被 Office 打开着,或者上一次的连接没有正确释放。

解决:确保OleDbConnection用完就Close和Dispose,最好用using包起来。另外提醒用户读取前先关闭 Excel。代码里没有显式释放连接,这是个隐患,二次开发时建议补上。

5. 二次开发改造:从能跑到好用

5.1 把读取逻辑抽成独立类

现在所有代码都在构造函数里,改起来牵一发动全身。第一步应该是抽一个FileDataReader类:

public class FileDataReader { public DataTable ReadCsv(string filePath, bool hasHeader = true) { string dir = Path.GetDirectoryName(filePath) + "\\"; string fileName = Path.GetFileName(filePath); string hdr = hasHeader ? "Yes" : "No"; string connStr = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={dir};" + $"Extended Properties='text;HDR={hdr};FMT=Delimited;'"; string sql = $"select * from [{fileName}]"; using (var conn = new OleDbConnection(connStr)) using (var adapter = new OleDbDataAdapter(sql, conn)) { var dt = new DataTable(); adapter.Fill(dt); return dt; } } public DataTable ReadExcel(string filePath, string sheetName) { string connStr = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={filePath};" + $"Extended Properties='Excel 8.0;HDR=YES;IMEX=1;'"; string sql = $"select * from [{sheetName}$]"; using (var conn = new OleDbConnection(connStr)) using (var adapter = new OleDbDataAdapter(sql, conn)) { var dt = new DataTable(); adapter.Fill(dt); return dt; } } }

这样窗体里只需要调用reader.ReadCsv(path),逻辑清晰,也方便加单元测试。using语句确保连接和适配器用完就释放,避免文件占用问题。

5.2 动态获取 Excel 工作表名

写死[test1$]不现实,用户不可能每次都把工作表命名成 test1。可以用 schema 查询拿到所有工作表:

public List<string> GetExcelSheets(string filePath) { var sheets = new List<string>(); string connStr = $"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={filePath};" + $"Extended Properties='Excel 8.0;HDR=YES;IMEX=1;'"; using (var conn = new OleDbConnection(connStr)) { conn.Open(); var schema = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null); foreach (DataRow row in schema.Rows) { string name = row["TABLE_NAME"].ToString(); if (name.EndsWith("$")) sheets.Add(name.TrimEnd('$')); } } return sheets; }

拿到列表后绑到一个 ComboBox,用户选哪个就读哪个。这个改动不大,但体验提升明显。

5.3 支持 .xlsx 和更多格式

代码里用的是Excel 8.0,只能读 .xls。要读 .xlsx,把 Provider 换成Microsoft.ACE.OLEDB.12.0,Extended Properties 换成Excel 12.0。注意 ACE 提供程序需要单独安装,而且分 32 位和 64 位。如果目标机器不方便装驱动,可以考虑用 EPPlus 或 NPOI 这类纯托管库,不依赖系统组件,部署更省心。

5.4 验证改造效果的方法

改完之后怎么确认没改坏?我一般会准备三个测试文件:一个标准 CSV(带列名、逗号分隔)、一个无列名 CSV、一个多工作表的 .xls。分别跑一遍,看 DataGridView 里的列名、行数、数据类型对不对。再故意把文件用 Excel 打开着,测试连接释放是否正常。最后把程序拷到一台没装 Office 的机器上跑,确认提供程序依赖没问题。

从那以后我每次写文件读取的代码,都会先把连接字符串单独拎出来做配置,不再写死在代码里。因为现场环境千差万别,今天能跑的字符串,明天换台机器就可能报错。希望这些经验能帮到你,少走几个弯路。

本文还有配套的精品资源,点击获取