From 5aa7464101fdcfe4f7bcbfec5f684fd687724eed Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Sun, 23 Mar 2025 20:56:50 +0100 Subject: [PATCH] Added capability to include first row in strong typed mapping query Added parameter hasHeader to Query and QueryAsync that, when set to false, wil not treat the first row as a header and include its data in the result set instead --- samples/xlsx/TestIssue542.xlsx | Bin 0 -> 6876 bytes src/MiniExcel/Csv/CsvReader.cs | 18 +++--- src/MiniExcel/IExcelReader.cs | 14 ++-- src/MiniExcel/MiniExcel.Async.cs | 34 +++++----- src/MiniExcel/MiniExcel.cs | 8 +-- .../OpenXml/ExcelOpenXmlSheetReader.cs | 26 ++++---- src/MiniExcel/Utils/CustomPropertyHelper.cs | 1 + tests/MiniExcelTests/MiniExcelIssueTests.cs | 60 ++++++++++++------ tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 2 +- 9 files changed, 94 insertions(+), 69 deletions(-) create mode 100644 samples/xlsx/TestIssue542.xlsx diff --git a/samples/xlsx/TestIssue542.xlsx b/samples/xlsx/TestIssue542.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d3a3a18d0ecc722a777395371f4dc702ec4d7919 GIT binary patch literal 6876 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 literal 0 HcmV?d00001 diff --git a/src/MiniExcel/Csv/CsvReader.cs b/src/MiniExcel/Csv/CsvReader.cs index ae8acfd0..c9d9f3f7 100644 --- a/src/MiniExcel/Csv/CsvReader.cs +++ b/src/MiniExcel/Csv/CsvReader.cs @@ -106,9 +106,9 @@ public IEnumerable> Query(bool useHeaderRow, string } } } - public IEnumerable Query(string sheetName, string startCell) where T : class, new() + public IEnumerable Query(string sheetName, string startCell, bool hasHeader) where T : class, new() { - return ExcelOpenXmlSheetReader.QueryImpl(Query(false, sheetName, startCell), startCell, this._config); + return ExcelOpenXmlSheetReader.QueryImpl(Query(false, sheetName, startCell), startCell, hasHeader, _config); } private string[] Split(string row) @@ -125,14 +125,14 @@ private string[] Split(string row) } } - public Task>> QueryAsync(bool UseHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) + public Task>> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default) { - return Task.Run(() => Query(UseHeaderRow, sheetName, startCell), cancellationToken); + return Task.Run(() => Query(useHeaderRow, sheetName, startCell), cancellationToken); } - public Task> QueryAsync(string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + public Task> QueryAsync(string sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() { - return Task.Run(() => Query(sheetName, startCell), cancellationToken); + return Task.Run(() => Query(sheetName, startCell, hasHeader), cancellationToken); } public void Dispose() @@ -190,14 +190,14 @@ public IEnumerable> QueryRange(bool useHeaderRow, st { return ExcelOpenXmlSheetReader.QueryImplRange(QueryRange(false, sheetName, startCell, endCel), startCell, endCel, this._config); } - public Task>> QueryAsyncRange(bool UseHeaderRow, string sheetName, string startCell, string endCel, CancellationToken cancellationToken = default(CancellationToken)) + public Task>> QueryAsyncRange(bool UseHeaderRow, string sheetName, string startCell, string endCel, CancellationToken cancellationToken = default) { return Task.Run(() => QueryRange(UseHeaderRow, sheetName, startCell, endCel), cancellationToken); } - public Task> QueryAsyncRange(string sheetName, string startCell, string endCel, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + public Task> QueryAsyncRange(string sheetName, string startCell, string endCel, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() { - return Task.Run(() => Query(sheetName, startCell), cancellationToken); + return Task.Run(() => Query(sheetName, startCell, hasHeader), cancellationToken); } #endregion } diff --git a/src/MiniExcel/IExcelReader.cs b/src/MiniExcel/IExcelReader.cs index d1b34be6..cf09a79c 100644 --- a/src/MiniExcel/IExcelReader.cs +++ b/src/MiniExcel/IExcelReader.cs @@ -7,13 +7,13 @@ namespace MiniExcelLibs { internal interface IExcelReader : IDisposable { - IEnumerable> Query(bool UseHeaderRow, string sheetName, string startCell); - IEnumerable Query(string sheetName, string startCell) where T : class, new(); - Task>> QueryAsync(bool UseHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)); - Task> QueryAsync(string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new(); - IEnumerable> QueryRange(bool UseHeaderRow, string sheetName, string startCell, string endCell); + IEnumerable> Query(bool useHeaderRow, string sheetName, string startCell); + IEnumerable Query(string sheetName, string startCell, bool hasHeader) where T : class, new(); + Task>> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default); + Task> QueryAsync(string sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); + IEnumerable> QueryRange(bool useHeaderRow, string sheetName, string startCell, string endCell); IEnumerable QueryRange(string sheetName, string startCell, string endCell) where T : class, new(); - Task>> QueryAsyncRange(bool UseHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)); - Task> QueryAsyncRange(string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new(); + Task>> QueryAsyncRange(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default); + Task> QueryAsyncRange(string sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); } } diff --git a/src/MiniExcel/MiniExcel.Async.cs b/src/MiniExcel/MiniExcel.Async.cs index 6d026b66..51ede3f3 100644 --- a/src/MiniExcel/MiniExcel.Async.cs +++ b/src/MiniExcel/MiniExcel.Async.cs @@ -52,7 +52,7 @@ public static async TaskInsertAsync(this Stream stream, object value, strin } } - public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default) { if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format"); @@ -61,42 +61,42 @@ public static async TaskInsertAsync(this Stream stream, object value, strin return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, cancellationToken); } - public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default) { return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAsAsync(cancellationToken); } - public static async Task MergeSameCellsAsync(string mergedFilePath, string path, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task MergeSameCellsAsync(string mergedFilePath, string path, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, CancellationToken cancellationToken = default) { await Task.Run(() => MergeSameCells(mergedFilePath, path, excelType, configuration), cancellationToken).ConfigureAwait(false); } - public static async Task MergeSameCellsAsync(this Stream stream, string path, ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task MergeSameCellsAsync(this Stream stream, string path, ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default) { await ExcelTemplateFactory.GetProvider(stream, configuration, excelType).MergeSameCellsAsync(path, cancellationToken); } - public static async Task MergeSameCellsAsync(this Stream stream, byte[] fileBytes, ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task MergeSameCellsAsync(this Stream stream, byte[] fileBytes, ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default) { await ExcelTemplateFactory.GetProvider(stream, configuration, excelType).MergeSameCellsAsync(fileBytes, cancellationToken); } - public static async Task> QueryAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task> QueryAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) { return await Task.Run(() => Query(path, useHeaderRow, sheetName, excelType, startCell, configuration), cancellationToken); } - public static async Task> QueryAsync(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + public static async Task> QueryAsync(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default, bool hasHeader = true) where T : class, new() { - return await ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration).QueryAsync(sheetName, startCell, cancellationToken).ConfigureAwait(false); + return await ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration).QueryAsync(sheetName, startCell, hasHeader, cancellationToken).ConfigureAwait(false); } - public static async Task> QueryAsync(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + public static async Task> QueryAsync(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default, bool hasHeader = true) where T : class, new() { - return await Task.Run(() => Query(path, sheetName, excelType, startCell, configuration), cancellationToken).ConfigureAwait(false); + return await Task.Run(() => Query(path, sheetName, excelType, startCell, configuration, hasHeader), cancellationToken).ConfigureAwait(false); } - public static async Task> QueryAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task> QueryAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource>(); cancellationToken.Register(() => @@ -119,22 +119,22 @@ await Task.Run(() => return await tcs.Task; } - public static async Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) { await ExcelTemplateFactory.GetProvider(stream, configuration).SaveAsByTemplateAsync(templatePath, value, cancellationToken).ConfigureAwait(false); } - public static async Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) { await ExcelTemplateFactory.GetProvider(stream, configuration).SaveAsByTemplateAsync(templateBytes, value, cancellationToken).ConfigureAwait(false); } - public static async Task SaveAsByTemplateAsync(string path, string templatePath, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsByTemplateAsync(string path, string templatePath, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) { await Task.Run(() => SaveAsByTemplate(path, templatePath, value, configuration), cancellationToken).ConfigureAwait(false); } - public static async Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) { await Task.Run(() => SaveAsByTemplate(path, templateBytes, value, configuration), cancellationToken).ConfigureAwait(false); } @@ -143,7 +143,7 @@ await Task.Run(() => /// QueryAsDataTable is not recommended, because it'll load all data into memory. /// [Obsolete("QueryAsDataTable is not recommended, because it'll load all data into memory.")] - public static async Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) { return await Task.Run(() => QueryAsDataTable(path, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration), cancellationToken).ConfigureAwait(false); } @@ -152,7 +152,7 @@ await Task.Run(() => /// QueryAsDataTable is not recommended, because it'll load all data into memory. /// [Obsolete("QueryAsDataTable is not recommended, because it'll load all data into memory.")] - public static async Task QueryAsDataTableAsync(this Stream stream, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default(CancellationToken)) + public static async Task QueryAsDataTableAsync(this Stream stream, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) { return await Task.Run(() => QueryAsDataTable(stream, useHeaderRow, sheetName, excelType, startCell, configuration), cancellationToken).ConfigureAwait(false); } diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index 844fb215..e2c805f4 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -76,17 +76,17 @@ public static int[] SaveAs(this Stream stream, object value, bool printHeader = return ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAs(); } - public static IEnumerable Query(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new() + public static IEnumerable Query(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, bool hasHeader = true) where T : class, new() { using (var stream = FileHelper.OpenSharedRead(path)) - foreach (var item in Query(stream, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration)) + foreach (var item in Query(stream, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, hasHeader)) yield return item; //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable } - public static IEnumerable Query(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) where T : class, new() + public static IEnumerable Query(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, bool hasHeader = true) where T : class, new() { using (var excelReader = ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration)) - foreach (var item in excelReader.Query(sheetName, startCell)) + foreach (var item in excelReader.Query(sheetName, startCell, hasHeader)) yield return item; } public static IEnumerable Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index 8e18aeab..f634da3b 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -411,15 +411,17 @@ private static void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, } } - public IEnumerable Query(string sheetName, string startCell) where T : class, new() + public IEnumerable Query(string sheetName, string startCell, bool hasHeader) where T : class, new() { if (sheetName == null) sheetName = CustomPropertyHelper.GetExcellSheetInfo(typeof(T), _config)?.ExcelSheetName; - - return QueryImpl(Query(false, sheetName, startCell), startCell, _config); + + //Todo: Find a way if possible to remove the 'hasHeader' parameter to check whether or not to include + // the first row in the result set in favor of modifying the already present 'useHeaderRow' to do the same job + return QueryImpl(Query(false, sheetName, startCell), startCell, hasHeader, _config); } - public static IEnumerable QueryImpl(IEnumerable> values, string startCell, Configuration configuration) where T : class, new() + public static IEnumerable QueryImpl(IEnumerable> values, string startCell, bool hasHeader, Configuration configuration) where T : class, new() { var type = typeof(T); @@ -441,7 +443,9 @@ private static void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, //TODO: alert don't duplicate column name props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); first = false; - continue; + + if (hasHeader) + continue; } var v = new T(); foreach (var pInfo in props) @@ -761,14 +765,14 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec } } - public async Task>> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) + public async Task>> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default) { return await Task.Run(() => Query(useHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); } - public async Task> QueryAsync(string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + public async Task> QueryAsync(string sheetName, string startCell, bool hasHeader = true, CancellationToken cancellationToken = default) where T : class, new() { - return await Task.Run(() => Query(sheetName, startCell), cancellationToken).ConfigureAwait(false); + return await Task.Run(() => Query(sheetName, startCell, hasHeader), cancellationToken).ConfigureAwait(false); } ~ExcelOpenXmlSheetReader() @@ -1214,14 +1218,14 @@ public IEnumerable> QueryRange(bool useHeaderRow, st } } - public async Task>> QueryAsyncRange(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) + public async Task>> QueryAsyncRange(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default) { return await Task.Run(() => Query(useHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); } - public async Task> QueryAsyncRange(string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() + public async Task> QueryAsyncRange(string sheetName, string startCell, string endCell, bool hasHeader = true, CancellationToken cancellationToken = default) where T : class, new() { - return await Task.Run(() => Query(sheetName, startCell), cancellationToken).ConfigureAwait(false); + return await Task.Run(() => Query(sheetName, startCell, hasHeader), cancellationToken).ConfigureAwait(false); } #endregion ReaderRange diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index 486b5bd7..120f2795 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -134,6 +134,7 @@ internal static List GetExcelCustomPropertyInfos(Type type, str var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1); if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(x => x.Count() > 1)) throw new InvalidOperationException("Duplicate column name"); + var maxkey = keys.Last(); var maxIndex = ColumnHelper.GetColumnIndex(maxkey); foreach (var p in props) diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index c7c01a36..248e1a9b 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -529,7 +529,7 @@ public void TestIssue401(bool autoFilter, int count) } { - var xlsxPath = @"../../../../../samples/xlsx/Test5x2.xlsx"; + var xlsxPath = "../../../../../samples/xlsx/Test5x2.xlsx"; var tempSqlitePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); var connectionString = $"Data Source={tempSqlitePath};Version=3;"; @@ -2764,7 +2764,7 @@ public void Issue207() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var tempaltePath = @"../../../../../samples/xlsx/TestIssue207_Template_Merge_row_list_rendering_without_merge/template.xlsx"; + var tempaltePath = "../../../../../samples/xlsx/TestIssue207_Template_Merge_row_list_rendering_without_merge/template.xlsx"; var value = new { @@ -2802,7 +2802,7 @@ public void Issue207() public void Issue87() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var templatePath = @"../../../../../samples/xlsx/TestTemplateCenterEmpty.xlsx"; + var templatePath = "../../../../../samples/xlsx/TestTemplateCenterEmpty.xlsx"; var value = new { Tests = Enumerable.Range(1, 5).Select((s, i) => new { test1 = i, test2 = i }) @@ -2857,7 +2857,7 @@ public void Issue206() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFill.xlsx"; + var templatePath = "../../../../../samples/xlsx/TestTemplateBasicIEmumerableFill.xlsx"; var dt = new DataTable(); { @@ -2887,7 +2887,7 @@ public void Issue193() { { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var templatePath = @"../../../../../samples/xlsx/TestTemplateComplexWithNamespacePrefix.xlsx"; + var templatePath = "../../../../../samples/xlsx/TestTemplateComplexWithNamespacePrefix.xlsx"; // 1. By Class var value = new @@ -2944,7 +2944,7 @@ public void Issue193() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var templatePath = @"../../../../../samples/xlsx/TestTemplateComplex.xlsx"; + var templatePath = "../../../../../samples/xlsx/TestTemplateComplex.xlsx"; // 2. By Dictionary var value = new Dictionary() @@ -3094,18 +3094,18 @@ public void Issue142() public void Issue142_Query() { { - var path = @"../../../../../samples/xlsx/TestIssue142.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue142.xlsx"; var rows = MiniExcel.Query(path).ToList(); Assert.Equal(0, rows[0].MyProperty1); } { - var path = @"../../../../../samples/xlsx/TestIssue142.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue142.xlsx"; Assert.Throws(() => MiniExcel.Query(path).ToList()); } { - var path = @"../../../../../samples/xlsx/TestIssue142.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue142.xlsx"; var rows = MiniExcel.Query(path).ToList(); Assert.Equal("CustomColumnName", rows[0].MyProperty1); Assert.Null(rows[0].MyProperty7); @@ -3117,7 +3117,7 @@ public void Issue142_Query() } { - var path = @"../../../../../samples/csv/TestIssue142.csv"; + var path = "../../../../../samples/csv/TestIssue142.csv"; var rows = MiniExcel.Query(path).ToList(); Assert.Equal("CustomColumnName", rows[0].MyProperty1); Assert.Null(rows[0].MyProperty7); @@ -3211,7 +3211,7 @@ public void Issue157() Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); } { - var path = @"../../../../../samples/xlsx/TestIssue157.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue157.xlsx"; { var rows = MiniExcel.Query(path, sheetName: "Sheet1").ToList(); @@ -3257,7 +3257,7 @@ public void Issue149() }.Select(s => s.ToString()).ToArray(); { - var path = @"../../../../../samples/xlsx/TestIssue149.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue149.xlsx"; var rows = MiniExcel.Query(path).Select(s => (string)s.A).ToList(); for (int i = 0; i < chars.Length; i++) { @@ -3310,7 +3310,7 @@ public class Issue149VO [Fact] public void Issue153() { - var path = @"../../../../../samples/xlsx/TestIssue153.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue153.xlsx"; var rows = MiniExcel.Query(path, true).First() as IDictionary; Assert.Equal( [ @@ -3412,7 +3412,7 @@ public class Issue137ExcelRow [Fact] public void Issue138() { - var path = @"../../../../../samples/xlsx/TestIssue138.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue138.xlsx"; { var rows = MiniExcel.Query(path, true).ToList(); Assert.Equal(6, rows.Count); @@ -3584,7 +3584,7 @@ public class Issue585VO3 [Fact] public void Issue585() { - var path = @"../../../../../samples/xlsx/TestIssue585.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue585.xlsx"; var items1 = MiniExcel.Query(path); Assert.Equal(2, items1.Count()); @@ -3596,6 +3596,26 @@ public void Issue585() Assert.Equal(2, items3.Count()); } + private class Issue542 + { + [ExcelColumnIndex(0)] public Guid ID { get; set; } + [ExcelColumnIndex(1)] public string Name { get; set; } + } + + [Fact] + public void Issue_542() + { + var path = "../../../../../samples/xlsx/TestIssue542.xlsx"; + + var resultWithoutFirstRow = MiniExcel.Query(path).ToList(); + var resultWithFirstRow = MiniExcel.Query(path, hasHeader: false).ToList(); + + Assert.Equal(15, resultWithoutFirstRow.Count); + Assert.Equal(16, resultWithFirstRow.Count); + + Assert.Equal("Felix", resultWithoutFirstRow[0].Name); + Assert.Equal("Wade", resultWithFirstRow[0].Name); + } class Issue507V01 { @@ -3646,14 +3666,14 @@ public void Issue507_1() Assert.Equal("Github", getRowsInfo[0].A); Assert.Equal("abcd", getRowsInfo[0].C); - Assert.Equal(@$"Microsoft {config.NewLine}Test 1", getRowsInfo[1].A); + Assert.Equal($"Microsoft {config.NewLine}Test 1", getRowsInfo[1].A); Assert.Equal("efgh", getRowsInfo[1].C); - Assert.Equal(@$"Microsoft {config.NewLine}Test 2", getRowsInfo[2].A); + Assert.Equal($"Microsoft {config.NewLine}Test 2", getRowsInfo[2].A); Assert.Equal($"ab{config.NewLine}c{config.NewLine}d", getRowsInfo[2].C); - Assert.Equal(@$"Microsoft"""" {config.NewLine}Test{config.NewLine}3", getRowsInfo[3].A); - Assert.Equal(@$"a""""{config.NewLine}b{config.NewLine}{config.NewLine}c", getRowsInfo[3].C); + Assert.Equal($"""Microsoft"" {config.NewLine}Test{config.NewLine}3""", getRowsInfo[3].A); + Assert.Equal($"""a""{config.NewLine}b{config.NewLine}{config.NewLine}c""", getRowsInfo[3].C); } [Fact] @@ -3757,7 +3777,7 @@ public void Issue606_1() string.Concat(nameof(MiniExcelIssueTests), "_", nameof(Issue606_1), ".xlsx") ); - var templateFileName = @"../../../../../samples/xlsx/TestIssue606_Template.xlsx"; + var templateFileName = "../../../../../samples/xlsx/TestIssue606_Template.xlsx"; MiniExcel.SaveAsByTemplate(path, Path.GetFullPath(templateFileName), value); diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 279d4c36..298d1938 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -159,7 +159,7 @@ public class CustomAttributesWihoutVaildPropertiesTestPoco [Fact] public void QueryCastToIDictionary() { - var path = @"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; + var path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; foreach (IDictionary row in MiniExcel.Query(path)) {