diff --git a/README.md b/README.md index 01c557b..59d23d7 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,22 @@ foreach(IDictionary row in MiniExcel.Query(path)) +#### 9. Query Query Excel return DataTable + +Not recommended, because DataTable will load all data into memory and lose MiniExcel's low memory consumption feature. + +```C# +var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true); +``` + +![image](https://user-images.githubusercontent.com/12729184/116673475-07917200-a9d6-11eb-947e-a6f68cce58df.png) + + + + + + + ### Create Excel 1. Must be a non-abstract type with a public parameterless constructor . @@ -302,6 +318,12 @@ using (var stream = File.Create(path)) } ``` +#### 6. Support IDataReader value parameter + +```csharp +MiniExcel.SaveAs(path, reader); +``` + ### Fill Data To Excel Template @@ -760,38 +782,6 @@ public static IEnumerable Page(IEnumerable en, int pageSize, int page) ### FAQ -#### Q: How to convert query results to DataTable - -Reminder: Not recommended, because DataTable will load all data into memory and lose MiniExcel's low memory consumption function. - -```csharp -public static DataTable QueryAsDataTable(string path) -{ - var rows = MiniExcel.Query(path, true); - var dt = new DataTable(); - var first = true; - foreach (IDictionary row in rows) - { - if (first) - { - foreach (var key in row.Keys) - { - var type = row[key]?.GetType() ?? typeof(string); - dt.Columns.Add(key, type); - } - - first = false; - } - dt.Rows.Add(row.Values.ToArray()); - } - return dt; -} -``` - -![image](https://user-images.githubusercontent.com/12729184/115068722-3105c480-9f25-11eb-8f5a-994416754134.png) - - - #### Q: Excel header title not equal class property name, how to mapping? A. Please use ExcelColumnName attribute diff --git a/README.zh-CN.md b/README.zh-CN.md index 2751e9d..9b4fb4a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -231,7 +231,15 @@ foreach(IDictionary row in MiniExcel.Query(path)) } ``` +#### 9. Query 读 Excel 返回 DataTable +提醒 : 不建议使用,因为DataTable会将数据`全载入内存`,失去MiniExcel低内存消耗功能。 + +```C# +var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true); +``` + +![image](https://user-images.githubusercontent.com/12729184/116673475-07917200-a9d6-11eb-947e-a6f68cce58df.png) @@ -297,7 +305,7 @@ output : | MiniExcel | 1 | | Github | 2 | -#### 5. SaveAs 支援 Stream [[Try it]](https://dotnetfiddle.net/JOen0e) +#### 5. SaveAs 支持 Stream [[Try it]](https://dotnetfiddle.net/JOen0e) ```csharp using (var stream = File.Create(path)) @@ -308,6 +316,14 @@ using (var stream = File.Create(path)) +#### 6. 支持 IDataReader 参数 + +```csharp +MiniExcel.SaveAs(path, reader); +``` + + + ### 模板填充 Excel @@ -769,36 +785,6 @@ public static IEnumerable Page(IEnumerable en, int pageSize, int page) ### FAQ 常见问题 -#### Q: 如何将查询结果转为 DataTable - -提醒 : 不建议使用,因为DataTable会将数据`全载入内存`,失去MiniExcel低内存消耗功能。 - -```csharp -public static DataTable QueryAsDataTable(string path) -{ - var rows = MiniExcel.Query(path, true); - var dt = new DataTable(); - var first = true; - foreach (IDictionary row in rows) - { - if (first) - { - foreach (var key in row.Keys) - { - var type = row[key]?.GetType() ?? typeof(string); - dt.Columns.Add(key, type); - } - - first = false; - } - dt.Rows.Add(row.Values.ToArray()); - } - return dt; -} -``` - -![image](https://user-images.githubusercontent.com/12729184/115068549-fac84500-9f24-11eb-9f12-884b19cf1489.png) - #### Q: Excel 表头标题名称跟 class 属性名称不一致,如何对应? A. 请使用 ExcelColumnName 作 mapping diff --git a/README.zh-Hant.md b/README.zh-Hant.md index b7ea0b8..a7c8834 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -229,9 +229,21 @@ foreach(IDictionary row in MiniExcel.Query(path)) } ``` +![image](https://user-images.githubusercontent.com/12729184/116673475-07917200-a9d6-11eb-947e-a6f68cce58df.png) +#### 9. Query 讀 Excel 返回 DataTable + +提醒 : 不建議使用,因為DataTable會將數據`全載入內存`,失去MiniExcel低記憶體消耗功能。 + +```C# +var table = MiniExcel.QueryAsDataTable(path, useHeaderRow: true); +``` + +![image](https://user-images.githubusercontent.com/12729184/116673475-07917200-a9d6-11eb-947e-a6f68cce58df.png) + + ### 寫 Excel @@ -304,7 +316,11 @@ using (var stream = File.Create(path)) } ``` +#### 6. 支持 IDataReader 參數 +```csharp +MiniExcel.SaveAs(path, reader); +``` @@ -768,36 +784,6 @@ public static IEnumerable Page(IEnumerable en, int pageSize, int page) ### FAQ 常見問題 -#### Q: 如何將查詢結果轉為 DataTable - -提醒 : 不建議使用,因為DataTable會將數據`全載入記憶體`,失去MiniExcel低記憶體消耗功能。 - -```csharp -public static DataTable QueryAsDataTable(string path) -{ - var rows = MiniExcel.Query(path, true); - var dt = new DataTable(); - var first = true; - foreach (IDictionary row in rows) - { - if (first) - { - foreach (var key in row.Keys) - { - var type = row[key]?.GetType() ?? typeof(string); - dt.Columns.Add(key, type); - } - - first = false; - } - dt.Rows.Add(row.Values.ToArray()); - } - return dt; -} -``` - -![image](https://user-images.githubusercontent.com/12729184/115068722-3105c480-9f25-11eb-8f5a-994416754134.png) - #### Q: Excel 表頭標題名稱跟 class 屬性名稱不一致,如何對應? A. 請使用 ExcelColumnName 作 mapping diff --git a/docs/README.md b/docs/README.md index 27a787e..43a4ff4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,6 +8,7 @@ ### 0.13.5 - [New] Support QueryAsDataTable method [#216](https://github.com/shps951023/MiniExcel/issues/216) +- [New] SaveAs support IDataReader value parameter [#211](https://github.com/shps951023/MiniExcel/issues/211) - [Bug] Fix numeric format string will be cast to numeric type [#I3OSKV](https://gitee.com/dotnetchina/MiniExcel/issues/I3OSKV) - [Opt] Optimize SaveAs convert value type logic to improve performance diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 80222ab..20cc062 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -9,6 +9,7 @@ ### 0.13.5 - [New] 新增 QueryAsDataTable 方法 [#216](https://github.com/shps951023/MiniExcel/issues/216) +- [New] SaveAs 支持 IDataReader value 参数 [#211](https://github.com/shps951023/MiniExcel/issues/211) - [Bug] 修正数字格式的字串会被强制转换为decimal类型 [#I3OSKV](https://gitee.com/dotnetchina/MiniExcel/issues/I3OSKV) - [Opt] 优化 SaveAs 类别转换算法,避免效率浪费 diff --git a/docs/README.zh-Hant.md b/docs/README.zh-Hant.md index 69c3f0b..1f88a69 100644 --- a/docs/README.zh-Hant.md +++ b/docs/README.zh-Hant.md @@ -9,6 +9,7 @@ ### 0.13.5 - [New] 新增 QueryAsDataTable 方法 [#216](https://github.com/shps951023/MiniExcel/issues/216) +- [New] SaveAs 支持 IDataReader value 參數 [#211](https://github.com/shps951023/MiniExcel/issues/211) - [Bug] 修正數字格式的字串會被強制轉換為decimal類型 [#I3OSKV](https://gitee.com/dotnetchina/MiniExcel/issues/I3OSKV) - [Opt] 優化 SaveAs 類別轉換算法,避免效率浪費 diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index f1799f9..a1d6df8 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -106,7 +106,7 @@ /// /// This method is not recommended, because it'll load all data into memory. /// - public static DataTable QueryAsDataTable(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) + public static DataTable QueryAsDataTable(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) { using (var stream = Helpers.OpenSharedRead(path)) return QueryAsDataTable(stream, useHeaderRow, sheetName, GetExcelType(path, excelType), configuration); @@ -115,7 +115,7 @@ /// /// This method is not recommended, because it'll load all data into memory. /// - public static DataTable QueryAsDataTable(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) + public static DataTable QueryAsDataTable(this Stream stream, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) { var dt = new DataTable(); dt.TableName = sheetName; diff --git a/src/MiniExcel/MiniExcelLibs.csproj b/src/MiniExcel/MiniExcelLibs.csproj index ec91048..e288bf6 100644 --- a/src/MiniExcel/MiniExcelLibs.csproj +++ b/src/MiniExcel/MiniExcelLibs.csproj @@ -1,7 +1,7 @@ net45;netstandard2.0;net5.0 - 0.13.4 + 0.13.5 MiniExcel diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index e308c4e..85df651 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -165,6 +165,10 @@ namespace MiniExcelLibs.OpenXml { GenerateSheetByDataTable(writer, archive, value as DataTable, printHeader); } + else if (value is IDataReader) + { + GenerateSheetByIDataReader(writer, archive, value as IDataReader, printHeader); + } else { throw new NotImplementedException($"Type {type.Name} & genericType {genericType.Name} not Implemented. please issue for me."); @@ -332,6 +336,55 @@ namespace MiniExcelLibs.OpenXml writer.Write(""); } + private void GenerateSheetByIDataReader(StreamWriter writer, MiniExcelZipArchive archive, IDataReader value, bool printHeader) + { + var xy = ExcelOpenXmlUtils.ConvertCellToXY("A1"); + + writer.Write($@""); + { + var yIndex = xy.Item2; + + // TODO: dimension + //var maxRowIndex = value.Rows.Count + (printHeader && value.Rows.Count > 0 ? 1 : 0); + //var maxColumnIndex = value.Columns.Count; + //writer.Write($@""); + writer.Write(""); + int fieldCount = value.FieldCount; + if (printHeader) + { + writer.Write($""); + var xIndex = xy.Item1; + for (int i = 0; i < fieldCount; i++) + { + var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex); + writer.Write($""); + writer.Write($"{value.GetName(i)}"); + writer.Write($""); + writer.Write($""); + xIndex++; + } + writer.Write($""); + yIndex++; + } + + while (value.Read()) + { + writer.Write($""); + var xIndex = xy.Item1; + + for(int i = 0; i < fieldCount; i++) + { + var cellValue = value.GetValue(i); + WriteCell(writer, yIndex, xIndex, cellValue); + xIndex++; + } + writer.Write($""); + yIndex++; + } + } + writer.Write(""); + } + private void GenerateContentTypesXml(MiniExcelZipArchive archive, Dictionary packages) { //[Content_Types].xml diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 5cafa37..0891f15 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -12,6 +12,8 @@ using Newtonsoft.Json; using MiniExcelLibs.Attributes; using MiniExcelLibs.Tests.Utils; using System.Data; +using System.Data.SQLite; +using Dapper; namespace MiniExcelLibs.Tests { @@ -23,6 +25,32 @@ namespace MiniExcelLibs.Tests this.output = output; } + + /// + /// [Can SaveAs support iDataReader export to avoid the dataTable consuming too much memory · Issue #211 · shps951023/MiniExcel] + /// (https://github.com/shps951023/MiniExcel/issues/211) + /// + [Fact] + public void Issue211() + { + var path = PathHelper.GetNewTemplateFilePath(); + var tempSqlitePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); + var connectionString = $"Data Source={tempSqlitePath};Version=3;"; + + using (var connection = new SQLiteConnection(connectionString)) + { + var reader = connection.ExecuteReader(@"select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); + + MiniExcel.SaveAs(path, reader); + + var rows = MiniExcel.Query(path,true).ToList(); + Assert.Equal((double)1, rows[0].Test1); + Assert.Equal((double)2, rows[0].Test2); + Assert.Equal((double)3, rows[1].Test1); + Assert.Equal((double)4, rows[1].Test2); + } + } + /// /// [When reading Excel, can return IDataReader and DataTable to facilitate the import of database. Like ExcelDataReader provide reader.AsDataSet() · Issue #216 · shps951023/MiniExcel](https://github.com/shps951023/MiniExcel/issues/216) /// @@ -34,14 +62,14 @@ namespace MiniExcelLibs.Tests MiniExcel.SaveAs(path, value); { - var dt = MiniExcel.QueryAsDataTable(path, true); - var columns = dt.Columns; - Assert.Equal("Test1", dt.Columns[0].ColumnName); - Assert.Equal("Test2", dt.Columns[1].ColumnName); - Assert.Equal("1", dt.Rows[0]["Test1"]); - Assert.Equal((double)2, dt.Rows[0]["Test2"]); - Assert.Equal("3", dt.Rows[1]["Test1"]); - Assert.Equal((double)4, dt.Rows[1]["Test2"]); + var table = MiniExcel.QueryAsDataTable(path); + var columns = table.Columns; + Assert.Equal("Test1", table.Columns[0].ColumnName); + Assert.Equal("Test2", table.Columns[1].ColumnName); + Assert.Equal("1", table.Rows[0]["Test1"]); + Assert.Equal((double)2, table.Rows[0]["Test2"]); + Assert.Equal("3", table.Rows[1]["Test1"]); + Assert.Equal((double)4, table.Rows[1]["Test2"]); } {