diff --git a/.gitignore b/.gitignore index c79821f5..067a3b5c 100644 --- a/.gitignore +++ b/.gitignore @@ -401,5 +401,6 @@ FodyWeavers.xsd /BenchmarkDotNet.Artifacts /tests/MiniExcel.Tests.AspNetMvc/packages /TestTemplate +/tests/data /tests/MiniExcelTests/TemplateOptimization /samples/xlsx/Test_EnableWriteFilePath.xlsx \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs index efa46fd8..9f4d7840 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel/Csv/CsvWriter.cs @@ -109,74 +109,92 @@ private async Task WriteValuesAsync(StreamWriter writer, object values, str { cancellationToken.ThrowIfCancellationRequested(); -#if NETSTANDARD2_0_OR_GREATER || NET IMiniExcelWriteAdapter writeAdapter = null; - if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) +#if NETSTANDARD2_0_OR_GREATER || NET + IAsyncMiniExcelWriteAdapter asyncWriteAdapter = null; +#endif + try { - writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - } - var props = writeAdapter != null ? writeAdapter.GetColumns() : await asyncWriteAdapter.GetColumnsAsync(); +#if NETSTANDARD2_0_OR_GREATER || NET + if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out asyncWriteAdapter)) + { + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + } + + var props = writeAdapter != null + ? writeAdapter.GetColumns() + : await asyncWriteAdapter.GetColumnsAsync(); #else - IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); var props = writeAdapter.GetColumns(); #endif - if (props == null) - { - await _writer.WriteAsync(_configuration.NewLine); - await _writer.FlushAsync(); - return 0; - } - - if (_printHeader) - { - await _writer.WriteAsync(GetHeader(props)); - await _writer.WriteAsync(newLine); - } - - var rowBuilder = new StringBuilder(); - var rowsWritten = 0; - - if (writeAdapter != null) - { - foreach (var row in writeAdapter.GetRows(props, cancellationToken)) + if (props == null) + { + await _writer.WriteAsync(_configuration.NewLine); + await _writer.FlushAsync(); + return 0; + } + + if (_printHeader) { - rowBuilder.Clear(); - foreach (var column in row) + await _writer.WriteAsync(GetHeader(props)); + await _writer.WriteAsync(newLine); + } + + var rowBuilder = new StringBuilder(); + var rowsWritten = 0; + + if (writeAdapter != null) + { + foreach (var row in writeAdapter.GetRows(props, cancellationToken)) { - cancellationToken.ThrowIfCancellationRequested(); - AppendColumn(rowBuilder, column); + rowBuilder.Clear(); + foreach (var column in row) + { + cancellationToken.ThrowIfCancellationRequested(); + AppendColumn(rowBuilder, column); + } + + RemoveTrailingSeparator(rowBuilder); + await _writer.WriteAsync(rowBuilder.ToString()); + await _writer.WriteAsync(newLine); + + rowsWritten++; } - - RemoveTrailingSeparator(rowBuilder); - await _writer.WriteAsync(rowBuilder.ToString()); - await _writer.WriteAsync(newLine); - - rowsWritten++; } - } #if NETSTANDARD2_0_OR_GREATER || NET - else - { - await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) + else { - cancellationToken.ThrowIfCancellationRequested(); - rowBuilder.Clear(); - - await foreach (var column in row) + await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - AppendColumn(rowBuilder, column); + rowBuilder.Clear(); + + await foreach (var column in row) + { + cancellationToken.ThrowIfCancellationRequested(); + AppendColumn(rowBuilder, column); + } + + RemoveTrailingSeparator(rowBuilder); + await _writer.WriteAsync(rowBuilder.ToString()); + await _writer.WriteAsync(newLine); + + rowsWritten++; } - - RemoveTrailingSeparator(rowBuilder); - await _writer.WriteAsync(rowBuilder.ToString()); - await _writer.WriteAsync(newLine); - - rowsWritten++; } +#endif + return rowsWritten; } + finally + { +#if NETSTANDARD2_0_OR_GREATER || NET + if (asyncWriteAdapter is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + } #endif - return rowsWritten; + } } public async Task SaveAsAsync(CancellationToken cancellationToken = default) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 85633cbf..b86c0173 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -171,128 +171,152 @@ private static async Task WriteDimensionAsync(MiniExcelAsyncStreamWriter writer, private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object values, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - -#if NETSTANDARD2_0_OR_GREATER || NET + IMiniExcelWriteAdapter writeAdapter = null; - if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) +#if NETSTANDARD2_0_OR_GREATER || NET + IAsyncMiniExcelWriteAdapter asyncWriteAdapter = null; +#endif + try { - writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - } +#if NETSTANDARD2_0_OR_GREATER || NET + if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out asyncWriteAdapter)) + { + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + } - var count = 0; - var isKnownCount = writeAdapter != null && writeAdapter.TryGetKnownCount(out count); - var props = writeAdapter != null ? writeAdapter?.GetColumns() : await asyncWriteAdapter.GetColumnsAsync(); + var count = 0; + var isKnownCount = writeAdapter != null && writeAdapter.TryGetKnownCount(out count); + var props = writeAdapter != null + ? writeAdapter?.GetColumns() + : await asyncWriteAdapter.GetColumnsAsync(); #else - IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - var isKnownCount = writeAdapter.TryGetKnownCount(out var count); - var props = writeAdapter.GetColumns(); + var isKnownCount = writeAdapter.TryGetKnownCount(out var count); + var props = writeAdapter.GetColumns(); #endif - if (props == null) - { - await WriteEmptySheetAsync(writer); - return 0; - } - var maxColumnIndex = props.Count; - int maxRowIndex; + if (props == null) + { + await WriteEmptySheetAsync(writer); + return 0; + } - await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship); + var maxColumnIndex = props.Count; + int maxRowIndex; - long dimensionPlaceholderPostition = 0; + await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship); - // We can write the dimensions directly if the row count is known - if (_configuration.FastMode && !isKnownCount) - { - dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer); - } - else if (isKnownCount) - { - maxRowIndex = count + (_printHeader ? 1 : 0); - await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count))); - } + long dimensionPlaceholderPostition = 0; - //sheet view - await writer.WriteAsync(GetSheetViews()); + // We can write the dimensions directly if the row count is known + if (_configuration.FastMode && !isKnownCount) + { + dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer); + } + else if (isKnownCount) + { + maxRowIndex = count + (_printHeader ? 1 : 0); + await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count))); + } - //cols:width - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } - else - { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken); - } + //sheet view + await writer.WriteAsync(GetSheetViews()); - //header - await writer.WriteAsync(WorksheetXml.StartSheetData); - var currentRowIndex = 0; - if (_printHeader) - { - await PrintHeaderAsync(writer, props, cancellationToken); - currentRowIndex++; - } + //cols:width + ExcelWidthCollection widths = null; + long columnWidthsPlaceholderPosition = 0; + if (_configuration.EnableAutoWidth) + { + columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props); + widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); + } + else + { + await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken); + } - if (writeAdapter != null) - { - foreach (var row in writeAdapter.GetRows(props, cancellationToken)) + //header + await writer.WriteAsync(WorksheetXml.StartSheetData); + var currentRowIndex = 0; + if (_printHeader) { - cancellationToken.ThrowIfCancellationRequested(); - - await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); - foreach (var cellValue in row) + await PrintHeaderAsync(writer, props, cancellationToken); + currentRowIndex++; + } + + if (writeAdapter != null) + { + foreach (var row in writeAdapter.GetRows(props, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths); + + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); + foreach (var cellValue in row) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, + cellValue.Prop, widths); + } + + await writer.WriteAsync(WorksheetXml.EndRow); } - await writer.WriteAsync(WorksheetXml.EndRow); } - } #if NETSTANDARD2_0_OR_GREATER || NET - else - { - await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) + else { - cancellationToken.ThrowIfCancellationRequested(); - await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); - - await foreach (var cellValue in row) + await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths); + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); + + await foreach (var cellValue in row) + { + cancellationToken.ThrowIfCancellationRequested(); + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, + cellValue.Prop, widths); + } + + await writer.WriteAsync(WorksheetXml.EndRow); } - await writer.WriteAsync(WorksheetXml.EndRow); } - } #endif - maxRowIndex = currentRowIndex; + maxRowIndex = currentRowIndex; - await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex)); - await writer.WriteAsync(WorksheetXml.EndSheetData); + await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex)); + await writer.WriteAsync(WorksheetXml.EndSheetData); - if (_configuration.AutoFilter) - { - await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); - } + if (_configuration.AutoFilter) + { + await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); + } - await writer.WriteAsync(WorksheetXml.EndWorksheet); + await writer.WriteAsync(WorksheetXml.EndWorksheet); - if (_configuration.FastMode && dimensionPlaceholderPostition != 0) - { - await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); + if (_configuration.FastMode && dimensionPlaceholderPostition != 0) + { + await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); + } + + if (_configuration.EnableAutoWidth) + { + await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns, + cancellationToken); + } + + var toSubtract = _printHeader ? 1 : 0; + return maxRowIndex - toSubtract; } - if (_configuration.EnableAutoWidth) + finally { - await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns, cancellationToken); +#if NETSTANDARD2_0_OR_GREATER || NET + if (asyncWriteAdapter is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + } +#endif } - - var toSubtract = _printHeader ? 1 : 0; - return maxRowIndex - toSubtract; } private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, ICollection props) diff --git a/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs b/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs index 5c8811a7..1f3e0d53 100644 --- a/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs +++ b/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs @@ -1,4 +1,5 @@ -using MiniExcelLibs.Utils; +using System; +using MiniExcelLibs.Utils; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -8,12 +9,14 @@ #if NETSTANDARD2_0_OR_GREATER || NET namespace MiniExcelLibs.WriteAdapter { - internal class AsyncEnumerableWriteAdapter : IAsyncMiniExcelWriteAdapter + internal class AsyncEnumerableWriteAdapter : IAsyncMiniExcelWriteAdapter, IAsyncDisposable { private readonly IAsyncEnumerable _values; private readonly Configuration _configuration; + private IAsyncEnumerator _enumerator; private bool _empty; + private bool _disposed; public AsyncEnumerableWriteAdapter(IAsyncEnumerable values, Configuration configuration) { @@ -46,7 +49,7 @@ public async IAsyncEnumerable> GetRowsAsync(List if (_enumerator is null) { - _enumerator = _values.GetAsyncEnumerator(); + _enumerator = _values.GetAsyncEnumerator(cancellationToken); if (!await _enumerator.MoveNextAsync()) { yield break; @@ -58,36 +61,40 @@ public async IAsyncEnumerable> GetRowsAsync(List cancellationToken.ThrowIfCancellationRequested(); yield return GetRowValuesAsync(_enumerator.Current, props); - } while (await _enumerator.MoveNextAsync()); + } + while (await _enumerator.MoveNextAsync()); } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public async static IAsyncEnumerable GetRowValuesAsync(T currentValue, List props) -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + private static async IAsyncEnumerable GetRowValuesAsync(T currentValue, List props) +#pragma warning restore CS1998 { - var column = 1; + var column = 0; foreach (var prop in props) { - if (prop == null) - { - column++; + column++; + + if (prop is null) continue; - } - switch (currentValue) + yield return currentValue switch { - case IDictionary genericDictionary: - yield return new CellWriteInfo(genericDictionary[prop.Key.ToString()], column, prop); - break; - case IDictionary dictionary: - yield return new CellWriteInfo(dictionary[prop.Key], column, prop); - break; - default: - yield return new CellWriteInfo(prop.Property.GetValue(currentValue), column, prop); - break; - } + IDictionary 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) + }; + } + } - column++; + public async ValueTask DisposeAsync() + { + if (!_disposed) + { + if (_enumerator != null) + { + await _enumerator.DisposeAsync().ConfigureAwait(false); + } + _disposed = true; } } } diff --git a/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs b/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs index 7f6eb97b..a883c914 100644 --- a/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs +++ b/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs @@ -14,7 +14,7 @@ public static bool TryGetAsyncWriteAdapter(object values, Configuration configur if (values.GetType().IsAsyncEnumerable(out var genericArgument)) { var writeAdapterType = typeof(AsyncEnumerableWriteAdapter<>).MakeGenericType(genericArgument); - writeAdapter = (IAsyncMiniExcelWriteAdapter)Activator.CreateInstance(writeAdapterType, values, configuration); + writeAdapter = Activator.CreateInstance(writeAdapterType, values, configuration) as IAsyncMiniExcelWriteAdapter; return true; } if (values is IMiniExcelDataReader miniExcelDataReader)