☰
C# 读取 CSV 与 Excel 实战:连接字符串、IMEX 与 DataGridView 绑定避坑指南
2026/10/15 8:10:38 网站建设 项目流程

简介:这份资源面向具备一定C#基础的开发者,聚焦于使用C#读取CSV与Excel文件这一常见数据处理需求,帮助解决日常开发中表格数据导入、解析与展示的问题。资源包内含1个doc文档,压缩包大小约34KB,以文档形式系统梳理了相关实现思路与代码示例。文档围绕OleDbConnection与OleDbDataAdapter两类核心对象展开,讲解了CSV文件连接字符串的构造方式,包括Provider、Data Source与Extended Properties等关键参数的设置,并说明了HDR与FMT属性的作用;同时介绍了如何通过OleDbDataAdapter的Fill方法将数据填充至DataTable,再借助DataGridView控件完成数据的自动列生成与界面展示。针对Excel文件,文档也给出了不同的连接字符串与提供程序配置方案。目前已有260人学习该资源,适合需要快速掌握C#表格文件读取技巧、对照代码理解连接与适配器用法的开发者参考。

1. 从一段“能跑但不敢改”的代码说起:C# 读取 CSV 与 EXCEL 的真实分界线

很多人第一次在 C# 里读表格文件,都是从一段类似OleDbConnection的代码开始的。它短、能跑、直接往DataGridView里灌数据,看起来像捡到了宝。但真正放到项目里,问题立刻冒出来:CSV 和 EXCEL 到底该不该走同一套连接字符串?HDR=YES和IMEX=1同时出现时谁说了算?为什么本地跑得好好的,换台机器就报“未注册提供程序”?这篇笔记就围绕一份典型的 C# 读取 CSV/EXCEL 源码,把连接字符串、SQL 语句、DataTable 填充和 DataGridView 绑定这几条线拆开讲。适合手里已经有一份类似代码、想把它改稳、改到能进生产环境的 C# 开发者,也适合刚接触 ADO.NET 读写表格文件、不想在玄学报错里反复翻车的新手。

2. 连接字符串拆解:Jet 与 ACE 的选型、HDR 与 IMEX 的真实作用

2.1 为什么 CSV 和 EXCEL 的连接字符串长得不一样

先看 CSV 分支里那行关键代码:

string connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + filePath + ";Extended Properties='text;HDR=YES;FMT=Delimited;'";

这里Provider用的是Microsoft.Jet.OLEDB.4.0,它是 32 位时代的 OLE DB 提供程序,对纯文本目录的支持非常直接。注意Data Source给的是目录,不是单个 CSV 文件。这是很多人第一次踩坑的地方:CSV 被当成一个“文本数据库”,目录是库,文件是表。所以后面commandText才写成select * from test1.csv这种形式,而不是直接指向文件路径。

EXCEL 分支则不同:

string connStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + csvDir + ";Extended Properties='Excel 8.0;HDR=YES;IMEX=1;'";

Data Source这里给的是具体文件,Extended Properties里写的是Excel 8.0。Excel 8.0对应的是较老的 .xls 格式,如果你拿它去读 .xlsx,常见结果就是“外部表不是预期的格式”。所以选型上要记住一条硬边界:Jet 提供程序对 .xls 友好,ACE 提供程序对 .xlsx 友好。原文里 EXCEL 分支用的是 Jet,如果你的文件是 .xlsx,要么换 ACE,要么先另存为 .xls,别硬扛。

2.2 HDR 和 IMEX 到底在控制什么

HDR=YES表示第一行是列名,读进来之后 DataTable 的列名就是表头;HDR=NO则会把第一行也当数据,列名变成 F1、F2 这种系统名。原文注释里写得很清楚,有列名用 YES,没列名用 NO。但真正容易翻车的是IMEX=1。

IMEX是 ImportMixedTypes 的缩写,IMEX=1告诉提供程序:当某一列里混着数字、日期、字符串时,按文本处理。不加它,Excel 驱动会自己“猜”类型,前 8 行如果是数字,后面突然来个“暂无”,那一格就可能变成空值。这个行为在数据量小的时候看不出来,数据一多就是血泪经验。所以读 EXCEL 时,只要列里可能混类型,IMEX=1基本是必加项。

