Add XCode skills for entity caching, ORM, and sharding ETL
大石头 authored at 2026-04-02 18:30:07
7.45 KiB
NewLife.Skills
--- name: data-file-formats description: > 使用 NewLife.Core 读写结构化数据文件:CsvFile(RFC4180 流式 CSV 读写)、 ExcelReader(轻量级 xlsx 导入,无需第三方组件)、DbTable(内存数据表,支持二进制/JSON/CSV序列化)。 适用于数据导入导出、批量 Excel 读取、跨格式数据交换等场景。 argument-hint: > 说明你的场景:读取还是写入 CSV;导入 Excel(.xlsx); 还是在内存中操作行列数据集(DbTable)并序列化为二进制/JSON/CSV。 是否需要超大文件流式处理、异步读取、或与数据库查询结果对接。 --- # 数据文件格式技能(CsvFile + ExcelReader + DbTable) ## 适用场景 - **`CsvFile`**:超大 CSV 流式逐行读取(不把整个文件加载进内存);正确处理含逗号/换行/双引号的 RFC4180 字段;导出时自动转义;支持自定义分隔符(Tab 分隔)。 - **`ExcelReader`**:服务端/无界面环境批量导入 `.xlsx`,不依赖 COM/Office/Interop;只需逐行读取数据,不需要公式计算、合并单元格等高级特性。 - **`DbTable`**:承载数据库查询结果(从 `IDataReader` 读取);在内存中做多次遍历/筛选;序列化为二进制(高效,可压缩)/JSON/XML/CSV;与模型列表相互映射。 - 代码审查:确认 `CsvFile`/`ExcelReader` 使用 `using` 确保流关闭;`DbTable` 序列化为二进制时 `Types` 数组必须与 `Columns` 对齐。 ## 核心原则 1. **`CsvFile` 是 RFC4180 兼容的流式读写器**:`ReadLine()` 返回 `null` 表示 EOF;`WriteLine()` 自动转义含分隔符/换行/引号的字段;大数字(>9位整数)自动加 `\t` 前缀防 Excel 科学计数。 2. **`ExcelReader` 只读 xlsx**:本质是解析 OpenXML zip,不支持 xls/宏/公式;`ReadRows()` 自动补齐缺失列(跳列补 `null`);`sheet=null` 取第一个工作表。 3. **`DbTable` 需要 `Types` 才能做二进制序列化**:列名 `Columns` + 列类型 `Types` + 行数据 `Rows` 三者需同步维护;`Read(IDataReader)` 会自动填充三者。 4. **超大文件用流式 API**:`CsvFile.ReadAll()` / `ReadAllAsync()` 是懒惰迭代器,不把整个文件加载内存;`ExcelReader.ReadRows()` 同理。 ## 执行步骤 ### 一、CsvFile — 读取 CSV ```csharp using NewLife.IO; // 从文件读取(流式,逐行) using var csv = new CsvFile("./data.csv"); // 方式1:while 循环(推荐,EOF 返回 null) while (true) { var row = csv.ReadLine(); if (row == null) break; // row 是 String[] 字段数组 Console.WriteLine($"Id={row[0]}, Name={row[1]}"); } // 方式2:枚举(LINQ 友好) foreach (var row in csv.ReadAll()) { ProcessRow(row); } // 方式3:异步(.NET 5+) await foreach (var row in csv.ReadAllAsync()) { await ProcessRowAsync(row); } ``` #### 自定义分隔符(Tab 分隔) ```csharp using var csv = new CsvFile(stream, leaveOpen: true) { Separator = '\t', Encoding = Encoding.UTF8, }; ``` ### 二、CsvFile — 写入 CSV ```csharp using NewLife.IO; // 写入文件(write=true) using var csv = new CsvFile("./out.csv", write: true); // 写表头 csv.WriteLine(new[] { "Id", "Name", "CreatedAt" }); // 写数据行(自动处理 Boolean → 1/0、DateTime → 标准格式、含逗号字段自动加引号) csv.WriteLine(new object[] { 1, "张三", DateTime.Now }); csv.WriteLine(new object[] { 2, "带,逗号的名字", DateTime.Today }); // 批量写入 csv.WriteAll(dataRows.Select(r => new object[] { r.Id, r.Name, r.CreatedAt })); ``` ### 三、ExcelReader — 读取 xlsx ```csharp using NewLife.IO; // 从文件读取(始终 using,确保流关闭) using var excel = new ExcelReader("./import.xlsx"); // 查看工作表列表 var sheets = excel.Sheets; // ICollection<String> // 读取第一个工作表(sheet=null 默认第一个) var rows = excel.ReadRows().ToList(); // List<Object?[]> // 第一行通常是表头 var header = rows[0]; // ["Id", "Name", "Price", ...] // 逐行处理数据(跳过表头) foreach (var row in rows.Skip(1)) { var id = (int?)row[0] ?? 0; var name = (string?)row[1] ?? ""; var price = Convert.ToDecimal(row[2] ?? 0); } // 指定工作表 foreach (var row in excel.ReadRows("Sheet2")) { ProcessRow(row); } // 从流读取(如 HTTP 上传) using var excel2 = new ExcelReader(uploadStream, Encoding.UTF8); ``` #### 类型转换说明 `ExcelReader` 自动将单元格解析为: | Excel 格式 | .NET 类型 | |-----------|---------| | 数字(无格式) | `Int32`/`Int64`/`Double`/`Decimal` | | 日期格式数字 | `DateTime` | | 时间格式数字 | `TimeSpan` | | 布尔 | `Boolean` | | 共享字符串 | `String` | | 空 / 缺失列 | `null` | ### 四、DbTable — 内存数据表 ```csharp using NewLife.Data; // 方式1:从数据库读取(自动填充 Columns/Types/Rows) var table = new DbTable(); using var dr = cmd.ExecuteReader(); table.Read(dr); // 或异步 await table.ReadAsync(dr); // 方式2:手动构建 var table = new DbTable { Columns = new[] { "Id", "Name", "Active" }, Types = new[] { typeof(int), typeof(string), typeof(bool) }, Rows = new List<object?[]> { new object?[] { 1, "张三", true }, new object?[] { 2, "李四", false }, }, Total = 2, }; // 遍历 foreach (var row in table) { var id = (int?)row["Id"] ?? 0; var name = (string?)row["Name"] ?? ""; } // 转换为模型列表 var users = table.ReadModels<User>().ToList(); // 序列化为 JSON var json = table.ToJson(); // 序列化为二进制(高效,需 Types 正确) var data = table.ToPacket(); // 从二进制反序列化 var table2 = new DbTable(); table2.Read(data); ``` ### 五、CsvFile + DbTable 联合使用 ```csharp // 从 CSV 读取到 DbTable var table = new DbTable { Columns = new[] { "Id", "Name", "Amount" }, Types = new[] { typeof(int), typeof(string), typeof(decimal) }, }; using var csv = new CsvFile("./data.csv"); csv.ReadLine(); // 跳过表头 while (csv.ReadLine() is { } row) { table.Rows.Add(new object?[] { row[0].ToInt(), row[1], row[2].ToDouble() }); } // 从 DbTable 写出 CSV using var csv2 = new CsvFile("./out.csv", write: true); csv2.WriteLine(table.Columns); table.WriteAll(csv2); ``` ## 重点检查项 - [ ] `CsvFile` 是否使用 `using` 确保 `Dispose()` 调用(`_writer?.Flush()` 会在 Dispose 中执行)? - [ ] 写入 `CsvFile` 时是否依赖自动转义(不要手动拼装含逗号/换行的字段字符串)? - [ ] `ExcelReader` 是否使用 `using` 确保 zip 文件句柄释放? - [ ] `ExcelReader` 读取结果中是否对 `null` 列做了空值保护(跳列/缺失单元格返回 `null`)? - [ ] `DbTable` 在做二进制序列化/反序列化时 `Types` 是否与 `Columns` 对齐(缺 `Types` 会导致序列化出错)? - [ ] `DbTable.Total` 是否在手动构建后设置为正确值(二进制协议中 `Total` 用于记录总条数)? ## 输出要求 - **CSV**:`CsvFile`(`NewLife.IO`)—— `ReadLine()`/`ReadAll()`/`ReadAllAsync()`;`WriteLine()`/`WriteAll()`;`Separator`/`Encoding`。 - **Excel**:`ExcelReader`(`NewLife.IO`)—— `ReadRows(sheet?)`;自动类型转换;仅支持 xlsx。 - **内存数据表**:`DbTable`(`NewLife.Data`)—— `Read(IDataReader)`;`ReadModels<T>()`;`ToJson()`/`ToPacket()`;`Columns`/`Types`/`Rows`/`Total`。 ## 参考资料 - `NewLife.Core/IO/CsvFile.cs` - `NewLife.Core/IO/ExcelReader.cs` - `NewLife.Core/Data/DbTable.cs`