Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,22 +362,21 @@ private static async Task WriteColumnsWidthsAsync(MiniExcelAsyncStreamWriter wri

private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List<ExcelColumnInfo> props, CancellationToken cancellationToken = default)
{
var xIndex = 1;
var yIndex = 1;
const int yIndex = 1;
await writer.WriteAsync(WorksheetXml.StartRow(yIndex));

var xIndex = 1;
foreach (var p in props)
{
cancellationToken.ThrowIfCancellationRequested();

if (p == null)
//reason : https://github.com/mini-software/MiniExcel/issues/142
if (p != null)
{
xIndex++; //reason : https://github.com/mini-software/MiniExcel/issues/142
continue;
}
if (p.ExcelIgnore)
continue;

var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex);
await WriteCellAsync(writer, r, columnName: p.ExcelColumnName);
var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex);
await WriteCellAsync(writer, r, columnName: p.ExcelColumnName);
}
xIndex++;
}

Expand Down
16 changes: 8 additions & 8 deletions src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,16 @@ private static async IAsyncEnumerable<CellWriteInfo> GetRowValuesAsync(T current
foreach (var prop in props)
{
column++;

if (prop is null)
continue;

yield return currentValue switch
if (prop is { ExcelIgnore: false })
{
IDictionary<string, object> genericDictionary => new CellWriteInfo(genericDictionary[prop.Key.ToString()], column, prop),
IDictionary dictionary => new CellWriteInfo(dictionary[prop.Key], column, prop),
_ => new CellWriteInfo(prop.Property.GetValue(currentValue), column, prop)
};
yield return currentValue switch
{
IDictionary<string, object> genericDictionary => new CellWriteInfo(genericDictionary[prop.Key.ToString()], column, prop),
IDictionary dictionary => new CellWriteInfo(dictionary[prop.Key], column, prop),
_ => new CellWriteInfo(prop.Property.GetValue(currentValue), column, prop)
};
}
Comment on lines 75 to +85
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The column index is incremented for every property, even for those that are ignored (prop.ExcelIgnore is true). However, a CellWriteInfo is not yielded for ignored properties. This will cause the cellIndex of subsequent non-ignored properties to have gaps, resulting in empty columns in the generated Excel file. To fix this, the column++ statement should be moved inside the if (prop is { ExcelIgnore: false }) block.

                if (prop is { ExcelIgnore: false })
                {
                    column++;
                    yield return currentValue switch
                    {
                        IDictionary<string, object> genericDictionary => new CellWriteInfo(genericDictionary[prop.Key.ToString()], column, prop),
                        IDictionary dictionary => new CellWriteInfo(dictionary[prop.Key], column, prop),
                        _ => new CellWriteInfo(prop.Property.GetValue(currentValue), column, prop)
                    };
                }

Copy link
Copy Markdown
Contributor Author

@michelebastione michelebastione Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, as we still want to increase the cellIndex value when the mapping is null (as that represents an actual column to leave empty), however we don't want to also leave a gap when the property is ignored. This currently does not happen, as ignored properties are actually filtered early, but the fact that checks like this are in place make the process ambiguous. It's possibly to be revised in the near future to make it less so.

}
}

Expand Down
38 changes: 37 additions & 1 deletion tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@

private class Issue243Dto
{
public string Name { get; set; }

Check warning on line 223 in tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 223 in tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public int Age { get; set; }
public DateTime InDate { get; set; }
}
Expand Down Expand Up @@ -292,7 +292,7 @@

private class Issue241Dto
{
public string Name { get; set; }

Check warning on line 295 in tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 295 in tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

[ExcelFormat("MM dd, yyyy")]
public DateTime InDate { get; set; }
Expand Down Expand Up @@ -359,11 +359,11 @@
var sheets = new DataSet();

var users = JsonConvert.DeserializeObject<DataTable>(JsonConvert.SerializeObject(new[] { new { Name = "Jack", Age = 25 }, new { Name = "Mike", Age = 44 } }));
users.TableName = "users";

Check warning on line 362 in tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.
sheets.Tables.Add(users);

var department = JsonConvert.DeserializeObject<DataTable>(JsonConvert.SerializeObject(new[] { new { ID = "01", Name = "HR" }, new { ID = "02", Name = "IT" } }));
department.TableName = "department";

Check warning on line 366 in tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Dereference of a possibly null reference.
sheets.Tables.Add(department);

var rowsWritten = await MiniExcel.SaveAsAsync(path, sheets);
Expand Down Expand Up @@ -1853,4 +1853,40 @@
public double? 波段 { get; set; }
public double? 當沖 { get; set; }
}
}

[Fact]
public async Task TestIssue584()
{
var excelconfig = new OpenXmlConfiguration
{
DynamicColumns =
[
new DynamicExcelColumn("Id") { Ignore = true }
]
};

await using var conn = Db.GetConnection();
conn.Open();

await using var cmd = conn.CreateCommand();
cmd.CommandText =
"""
WITH test('Id', 'Name') AS (
VALUES
(1, 'test1'),
(2, 'test2'),
(3, 'test3')
)
SELECT * FROM test;
""";

await using var reader = cmd.ExecuteReader();

using var path = AutoDeletingPath.Create();
await MiniExcel.SaveAsAsync(path.FilePath, reader, configuration: excelconfig, overwriteFile: true);

var rows = (await MiniExcel.QueryAsync(path.FilePath)).ToList();
Assert.All(rows, x => Assert.Single(x));
Assert.Equal("Name", rows[0].A);
}
}
Loading