CSV 分支里FMT=Delimited表示按分隔符解析,默认是逗号。如果你的 CSV 是分号或竖线分隔,需要额外加FMT=Delimited(;)这类写法,否则整行会被当成一列。原文注释里提到“FMT=Delimited 是否有分隔符如,|”,说的就是这个点。

提示:CSV 的Data Source是目录,EXCEL 的Data Source是文件,这两者写反了,报错信息通常不会直接告诉你原因。

2.3 路径拼接与 SQL 语句的隐藏依赖

原文里有一段路径处理:

int ipos = csvDir.LastIndexOf("\\"); string filePath = csvDir.Substring(0, ipos + 1); string commandText = "select * from " + csvDir.Replace(filePath, "");

逻辑是:从完整路径里截出目录部分,再用Replace把目录去掉,剩下的就是文件名,拼成select * from 文件名。这段代码能跑,但依赖两个前提:路径里用的是反斜杠,且文件名不重复出现在目录名里。如果目录叫C:\csv\csv\,Replace会把目录里的csv也替掉,SQL 就废了。更稳的写法是用Path.GetDirectoryName和Path.GetFileName,这也是我后来改这类代码时的固定动作。

3. 从 OleDbDataAdapter 到 DataGridView:填充、绑定与列名处理

3.1 DataTable 填充的完整链路

连接对象打开之后,核心就三行:

OleDbDataAdapter odp = new OleDbDataAdapter(commandText, olconn); DataTable dt = new DataTable(); odp.Fill(dt);

OleDbDataAdapter在这里扮演的是“查询执行 + 结果搬运”的角色。Fill方法会把结果集写进DataTable,同时根据结果集的元数据自动建列。这里有个细节:Fill内部会自己打开和关闭连接,如果你已经手动Open了,它不会重复打开,但用完不会帮你关。所以olconn最好放在using里,或者至少在Fill之后显式Close。原文没有写关闭,短时间跑一次没事,循环读多个文件时连接池会被占满。

参数上,commandText就是那条select * from ...,olconn是已经配置好连接字符串的连接对象。如果 CSV 没有列名,HDR=NO时 DataTable 的列名会是 F1、F2,这时候绑定到 DataGridView 会显示成系统列名,需要手动改HeaderText,原文注释里也给了对应写法。

3.2 DataGridView 绑定的两种姿势

有列名时,原文用的是:

dataGridView1.AutoGenerateColumns = true; dataGridView1.DataSource = dt.DefaultView;

AutoGenerateColumns = true让控件根据 DataTable 的列自动生成列,省事,但列的顺序、宽度、标题都不可控。没有列名时,原文注释里换成了手动指定:

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

这里SortMode设成NotSortable是为了防止用户点击列头排序时,DataTable 的默认视图和绑定源不同步导致数据错位。手动改HeaderText则是因为HDR=NO时列名没有业务含义。两种姿势没有绝对优劣,有表头就用自动,没表头就手动,关键是别在自动生成之后再逐列去改,那样容易和自动生成的列索引对不上。

3.3 一个容易忽略的边界:空值和类型推断

CSV 里空单元格读进来通常是DBNull.Value,绑定到 DataGridView 显示为空,这没问题。但如果后续要拿 DataTable 做计算,直接Convert.ToDouble就会炸。常见做法是在Fill之后遍历一遍,把关键列的空值替换成默认值,或者用dt.Columns["高度"].DataType先看驱动推断成了什么类型。EXCEL 那边因为IMEX=1的存在,混类型列会被当成字符串,取数时统一按字符串处理再转换,反而比依赖驱动推断更稳。

4. 避坑与排查:连接字符串、驱动版本和路径的五个真实翻车点

4.1 现象:报“未在本地计算机上注册 Microsoft.Jet.OLEDB.4.0 提供程序”

原因:项目编译成了 64 位,而 Jet 提供程序只有 32 位版本。这是最经典的翻车点,代码本身没错,平台目标错了。

解决:在项目属性里把目标平台改成 x86,或者改用Microsoft.ACE.OLEDB.12.0并安装对应位数的驱动。改平台比换驱动快,但换驱动能顺带支持 .xlsx。

4.2 现象:读 .xlsx 时报“外部表不是预期的格式”

