VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • CSV文件处理——C#编写销售数据导入导出

第一部分:C#基础入门
CSV文件处理——销售数据导入导出
实例介绍
CSV(逗号分隔值) 是企业中最常用的轻量级数据交换格式——它比Excel简单(无需解析复杂的二进制结构),比XML/JSON更省空间,跨平台兼容性极强(几乎所有系统、工具都支持)。销售数据的导入导出是典型场景:比如将ERP系统的销售记录导出为CSV给vb.net教程C#教程python教程SQL教程access 2010教程
财务部门,或从经销商提供的CSV中导入销售数据到自己的系统。
本节不依赖任何第三方库(如CsvHelper),用原生C# IO+正则表达式实现CSV的完整处理,带你掌握CSV的标准格式规则(如特殊字符转义)、导入导出的核心逻辑,以及如何处理实际场景中的异常(如格式错误、编码问题)。
需求分析

  1. 业务场景
    模拟销售数据的双向流转:
    导出:将内存中的SalesRecord列表(含订单ID、产品名称、销售数量、单价、销售日期、客户名称)导出为CSV文件,支持处理含逗号、引号的特殊字段;
    导入:从CSV文件解析出SalesRecord列表,正确处理带引号的字段、日期/数值类型转换,兼容UTF-8/GBK编码;
    异常处理:捕获文件不存在、格式错误(如字段数不匹配、日期格式错误)等问题,返回友好提示。
  2. 核心规则(遵循RFC 4180标准)
    字段间用逗号分隔;
    第一行可选表头;
    字段含逗号/引号时,需用双引号包裹;
    字段内的双引号需转义为两个双引号(如He said "Hello" → "He said ""Hello""");
    行结束符为CRLF( )。
    代码实现
    csharp
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using System.Text;
	using System.Text.RegularExpressions;
	
	namespace CsvSalesDemo
	{
	// ==================== 1. 销售记录模型 ====================
	public class SalesRecord
	{
	public string OrderId { get; set; } = string.Empty;
	public string ProductName { get; set; } = string.Empty;
	public int Quantity { get; set; }
	public decimal UnitPrice { get; set; }
	public DateTime SaleDate { get; set; }
	public string CustomerName { get; set; } = string.Empty;
	}
	
	// ==================== 2. CSV处理工具类 ====================
	public static class CsvSalesHandler
	{
	// CSV字段分割正则(处理带引号的字段)
	private static readonly Regex _csvFieldRegex = new Regex(
	@"(""([^""]|""""")*""|[^,]*)(?=,|$)", 
	RegexOptions.Compiled | RegexOptions.ExplicitCapture
	);
	
	/// <summary>
	/// 导出销售记录到CSV文件
	/// </summary>
	/// <param name="records">销售记录列表</param>
	/// <param name="filePath">文件路径</param>
	/// <param name="encoding">编码(默认UTF-8)</param>
	public static void ExportToCsv(List<SalesRecord> records, string filePath, Encoding? encoding = null)
	{
	encoding ??= Encoding.UTF8;
	var lines = new List<string>();
	
	// 1. 添加表头
	lines.Add("OrderId,ProductName,Quantity,UnitPrice,SaleDate,CustomerName");
	
	// 2. 添加数据行
	foreach (var record in records)
	{
	var fields = new List<string>
	{
	EscapeCsvField(record.OrderId),
	EscapeCsvField(record.ProductName),
	record.Quantity.ToString(),
	record.UnitPrice.ToString("F2"),
	record.SaleDate.ToString("yyyy-MM-dd"),
	EscapeCsvField(record.CustomerName)
	};
	lines.Add(string.Join(",", fields));
	}
	
	// 3. 写入文件(用CRLF换行)
	File.WriteAllLines(filePath, lines, encoding);
	Console.WriteLine($"✅ 导出成功:{filePath}(编码:{encoding.EncodingName})");
	}
	
	/// <summary>
	/// 从CSV文件导入销售记录
	/// </summary>
	/// <param name="filePath">文件路径</param>
	/// <param name="encoding">编码(默认UTF-8)</param>
	/// <returns>销售记录列表</returns>
	public static List<SalesRecord> ImportFromCsv(string filePath, Encoding? encoding = null)
	{
	encoding ??= Encoding.UTF8;
	var records = new List<SalesRecord>();
	
	// 1. 检查文件存在
	if (!File.Exists(filePath))
	{
	throw new FileNotFoundException("CSV文件不存在", filePath);
	}
	
	// 2. 读取所有行(跳过表头)
	var lines = File.ReadAllLines(filePath, encoding).Skip(1).ToList();
	if (lines.Count == 0)
	{
	Console.WriteLine("⚠️ CSV文件无数据行");
	return records;
	}
	
	// 3. 解析每一行
	for (int i = 0; i < lines.Count; i++)
	{
	var line = lines[i].Trim();
	if (string.IsNullOrEmpty(line)) continue;
	
	try
	{
	// 分割字段(处理带引号的情况)
	var fields = ParseCsvLine(line);
	if (fields.Count != 6)
	{
	throw new FormatException($"字段数不匹配(预期6个,实际{fields.Count}个)");
	}
	
	// 转换数据类型
	records.Add(new SalesRecord
	{
	OrderId = fields[0],
	ProductName = fields[1],
	Quantity = int.TryParse(fields[2], out var qty) ? qty : 0,
	UnitPrice = decimal.TryParse(fields[3], out var price) ? price : 0m,
	SaleDate = DateTime.TryParse(fields[4], out var date) ? date : DateTime.MinValue,
	CustomerName = fields[5]
	});
	}
	catch (Exception ex)
	{
	Console.WriteLine($"❌ 解析第{i+2}行失败:{ex.Message}(内容:{line})");
	}
	}
	
	Console.WriteLine($"✅ 导入完成:共{records.Count}条有效记录");
	return records;
	}
	
	/// <summary>
	/// 转义CSV字段(处理逗号、引号)
	/// </summary>
	private static string EscapeCsvField(string field)
	{
	if (string.IsNullOrEmpty(field)) return string.Empty;
	// 若字段含逗号、引号或换行,需用双引号包裹
	if (field.Contains(',') || field.Contains('"') || field.Contains('
') || field.Contains('
'))
	{
	// 引号转义为两个引号
	return $""{field.Replace(""", """")}"";
	}
	return field;
	}
	
	/// <summary>
	/// 解析CSV行(提取字段,处理带引号的情况)
	/// </summary>
	private static List<string> ParseCsvLine(string line)
	{
	var fields = new List<string>();
	var matches = _csvFieldRegex.Matches(line);
	
	foreach (Match match in matches)
	{
	var field = match.Value;
	// 去除前后引号,并转义回单个引号
	if (field.StartsWith('"') && field.EndsWith('"'))
	{
	field = field.Substring(1, field.Length - 2).Replace("""", """);
	}
	fields.Add(field);
	}
	return fields;
	}
	}
	
	// ==================== 3. 测试程序 ====================
	class Program
	{
	static void Main(string[] args)
	{
	// 测试数据(含特殊字符)
	var testRecords = new List<SalesRecord>
	{
	new() { OrderId = "ORD2025001", ProductName = "Laptop, Pro", Quantity = 2, UnitPrice = 9999.00m, SaleDate = new DateTime(2025, 1, 15), CustomerName = "张三"先生" },
	new() { OrderId = "ORD2025002", ProductName = "Mouse", Quantity = 5, UnitPrice = 129.00m, SaleDate = new DateTime(2025, 1, 16), CustomerName = "李四" }
	};
	
	// 1. 导出测试
	string csvPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sales.csv");
	CsvSalesHandler.ExportToCsv(testRecords, csvPath);
	
	// 2. 导入测试
	Console.WriteLine("
=== 导入结果 ===");
	var importedRecords = CsvSalesHandler.ImportFromCsv(csvPath);
	foreach (var record in importedRecords)
	{
	Console.WriteLine($"订单ID:{record.OrderId} | 产品:{record.ProductName} | 数量:{record.Quantity} | 日期:{record.SaleDate:yyyy-MM-dd}");
	}
	}
	}
	}

逐行讲解

  1. 销售记录模型(SalesRecord)
    包含销售场景的核心字段:订单ID、产品名称、数量、单价、销售日期、客户名称;
    所有字符串字段默认初始化空字符串,避免空引用异常。
  2. CSV处理工具类(CsvSalesHandler)
    (1)导出方法(ExportToCsv)
    编码处理:默认UTF-8,支持传入自定义编码(如GBK);
    表头生成:第一行固定为字段名,与模型属性对应;
    字段转义:EscapeCsvField方法处理特殊字符——若字段含逗号、引号或换行,用双引号包裹,引号转义为两个引号(符合RFC 4180标准);
    写入文件:用File.WriteAllLines写入,自动处理换行(CRLF)。
    (2)导入方法(ImportFromCsv)
    文件检查:先判断文件是否存在,避免FileNotFoundException;
    跳过表头:用Skip(1)跳过第一行表头;
    行解析:ParseCsvLine方法通过正则表达式提取字段,处理带引号的情况(正则匹配规则:要么是带引号的字段,要么是不含逗号的普通字段);
    类型转换:用TryParse安全转换数值和日期,避免格式错误导致整个导入失败;
    异常捕获:针对每一行单独捕获异常,打印错误信息但不中断整个导入流程。
    (3)核心辅助方法
    EscapeCsvField:转义特殊字段,确保导出的CSV格式正确;
    ParseCsvLine:用正则表达式解决CSV字段分割的痛点(直接Split(',')会错误分割引号内的逗号)。
  3. 测试程序
    特殊字符测试:第一条记录的产品名称含逗号(Laptop, Pro),客户名称含引号(张三"先生),验证导出导入是否正确处理;
    流程验证:导出后立即导入,对比原始数据与导入结果是否一致。
    基础知识拓展
  4. CSV标准格式(RFC 4180)
规则 说明
分隔符 逗号(,)
换行符 CRLF( )
特殊字段 含逗号、引号或换行的字段必须用双引号包裹
引号转义 字段内的双引号需替换为两个双引号("")
表头 可选,若存在则为第一行
  1. 原生IO vs 第三方库(如CsvHelper)
    | 方案 | 优点 | 缺点 |
    | ---- | ---- | ---- |
    | 原生IO | 轻量无依赖、灵活可控 | 需手动处理转义、类型转换、异常;代码量大 |
    | CsvHelper | 强大的映射功能(模型与CSV字段自动匹配)、支持验证、分页、大文件处理 | 需要安装NuGet包;学习成本略高 |

  2. 编码问题
    UTF-8:跨平台首选,建议带BOM(避免部分工具乱码);
    GBK:适合国内传统系统(如老版Excel),需指定Encoding.GetEncoding("GBK");
    注意:导入时需与导出编码一致,否则会出现乱码。
    扩展思考
    大文件处理:若CSV文件超过100MB,用File.ReadAllLines会占用大量内存,可改为StreamReader逐行读取:
    csharp

	using var reader = new StreamReader(filePath, encoding);
	reader.ReadLine(); // 跳过表头
	string? line;
	while ((line = reader.ReadLine()) != null) { /* 解析行 */ }

自定义分隔符:支持分号(;)或制表符( ),只需修改正则表达式和转义逻辑;
数据验证:导入时添加业务规则(如数量>0、单价>0),不符合的记录标记为无效;
数据库集成:导入后将记录存入SQL Server(用Dapper/EFCore),导出前从数据库查询数据。
总结
本节通过原生C#实现了CSV的导入导出,重点解决了特殊字符处理、格式解析、异常兼容等核心问题,让你理解CSV的底层逻辑。虽然第三方库更便捷,但原生实现能帮你掌握文件操作、正则表达式、编码处理等基础技能。在实际项目中,若需求简单用原生IO即可,复杂场景推荐用CsvHelper等成熟库。
记住:CSV看似简单,但细节(如特殊字符、编码)处理不好会导致数据错乱——遵循标准格式是避免问题的关键!
(实例完)

 
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49421.html

相关教程