原因:连接字符串里写的是Excel 8.0,这是给 .xls 用的,.xlsx 需要Excel 12.0或更高,并且提供程序要换成 ACE。

解决:把Extended Properties改成'Excel 12.0;HDR=YES;IMEX=1;',Provider改成Microsoft.ACE.OLEDB.12.0。如果机器上没装 ACE 驱动,这一步会继续报“未注册”,需要先补驱动。

4.3 现象:CSV 读进来只有一列,整行挤在一起

原因:FMT=Delimited默认按逗号分隔,但文件实际用的是分号、制表符或竖线。

解决:在FMT里显式指定分隔符,比如FMT=Delimited(;)。另外要确认Data Source给的是目录而不是文件,给成文件时驱动可能直接报错或返回空结果。

4.4 现象:EXCEL 里明明是数字,读进来变成空或字符串

原因:IMEX没设或设成了 0,驱动按前几行猜类型,后面类型不一致的单元格被置空。

解决:连接字符串里加IMEX=1,让混类型列统一按文本处理。代价是数字列也会变成字符串,取数时自己做类型转换。

4.5 现象:SQL 语句里的表名不对,报“找不到表”

原因:CSV 的表名是文件名,EXCEL 的表名是工作表名加$,比如[test1$]。工作表名里有空格或特殊字符时,还需要用方括号包起来。

解决:CSV 用Path.GetFileName拿文件名拼 SQL;EXCEL 先确认工作表名,写成[工作表名$]。如果工作表名不确定,可以先查GetOleDbSchemaTable拿到所有表名再拼。

5. 进阶技巧:把这段代码改成能进项目的读取工具

5.1 用 Path API 替换字符串截取

原文用LastIndexOf和Substring拼路径,能跑但脆。改成下面这样,目录和文件名各归各的,不再依赖Replace:

string fullPath = @"C:\test1.xls"; string dir = Path.GetDirectoryName(fullPath); // C:\ string fileName = Path.GetFileName(fullPath); // test1.xls string ext = Path.GetExtension(fullPath).ToUpper(); // .XLS

Path.GetDirectoryName返回不带末尾反斜杠的目录,拼连接字符串时自己补一个\。Path.GetExtension拿到的扩展名带点,统一转大写再判断,比EndsWith("CSV")更不容易被大小写和空格干扰。

5.2 把连接字符串抽成配置方法

CSV 和 EXCEL 各写一个方法,返回连接字符串,调用处只传路径:

string BuildCsvConnStr(string dir) { return "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + dir + ";Extended Properties='text;HDR=YES;FMT=Delimited;'"; } string BuildExcelConnStr(string file) { return "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + file + ";Extended Properties='Excel 12.0;HDR=YES;IMEX=1;'"; }

这样改分隔符、换提供程序、调 IMEX 都只动一个地方。方法里不要拼 SQL,SQL 由调用方根据文件类型单独构造,职责分开。

5.3 读取结果做一次列名和空值清洗

不管 CSV 还是 EXCEL,Fill之后统一走一遍清洗:

foreach (DataColumn col in dt.Columns) { if (string.IsNullOrWhiteSpace(col.Caption)) col.Caption = col.ColumnName; } foreach (DataRow row in dt.Rows) { for (int i = 0; i < dt.Columns.Count; i++) { if (row[i] == DBNull.Value) row[i] = string.Empty; } }

Caption是显示名,ColumnName是内部名,绑定到 DataGridView 时优先显示Caption。把空值统一成空字符串,后续做字符串拼接或导出时不用到处判DBNull。这一步看起来多余,但真正进项目后,能省掉大量“为什么这里显示空白”的排查时间。

5.4 验证读取是否完整的三个检查点

第一,看dt.Rows.Count和源文件行数是否对得上,CSV 有表头时源文件行数减一。第二,看dt.Columns.Count和预期列数是否一致,少列通常是分隔符或HDR设错。第三,抽一行混类型的数据,确认IMEX=1生效后该列是字符串而不是空。这三个检查点跑一遍,基本能覆盖八成读取异常。

从那以后我每次接这类表格读取需求,都强制先跑一遍路径解析和连接字符串构造,再拿三行测试数据验证列数和空值,最后才往 DataGridView 上绑。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询