diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs index 9b4e8c70..54998b94 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel/Csv/CsvWriter.cs @@ -1,11 +1,11 @@ using MiniExcelLibs.Utils; +using MiniExcelLibs.WriteAdapter; using System; -using System.Collections; using System.Collections.Generic; -using System.Data; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -31,42 +31,15 @@ public CsvWriter(Stream stream, object value, IConfiguration configuration, bool public void SaveAs() { - var seperator = _configuration.Seperator.ToString(); - var newLine = _configuration.NewLine; + if (_value == null) { - if (_value == null) - { - _writer.Write(""); - this._writer.Flush(); - return; - } - - var type = _value.GetType(); - - if (_value is IDataReader dataReader) - { - GenerateSheetByIDataReader(dataReader, seperator, newLine, _writer); - } - else if (_value is IEnumerable enumerable) - { - GenerateSheetByIEnumerable(enumerable, seperator, newLine, _writer); - } - else if (_value is DataTable dataTable) - { - GenerateSheetByDataTable(_writer, dataTable, seperator, newLine); - } - else - { - throw new NotImplementedException($"Type {type?.Name} not Implemented. please issue for me."); - } - - this._writer.Flush(); + _writer.Write(""); + _writer.Flush(); + return; } - } - public async Task SaveAsAsync(CancellationToken cancellationToken = default) - { - await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false); + WriteValues(_writer, _value); + _writer.Flush(); } public void Insert(bool overwriteSheet = false) @@ -74,162 +47,136 @@ public void Insert(bool overwriteSheet = false) SaveAs(); } - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column) { - await Task.Run(() => SaveAs(), cancellationToken).ConfigureAwait(false); + rowBuilder.Append(CsvHelpers.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration.AlwaysQuote, _configuration.Seperator)); + rowBuilder.Append(_configuration.Seperator); } - private void GenerateSheetByIEnumerable(IEnumerable values, string seperator, string newLine, StreamWriter writer) + private void RemoveTrailingSeparator(StringBuilder rowBuilder) { - Type genericType = null; - List props = null; - string mode = null; - - var enumerator = values.GetEnumerator(); - var empty = !enumerator.MoveNext(); - if (empty) - { - // only when empty IEnumerable need to check this issue #133 https://github.com/shps951023/MiniExcel/issues/133 - genericType = TypeHelper.GetGenericIEnumerables(values).FirstOrDefault(); - if (genericType == null || genericType == typeof(object) // sometime generic type will be object, e.g: https://user-images.githubusercontent.com/12729184/132812859-52984314-44d1-4ee8-9487-2d1da159f1f0.png - || typeof(IDictionary).IsAssignableFrom(genericType) - || typeof(IDictionary).IsAssignableFrom(genericType) - || typeof(KeyValuePair).IsAssignableFrom(genericType)) - { - _writer.Write(newLine); - this._writer.Flush(); - return; - } - - mode = "Properties"; - props = CustomPropertyHelper.GetSaveAsProperties(genericType, _configuration); - } - else + if (rowBuilder.Length == 0) { - var firstItem = enumerator.Current; - if (firstItem is IDictionary genericDic) - { - mode = "IDictionary"; - props = CustomPropertyHelper.GetDictionaryColumnInfo(genericDic, null, _configuration); - } - else if (firstItem is IDictionary dic) - { - mode = "IDictionary"; - props = CustomPropertyHelper.GetDictionaryColumnInfo(null, dic, _configuration); - mode = "IDictionary"; - } - else - { - mode = "Properties"; - genericType = firstItem.GetType(); - props = CustomPropertyHelper.GetSaveAsProperties(genericType, _configuration); - } - } - - if (this._printHeader) - { - _writer.Write(string.Join(seperator, props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration.AlwaysQuote, _configuration.Seperator)))); - _writer.Write(newLine); - } - - if (!empty) - { - if (mode == "IDictionary") //Dapper Row - GenerateSheetByDapperRow(_writer, enumerator, props.Select(x => x.Key.ToString()).ToList(), seperator, newLine); - else if (mode == "IDictionary") //IDictionary - GenerateSheetByIDictionary(_writer, enumerator, props.Select(x => x.Key).ToList(), seperator, newLine); - else if (mode == "Properties") - GenerateSheetByProperties(_writer, enumerator, props, seperator, newLine); - else - throw new NotImplementedException($"Mode for genericType {genericType?.Name} not Implemented. please issue for me."); + return; } + rowBuilder.Remove(rowBuilder.Length - 1, 1); } - private void GenerateSheetByIDataReader(IDataReader reader, string seperator, string newLine, StreamWriter writer) + private string GetHeader(List props) => string.Join( + _configuration.Seperator.ToString(), + props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration.AlwaysQuote, _configuration.Seperator))); + + private void WriteValues(StreamWriter writer, object values) { - int fieldCount = reader.FieldCount; - if (fieldCount == 0) - throw new InvalidDataException("fieldCount is 0"); + IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - if (this._printHeader) + var props = writeAdapter.GetColumns(); + if (props == null) { - for (int i = 0; i < fieldCount; i++) - { - var columnName = reader.GetName(i); + _writer.Write(_configuration.NewLine); + _writer.Flush(); + return; + } - if (i != 0) - writer.Write(seperator); - writer.Write(CsvHelpers.ConvertToCsvValue(ToCsvString(columnName, null), _configuration.AlwaysQuote, _configuration.Seperator)); - } - writer.Write(newLine); + if (_printHeader) + { + _writer.Write(GetHeader(props)); + _writer.Write(_configuration.NewLine); } - while (reader.Read()) + var rowBuilder = new StringBuilder(); + if (writeAdapter != null) { - for (int i = 0; i < fieldCount; i++) + foreach (var row in writeAdapter.GetRows(props)) { - var cellValue = reader.GetValue(i); - if (i != 0) - writer.Write(seperator); - writer.Write(CsvHelpers.ConvertToCsvValue(ToCsvString(cellValue, null), _configuration.AlwaysQuote, _configuration.Seperator)); + rowBuilder.Clear(); + foreach (var column in row) + { + AppendColumn(rowBuilder, column); + } + RemoveTrailingSeparator(rowBuilder); + _writer.Write(rowBuilder.ToString()); + _writer.Write(_configuration.NewLine); } - writer.Write(newLine); } } - private void GenerateSheetByDataTable(StreamWriter writer, DataTable dt, string seperator, string newLine) + private async Task WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken) { +#if NETSTANDARD2_0_OR_GREATER || NET + IMiniExcelWriteAdapter writeAdapter = null; + if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) + { + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + } + var props = writeAdapter != null ? writeAdapter.GetColumns() : await asyncWriteAdapter.GetColumnsAsync(); +#else + IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + var props = writeAdapter.GetColumns(); +#endif + if (props == null) + { + await _writer.WriteAsync(_configuration.NewLine); + await _writer.FlushAsync(); + return; + } if (_printHeader) { - writer.Write(string.Join(seperator, dt.Columns.Cast().Select(s => CsvHelpers.ConvertToCsvValue(s.Caption ?? s.ColumnName, _configuration.AlwaysQuote, _configuration.Seperator)))); - writer.Write(newLine); + await _writer.WriteAsync(GetHeader(props)); + await _writer.WriteAsync(newLine); } - for (int i = 0; i < dt.Rows.Count; i++) + var rowBuilder = new StringBuilder(); + if (writeAdapter != null) { - var first = true; - for (int j = 0; j < dt.Columns.Count; j++) + foreach (var row in writeAdapter.GetRows(props, cancellationToken)) { - var cellValue = CsvHelpers.ConvertToCsvValue(ToCsvString(dt.Rows[i][j], null), _configuration.AlwaysQuote, _configuration.Seperator); - if (!first) - writer.Write(seperator); - writer.Write(cellValue); - first = false; + rowBuilder.Clear(); + foreach (var column in row) + { + AppendColumn(rowBuilder, column); + } + RemoveTrailingSeparator(rowBuilder); + await _writer.WriteAsync(rowBuilder.ToString()); + await _writer.WriteAsync(newLine); } - writer.Write(newLine); } - } - - private void GenerateSheetByProperties(StreamWriter writer, IEnumerator value, List props, string seperator, string newLine) - { - do +#if NETSTANDARD2_0_OR_GREATER || NET + else { - var v = value.Current; - var values = props.Select(s => CsvHelpers.ConvertToCsvValue(ToCsvString(s?.Property.GetValue(v), s), _configuration.AlwaysQuote, _configuration.Seperator)); - writer.Write(string.Join(seperator, values)); - writer.Write(newLine); - } while (value.MoveNext()); + await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) + { + rowBuilder.Clear(); + await foreach (var column in row) + { + AppendColumn(rowBuilder, column); + } + RemoveTrailingSeparator(rowBuilder); + await _writer.WriteAsync(rowBuilder.ToString()); + await _writer.WriteAsync(newLine); + } + } +#endif } - private void GenerateSheetByIDictionary(StreamWriter writer, IEnumerator value, List keys, string seperator, string newLine) + public async Task SaveAsAsync(CancellationToken cancellationToken = default) { - do + var seperator = _configuration.Seperator.ToString(); + var newLine = _configuration.NewLine; + + if (_value == null) { - var v = (IDictionary)value.Current; - var values = keys.Select(key => CsvHelpers.ConvertToCsvValue(ToCsvString(v[key], null), _configuration.AlwaysQuote, _configuration.Seperator)); - writer.Write(string.Join(seperator, values)); - writer.Write(newLine); - } while (value.MoveNext()); + await _writer.WriteAsync(""); + await _writer.FlushAsync(); + return; + } + + await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken); + await _writer.FlushAsync(); } - private void GenerateSheetByDapperRow(StreamWriter writer, IEnumerator value, List keys, string seperator, string newLine) + public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) { - do - { - var v = (IDictionary)value.Current; - var values = keys.Select(key => CsvHelpers.ConvertToCsvValue(ToCsvString(v[key], null), _configuration.AlwaysQuote, _configuration.Seperator)); - writer.Write(string.Join(seperator, values)); - writer.Write(newLine); - } while (value.MoveNext()); + await SaveAsAsync(cancellationToken); } public string ToCsvString(object value, ExcelColumnInfo p) diff --git a/src/MiniExcel/MiniExcelLibs.csproj b/src/MiniExcel/MiniExcelLibs.csproj index 6cb332ce..976c0843 100644 --- a/src/MiniExcel/MiniExcelLibs.csproj +++ b/src/MiniExcel/MiniExcelLibs.csproj @@ -3,6 +3,9 @@ net45;netstandard2.0;net8.0; 1.36.1 + + 8 + MiniExcel Mini-Software @@ -35,9 +38,6 @@ Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=truesnupkg README.md - - - @@ -56,4 +56,9 @@ Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true + + + 9.0.0 + + diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index d0b7fab1..a4823bf2 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -2,11 +2,10 @@ using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.OpenXml.Styles; using MiniExcelLibs.Utils; +using MiniExcelLibs.WriteAdapter; using MiniExcelLibs.Zip; using System; -using System.Collections; using System.Collections.Generic; -using System.Data; using System.IO.Compression; using System.Linq; using System.Text; @@ -102,34 +101,19 @@ internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationTo await GenerateStylesXmlAsync(cancellationToken); } - private async Task CreateSheetXmlAsync(object value, string sheetPath, CancellationToken cancellationToken) + private async Task CreateSheetXmlAsync(object values, string sheetPath, CancellationToken cancellationToken) { ZipArchiveEntry entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); using (var zipStream = entry.Open()) using (MiniExcelAsyncStreamWriter writer = new MiniExcelAsyncStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize, cancellationToken)) { - if (value == null) + if (values == null) { await WriteEmptySheetAsync(writer); } else { - //DapperRow - - switch (value) - { - case IDataReader dataReader: - await GenerateSheetByIDataReaderAsync(writer, dataReader, cancellationToken); - break; - case IEnumerable enumerable: - await GenerateSheetByEnumerableAsync(writer, enumerable); - break; - case DataTable dataTable: - await GenerateSheetByDataTableAsync(writer, dataTable); - break; - default: - throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); - } + await WriteValuesAsync(writer, values, cancellationToken); } } _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); @@ -159,315 +143,46 @@ private async Task WriteDimensionAsync(MiniExcelAsyncStreamWriter writer, int ma writer.SetPosition(position); } - private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter writer, IDataReader reader, CancellationToken cancellationToken) + private async Task WriteValuesAsync(MiniExcelAsyncStreamWriter writer, object values, CancellationToken cancellationToken) { - if (reader is IMiniExcelDataReader miniExcelDataReader) +#if NETSTANDARD2_0_OR_GREATER || NET + IMiniExcelWriteAdapter writeAdapter = null; + if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) { - await GenerateSheetByIMiniExcelDataReaderAsync(writer, miniExcelDataReader, cancellationToken); - return; + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); } - long dimensionPlaceholderPostition = 0; - await writer.WriteAsync(WorksheetXml.StartWorksheet); - var yIndex = 1; - int maxColumnIndex; - int maxRowIndex; - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - { - if (_configuration.FastMode) - { - dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer); - } + 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); - var props = new List(); - for (var i = 0; i < reader.FieldCount; i++) - { - var columnName = reader.GetName(i); + var isKnownCount = writeAdapter.TryGetKnownCount(out var count); + var props = writeAdapter.GetColumns(); +#endif - if (!_configuration.DynamicColumnFirst) - { - var prop = GetColumnInfosFromDynamicConfiguration(columnName); - props.Add(prop); - continue; - } - - if (_configuration - .DynamicColumns - .Any(a => string.Equals( - a.Key, - columnName, - StringComparison.OrdinalIgnoreCase))) - - { - var prop = GetColumnInfosFromDynamicConfiguration(columnName); - props.Add(prop); - } - } - maxColumnIndex = props.Count; - - //sheet view - await writer.WriteAsync(GetSheetViews()); - - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } - else - { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props)); - } - - await writer.WriteAsync(WorksheetXml.StartSheetData); - int fieldCount = reader.FieldCount; - if (_printHeader) - { - await PrintHeaderAsync(writer, props); - if (props.Count > 0) - { - yIndex++; - } - } - - while (reader.Read()) - { - await writer.WriteAsync(WorksheetXml.StartRow(yIndex)); - var xIndex = 1; - for (int i = 0; i < fieldCount; i++) - { - object cellValue; - - if (_configuration.DynamicColumnFirst) - { - var columnIndex = reader.GetOrdinal(props[i].Key.ToString()); - cellValue = reader.GetValue(columnIndex); - } - else - { - cellValue = reader.GetValue(i); - } - - await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[i], widths); - xIndex++; - } - await writer.WriteAsync(WorksheetXml.EndRow); - yIndex++; - } - - // Subtract 1 because cell indexing starts with 1 - maxRowIndex = yIndex - 1; - } - - await writer.WriteAsync(WorksheetXml.EndSheetData); - - if (_configuration.AutoFilter) - { - await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); - } - - await writer.WriteAndFlushAsync(WorksheetXml.EndWorksheet); - - if (_configuration.FastMode) - { - await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); - } - if (_configuration.EnableAutoWidth) + if (props == null) { - await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns); + await WriteEmptySheetAsync(writer); + return; } - } - - private async Task GenerateSheetByIMiniExcelDataReaderAsync(MiniExcelAsyncStreamWriter writer, IMiniExcelDataReader reader, CancellationToken cancellationToken) - { - long dimensionPlaceholderPostition = 0; - await writer.WriteAsync(WorksheetXml.StartWorksheet); - var yIndex = 1; - int maxColumnIndex; + var maxColumnIndex = props.Count; int maxRowIndex; - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - { - if (_configuration.FastMode) - { - dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer); - } - - var props = new List(); - for (var i = 0; i < reader.FieldCount; i++) - { - var columnName = await reader.GetNameAsync(i, cancellationToken); - - if (!_configuration.DynamicColumnFirst) - { - var prop = GetColumnInfosFromDynamicConfiguration(columnName); - props.Add(prop); - continue; - } - - if (_configuration - .DynamicColumns - .Any(a => string.Equals( - a.Key, - columnName, - StringComparison.OrdinalIgnoreCase))) - - { - var prop = GetColumnInfosFromDynamicConfiguration(columnName); - props.Add(prop); - } - } - maxColumnIndex = props.Count; - - //sheet view - await writer.WriteAsync(GetSheetViews()); - - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } - else - { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props)); - } - - await writer.WriteAsync(WorksheetXml.StartSheetData); - int fieldCount = reader.FieldCount; - if (_printHeader) - { - await PrintHeaderAsync(writer, props); - if (props.Count > 0) - { - yIndex++; - } - } - - while (await reader.ReadAsync(cancellationToken)) - { - await writer.WriteAsync(WorksheetXml.StartRow(yIndex)); - var xIndex = 1; - for (int i = 0; i < fieldCount; i++) - { - object cellValue; - - if (_configuration.DynamicColumnFirst) - { - var columnIndex = reader.GetOrdinal(props[i].Key.ToString()); - cellValue = await reader.GetValueAsync(columnIndex, cancellationToken); - } - else - { - cellValue = await reader.GetValueAsync(i, cancellationToken); - } - - await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[i], widths); - xIndex++; - } - await writer.WriteAsync(WorksheetXml.EndRow); - yIndex++; - } - - // Subtract 1 because cell indexing starts with 1 - maxRowIndex = yIndex - 1; - } - - await writer.WriteAsync(WorksheetXml.EndSheetData); - - if (_configuration.AutoFilter) - { - await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); - } - - await writer.WriteAndFlushAsync(WorksheetXml.EndWorksheet); - - if (_configuration.FastMode) - { - await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); - } - if (_configuration.EnableAutoWidth) - { - await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns); - } - } - - private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter writer, IEnumerable values) - { - var maxColumnIndex = 0; - var maxRowIndex = 0; - List props = null; - string mode = null; - - int? rowCount = null; - var collection = values as ICollection; - if (collection != null) - { - rowCount = collection.Count; - } - else if (!_configuration.FastMode) - { - // The row count is only required up front when not in fastmode - collection = new List(values.Cast()); - rowCount = collection.Count; - } - - // Get the enumerator once to ensure deferred linq execution - var enumerator = (collection ?? values).GetEnumerator(); - - // Move to the first item in order to inspect the value type and determine whether it is empty - var empty = !enumerator.MoveNext(); - - if (empty) - { - // only when empty IEnumerable need to check this issue #133 https://github.com/shps951023/MiniExcel/issues/133 - var genericType = TypeHelper.GetGenericIEnumerables(values).FirstOrDefault(); - if (genericType == null || genericType == typeof(object) // sometime generic type will be object, e.g: https://user-images.githubusercontent.com/12729184/132812859-52984314-44d1-4ee8-9487-2d1da159f1f0.png - || typeof(IDictionary).IsAssignableFrom(genericType) - || typeof(IDictionary).IsAssignableFrom(genericType)) - { - await WriteEmptySheetAsync(writer); - return; - } - else - { - SetGenericTypePropertiesMode(genericType, ref mode, out maxColumnIndex, out props); - } - } - else - { - var firstItem = enumerator.Current; - if (firstItem is IDictionary genericDic) - { - mode = "IDictionary"; - props = CustomPropertyHelper.GetDictionaryColumnInfo(genericDic, null, _configuration); - maxColumnIndex = props.Count; - } - else if (firstItem is IDictionary dic) - { - mode = "IDictionary"; - props = CustomPropertyHelper.GetDictionaryColumnInfo(null, dic, _configuration); - //maxColumnIndex = dic.Keys.Count; - maxColumnIndex = props.Count; // why not using keys, because ignore attribute ![image](https://user-images.githubusercontent.com/12729184/163686902-286abb70-877b-4e84-bd3b-001ad339a84a.png) - } - else - { - SetGenericTypePropertiesMode(firstItem.GetType(), ref mode, out maxColumnIndex, out props); - } - } await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship); long dimensionPlaceholderPostition = 0; // We can write the dimensions directly if the row count is known - if (_configuration.FastMode && rowCount == null) + if (_configuration.FastMode && !isKnownCount) { dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer); } - else + else if (isKnownCount) { - maxRowIndex = rowCount.Value + (_printHeader && rowCount > 0 ? 1 : 0); - await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, maxColumnIndex))); + maxRowIndex = count + (_printHeader ? 1 : 0); + await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count))); } //sheet view @@ -488,131 +203,60 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri //header await writer.WriteAsync(WorksheetXml.StartSheetData); - var yIndex = 1; - var xIndex = 1; + var currentRowIndex = 0; if (_printHeader) { await PrintHeaderAsync(writer, props); - yIndex++; + currentRowIndex++; } - if (!empty) + if (writeAdapter != null) { - // body - switch (mode) + foreach (var row in writeAdapter.GetRows(props, cancellationToken)) { - case "IDictionary": //Dapper Row - maxRowIndex = await GenerateSheetByColumnInfoAsync>(writer, enumerator, props, widths, xIndex, yIndex); - break; - case "IDictionary": - maxRowIndex = await GenerateSheetByColumnInfoAsync(writer, enumerator, props, widths, xIndex, yIndex); - break; - case "Properties": - maxRowIndex = await GenerateSheetByColumnInfoAsync(writer, enumerator, props, widths, xIndex, yIndex); - break; - default: - throw new NotImplementedException($"Type {values.GetType().FullName} is not implemented. Please open an issue."); + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); + foreach (var cellValue in row) + { + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths); + } + await writer.WriteAsync(WorksheetXml.EndRow); } } - - await writer.WriteAsync(WorksheetXml.EndSheetData); - if (_configuration.AutoFilter) - { - await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); - } - - await writer.WriteAsync(WorksheetXml.Drawing(currentSheetIndex)); - await writer.WriteAsync(WorksheetXml.EndWorksheet); - - // The dimension has already been written if row count is defined - if (_configuration.FastMode && rowCount == null) - { - await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); - } - if (_configuration.EnableAutoWidth) - { - await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns); - } - } - - private async Task GenerateSheetByDataTableAsync(MiniExcelAsyncStreamWriter writer, DataTable value) - { - var xy = ExcelOpenXmlUtils.ConvertCellToXY("A1"); - - await writer.WriteAsync(WorksheetXml.StartWorksheet); - var yIndex = xy.Item2; - - // dimension - var maxRowIndex = value.Rows.Count + (_printHeader && value.Rows.Count > 0 ? 1 : 0); - var maxColumnIndex = value.Columns.Count; - await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, maxColumnIndex))); - - var props = new List(); - for (var i = 0; i < value.Columns.Count; i++) - { - var columnName = value.Columns[i].Caption ?? value.Columns[i].ColumnName; - var prop = GetColumnInfosFromDynamicConfiguration(columnName); - props.Add(prop); - } - - //sheet view - await writer.WriteAsync(GetSheetViews()); - - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, props); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } +#if NETSTANDARD2_0_OR_GREATER || NET else { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props)); - } - - await writer.WriteAsync(WorksheetXml.StartSheetData); - if (_printHeader) - { - await writer.WriteAsync(WorksheetXml.StartRow(yIndex)); - var xIndex = xy.Item1; - foreach (var p in props) + await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken)) { - var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex); - await WriteCellAsync(writer, r, columnName: p.ExcelColumnName); - xIndex++; + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex)); + await foreach (var cellValue in row) + { + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths); + } + await writer.WriteAsync(WorksheetXml.EndRow); } - - await writer.WriteAsync(WorksheetXml.EndRow); - yIndex++; } +#endif - for (int i = 0; i < value.Rows.Count; i++) - { - await writer.WriteAsync(WorksheetXml.StartRow(yIndex)); - var xIndex = xy.Item1; - - for (int j = 0; j < value.Columns.Count; j++) - { - var cellValue = value.Rows[i][j]; - await WriteCellAsync(writer, yIndex, xIndex, cellValue, props[j], widths); - xIndex++; - } - await writer.WriteAsync(WorksheetXml.EndRow); - yIndex++; - } + maxRowIndex = currentRowIndex; + await writer.WriteAsync(WorksheetXml.Drawing(currentSheetIndex)); await writer.WriteAsync(WorksheetXml.EndSheetData); if (_configuration.AutoFilter) { await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); } + + await writer.WriteAsync(WorksheetXml.EndWorksheet); + + if (_configuration.FastMode && dimensionPlaceholderPostition != default) + { + await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); + } if (_configuration.EnableAutoWidth) { await OverWriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths.Columns); } - - await writer.WriteAsync(WorksheetXml.EndWorksheet); } private async Task WriteColumnWidthPlaceholdersAsync(MiniExcelAsyncStreamWriter writer, ICollection props) @@ -674,53 +318,6 @@ private async Task PrintHeaderAsync(MiniExcelAsyncStreamWriter writer, List GenerateSheetByColumnInfoAsync(MiniExcelAsyncStreamWriter writer, IEnumerator value, List props, ExcelWidthCollection widthCollection, int xIndex = 1, int yIndex = 1) - { - var isDic = typeof(T) == typeof(IDictionary); - var isDapperRow = typeof(T) == typeof(IDictionary); - do - { - // The enumerator has already moved to the first item - T v = (T)value.Current; - - await writer.WriteAsync(WorksheetXml.StartRow(yIndex)); - var cellIndex = xIndex; - foreach (var p in props) - { - if (p == null) //reason:https://github.com/shps951023/MiniExcel/issues/142 - { - cellIndex++; - continue; - } - - object cellValue = null; - if (isDic) - { - cellValue = ((IDictionary)v)[p.Key]; - //WriteCell(writer, yIndex, cellIndex, cellValue, null); // why null because dictionary that needs to check type every time - //TODO: user can specefic type to optimize efficiency - } - else if (isDapperRow) - { - cellValue = ((IDictionary)v)[p.Key.ToString()]; - } - else - { - cellValue = p.Property.GetValue(v); - } - - await WriteCellAsync(writer, yIndex, cellIndex, cellValue, p, widthCollection); - - cellIndex++; - } - - await writer.WriteAsync(WorksheetXml.EndRow); - yIndex++; - } while (value.MoveNext()); - - return yIndex - 1; - } - private async Task WriteCellAsync(MiniExcelAsyncStreamWriter writer, string cellReference, string columnName) { await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", GetCellXfId("1"), ExcelOpenXmlUtils.EncodeXML(columnName))); diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index fc1d65ff..a61e38e6 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -177,73 +177,6 @@ private string GetPanes() } - private ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName) - { - var prop = new ExcelColumnInfo - { - ExcelColumnName = columnName, - Key = columnName - }; - - if (_configuration.DynamicColumns == null || _configuration.DynamicColumns.Length <= 0) - return prop; - - var dynamicColumn = _configuration.DynamicColumns.SingleOrDefault(_ => string.Equals(_.Key, columnName, StringComparison.OrdinalIgnoreCase)); - if (dynamicColumn == null || dynamicColumn.Ignore) - { - return prop; - } - - prop.Nullable = true; - prop.ExcelIgnore = dynamicColumn.Ignore; - prop.ExcelColumnType = dynamicColumn.Type; - prop.ExcelColumnIndex = dynamicColumn.Index; - prop.ExcelColumnWidth = dynamicColumn.Width; - prop.CustomFormatter = dynamicColumn.CustomFormatter; - //prop.ExcludeNullableType = item2[key]?.GetType(); - - if (dynamicColumn.Format != null) - { - prop.ExcelFormat = dynamicColumn.Format; - prop.ExcelFormatId = dynamicColumn.FormatId; - } - - if (dynamicColumn.Aliases != null) - { - prop.ExcelColumnAliases = dynamicColumn.Aliases; - } - - if (dynamicColumn.IndexName != null) - { - prop.ExcelIndexName = dynamicColumn.IndexName; - } - - if (dynamicColumn.Name != null) - { - prop.ExcelColumnName = dynamicColumn.Name; - } - - return prop; - } - - private void SetGenericTypePropertiesMode(Type genericType, ref string mode, out int maxColumnIndex, out List props) - { - mode = "Properties"; - if (genericType.IsValueType) - { - throw new NotImplementedException($"MiniExcel not support only {genericType.Name} value generic type"); - } - - if (genericType == typeof(string) || genericType == typeof(DateTime) || genericType == typeof(Guid)) - { - throw new NotImplementedException($"MiniExcel not support only {genericType.Name} generic type"); - } - - props = CustomPropertyHelper.GetSaveAsProperties(genericType, _configuration); - - maxColumnIndex = props.Count; - } - private Tuple GetCellValue(int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, bool valueIsNull) { if (valueIsNull) @@ -446,7 +379,7 @@ private string GetDimensionRef(int maxRowIndex, int maxColumnIndex) string dimensionRef; if (maxRowIndex == 0 && maxColumnIndex == 0) dimensionRef = "A1"; - else if (maxColumnIndex == 1) + else if (maxColumnIndex <= 1) dimensionRef = $"A{maxRowIndex}"; else if (maxRowIndex == 0) dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}1"; diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 1dee3a4d..d533e335 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -3,11 +3,11 @@ using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.OpenXml.Styles; using MiniExcelLibs.Utils; +using MiniExcelLibs.WriteAdapter; using MiniExcelLibs.Zip; using System; using System.Collections; using System.Collections.Generic; -using System.Data; using System.IO; using System.IO.Compression; using System.Linq; @@ -137,36 +137,19 @@ internal void GenerateDefaultOpenXml() GenerateStylesXml(); } - private void CreateSheetXml(object value, string sheetPath) + private void CreateSheetXml(object values, string sheetPath) { ZipArchiveEntry entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); using (var zipStream = entry.Open()) using (MiniExcelStreamWriter writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) { - if (value == null) + if (values == null) { WriteEmptySheet(writer); } else { - //DapperRow - - if (value is IDataReader) - { - GenerateSheetByIDataReader(writer, value as IDataReader); - } - else if (value is IEnumerable) - { - GenerateSheetByEnumerable(writer, value as IEnumerable); - } - else if (value is DataTable) - { - GenerateSheetByDataTable(writer, value as DataTable); - } - else - { - throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); - } + WriteValues(writer, values); } } _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); @@ -196,169 +179,33 @@ private void WriteDimension(MiniExcelStreamWriter writer, int maxRowIndex, int m writer.SetPosition(position); } - private void GenerateSheetByIDataReader(MiniExcelStreamWriter writer, IDataReader reader) + private void WriteValues(MiniExcelStreamWriter writer, object values) { - long dimensionPlaceholderPosition = 0; - writer.Write(WorksheetXml.StartWorksheet); - var yIndex = 1; - int maxColumnIndex; - int maxRowIndex; - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - { - if (_configuration.FastMode) - { - dimensionPlaceholderPosition = WriteDimensionPlaceholder(writer); - } - - var props = new List(); - for (var i = 0; i < reader.FieldCount; i++) - { - var columnName = reader.GetName(i); - var prop = GetColumnInfosFromDynamicConfiguration(columnName); - props.Add(prop); - } - maxColumnIndex = props.Count; - - //sheet view - writer.Write(GetSheetViews()); - - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = WriteColumnWidthPlaceholders(writer, props); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } - else - { - WriteColumnsWidths(writer, ExcelColumnWidth.FromProps(props)); - } - - writer.Write(WorksheetXml.StartSheetData); - int fieldCount = reader.FieldCount; - if (_printHeader) - { - PrintHeader(writer, props); - if (props.Count > 0) - { - yIndex++; - } - } - - while (reader.Read()) - { - writer.Write(WorksheetXml.StartRow(yIndex)); - var xIndex = 1; - for (int i = 0; i < fieldCount; i++) - { - var cellValue = reader.GetValue(i); - WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: props?.FirstOrDefault(x => x?.ExcelColumnIndex == xIndex - 1), widths); - xIndex++; - } - writer.Write(WorksheetXml.EndRow); - yIndex++; - } - - // Subtract 1 because cell indexing starts with 1 - maxRowIndex = yIndex - 1; - } - writer.Write(WorksheetXml.EndSheetData); - - if (_configuration.AutoFilter) - { - writer.Write(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); - } - - writer.WriteAndFlush(WorksheetXml.EndWorksheet); - - if (_configuration.FastMode) - { - WriteDimension(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPosition); - } + IMiniExcelWriteAdapter writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - if (_configuration.EnableAutoWidth) - { - OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths.Columns); - } - - } - - private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable values) - { - var maxRowIndex = 0; - string mode = null; - - int? rowCount = null; - var collection = values as ICollection; - if (collection != null) - { - rowCount = collection.Count; - } - else if (!_configuration.FastMode) - { - // The row count is only required up front when not in fastmode - collection = new List(values.Cast()); - rowCount = collection.Count; - } - - // Get the enumerator once to ensure deferred linq execution - var enumerator = (collection ?? values).GetEnumerator(); - - // Move to the first item in order to inspect the value type and determine whether it is empty - var empty = !enumerator.MoveNext(); - - int maxColumnIndex; - List props; - if (empty) - { - // only when empty IEnumerable need to check this issue #133 https://github.com/shps951023/MiniExcel/issues/133 - var genericType = TypeHelper.GetGenericIEnumerables(values).FirstOrDefault(); - if (genericType == null || genericType == typeof(object) // sometime generic type will be object, e.g: https://user-images.githubusercontent.com/12729184/132812859-52984314-44d1-4ee8-9487-2d1da159f1f0.png - || typeof(IDictionary).IsAssignableFrom(genericType) - || typeof(IDictionary).IsAssignableFrom(genericType)) - { - WriteEmptySheet(writer); - return; - } - else - { - SetGenericTypePropertiesMode(genericType, ref mode, out maxColumnIndex, out props); - } - } - else + var isKnownCount = writeAdapter.TryGetKnownCount(out var count); + var props = writeAdapter.GetColumns(); + if (props == null) { - var firstItem = enumerator.Current; - if (firstItem is IDictionary genericDic) - { - mode = "IDictionary"; - props = CustomPropertyHelper.GetDictionaryColumnInfo(genericDic, null, _configuration); - maxColumnIndex = props.Count; - } - else if (firstItem is IDictionary dic) - { - mode = "IDictionary"; - props = CustomPropertyHelper.GetDictionaryColumnInfo(null, dic, _configuration); - //maxColumnIndex = dic.Keys.Count; - maxColumnIndex = props.Count; // why not using keys, because ignore attribute ![image](https://user-images.githubusercontent.com/12729184/163686902-286abb70-877b-4e84-bd3b-001ad339a84a.png) - } - else - { - SetGenericTypePropertiesMode(firstItem.GetType(), ref mode, out maxColumnIndex, out props); - } + WriteEmptySheet(writer); + return; } + var maxColumnIndex = props.Count; + int maxRowIndex; writer.Write(WorksheetXml.StartWorksheetWithRelationship); long dimensionPlaceholderPostition = 0; // We can write the dimensions directly if the row count is known - if (_configuration.FastMode && rowCount == null) + if (_configuration.FastMode && !isKnownCount) { dimensionPlaceholderPostition = WriteDimensionPlaceholder(writer); } - else + else if (isKnownCount) { - maxRowIndex = rowCount.Value + (_printHeader && rowCount > 0 ? 1 : 0); - writer.Write(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, maxColumnIndex))); + maxRowIndex = count + (_printHeader ? 1 : 0); + writer.Write(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count))); } //sheet view @@ -379,34 +226,26 @@ private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable //header writer.Write(WorksheetXml.StartSheetData); - var yIndex = 1; - var xIndex = 1; + var currentRowIndex = 0; if (_printHeader) { PrintHeader(writer, props); - yIndex++; + currentRowIndex++; } - if (!empty) + foreach (var row in writeAdapter.GetRows(props)) { - // body - switch (mode) + writer.Write(WorksheetXml.StartRow(++currentRowIndex)); + foreach (var cellValue in row) { - case "IDictionary": //Dapper Row - maxRowIndex = GenerateSheetByColumnInfo>(writer, enumerator, props, widths, xIndex, yIndex); - break; - case "IDictionary": - maxRowIndex = GenerateSheetByColumnInfo(writer, enumerator, props, widths, xIndex, yIndex); - break; - case "Properties": - maxRowIndex = GenerateSheetByColumnInfo(writer, enumerator, props, widths, xIndex, yIndex); - break; - default: - throw new NotImplementedException($"Type {values.GetType().FullName} is not implemented. Please open an issue."); + WriteCell(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths); } + writer.Write(WorksheetXml.EndRow); } + maxRowIndex = currentRowIndex; writer.Write(WorksheetXml.EndSheetData); + if (_configuration.AutoFilter) { writer.Write(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); @@ -415,101 +254,16 @@ private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable writer.Write(WorksheetXml.Drawing(currentSheetIndex)); writer.Write(WorksheetXml.EndWorksheet); - // The dimension has already been written if row count is defined - if (_configuration.FastMode && rowCount == null) + if (_configuration.FastMode && dimensionPlaceholderPostition != default) { WriteDimension(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); } - if (_configuration.EnableAutoWidth) { OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths.Columns); } } - private void GenerateSheetByDataTable(MiniExcelStreamWriter writer, DataTable value) - { - var xy = ExcelOpenXmlUtils.ConvertCellToXY("A1"); - - //GOTO Top Write: - writer.Write(WorksheetXml.StartWorksheet); - - var yIndex = xy.Item2; - - // dimension - var maxRowIndex = value.Rows.Count + (_printHeader && value.Rows.Count > 0 ? 1 : 0); - var maxColumnIndex = value.Columns.Count; - writer.Write(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, maxColumnIndex))); - - var props = new List(); - for (var i = 0; i < value.Columns.Count; i++) - { - var columnName = value.Columns[i].Caption ?? value.Columns[i].ColumnName; - var prop = GetColumnInfosFromDynamicConfiguration(columnName); - props.Add(prop); - } - - //sheet view - writer.Write(GetSheetViews()); - - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = WriteColumnWidthPlaceholders(writer, props); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } - else - { - WriteColumnsWidths(writer, ExcelColumnWidth.FromProps(props)); - } - - writer.Write(WorksheetXml.StartSheetData); - if (_printHeader) - { - writer.Write(WorksheetXml.StartRow(yIndex)); - var xIndex = xy.Item1; - foreach (var p in props) - { - var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex); - WriteCell(writer, r, columnName: p.ExcelColumnName); - xIndex++; - } - - writer.Write(WorksheetXml.EndRow); - yIndex++; - } - - for (int i = 0; i < value.Rows.Count; i++) - { - writer.Write(WorksheetXml.StartRow(yIndex)); - var xIndex = xy.Item1; - - for (int j = 0; j < value.Columns.Count; j++) - { - var cellValue = value.Rows[i][j]; - WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: props?.FirstOrDefault(x => x?.ExcelColumnIndex == xIndex - 1), widths); - xIndex++; - } - writer.Write(WorksheetXml.EndRow); - yIndex++; - } - - writer.Write(WorksheetXml.EndSheetData); - - if (_configuration.AutoFilter) - { - writer.Write(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex))); - } - - if (_configuration.EnableAutoWidth) - { - OverWriteColumnWidthPlaceholders(writer, columnWidthsPlaceholderPosition, widths.Columns); - } - - writer.Write(WorksheetXml.EndWorksheet); - } - private long WriteColumnWidthPlaceholders(MiniExcelStreamWriter writer, ICollection props) { var placeholderPosition = writer.Flush(); @@ -569,52 +323,6 @@ private void PrintHeader(MiniExcelStreamWriter writer, List pro writer.Write(WorksheetXml.EndRow); } - private int GenerateSheetByColumnInfo(MiniExcelStreamWriter writer, IEnumerator value, List props, ExcelWidthCollection widthCollection, int xIndex = 1, int yIndex = 1) - { - var isDic = typeof(T) == typeof(IDictionary); - var isDapperRow = typeof(T) == typeof(IDictionary); - do - { - // The enumerator has already moved to the first item - T v = (T)value.Current; - - writer.Write(WorksheetXml.StartRow(yIndex)); - var cellIndex = xIndex; - foreach (var columnInfo in props) - { - if (columnInfo == null) //reason:https://github.com/shps951023/MiniExcel/issues/142 - { - cellIndex++; - continue; - } - object cellValue = null; - if (isDic) - { - cellValue = ((IDictionary)v)[columnInfo.Key]; - //WriteCell(writer, yIndex, cellIndex, cellValue, null); // why null because dictionary that needs to check type every time - //TODO: user can specefic type to optimize efficiency - } - else if (isDapperRow) - { - cellValue = ((IDictionary)v)[columnInfo.Key.ToString()]; - } - else - { - cellValue = columnInfo.Property.GetValue(v); - } - - WriteCell(writer, yIndex, cellIndex, cellValue, columnInfo, widthCollection); - - cellIndex++; - } - - writer.Write(WorksheetXml.EndRow); - yIndex++; - } while (value.MoveNext()); - - return yIndex - 1; - } - private void WriteCell(MiniExcelStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, ExcelWidthCollection widthCollection) { var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index fee4fdef..217f3e09 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -317,5 +317,98 @@ internal static void SetDictionaryColumnInfo(List _props, objec if (!isIgnore) _props.Add(p); } + + internal static bool TryGetTypeColumnInfo(Type type, Configuration configuration, out List props) + { + // Unknown type + if (type == null) + { + props = null; + return false; + } + + if (type.IsValueType) + { + throw new NotImplementedException($"MiniExcel not support only {type.Name} value generic type"); + } + + if (type == typeof(string) || type == typeof(DateTime) || type == typeof(Guid)) + { + throw new NotImplementedException($"MiniExcel not support only {type.Name} generic type"); + } + + if (ValueIsNeededToDetermineProperties(type)) + { + props = null; + return false; + } + + props = GetSaveAsProperties(type, configuration); + return true; + } + internal static List GetColumnInfoFromValue(object value, Configuration configuration) + { + switch (value) + { + case IDictionary genericDictionary: + return GetDictionaryColumnInfo(genericDictionary, null, configuration); + case IDictionary dictionary: + return GetDictionaryColumnInfo(null, dictionary, configuration); + default: + return GetSaveAsProperties(value.GetType(), configuration); + } + } + + private static bool ValueIsNeededToDetermineProperties(Type type) => type == typeof(object) + || typeof(IDictionary).IsAssignableFrom(type) + || typeof(IDictionary).IsAssignableFrom(type); + + internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName, Configuration configuration) + { + var prop = new ExcelColumnInfo + { + ExcelColumnName = columnName, + Key = columnName + }; + + if (configuration.DynamicColumns == null || configuration.DynamicColumns.Length <= 0) + return prop; + + var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(_ => string.Equals(_.Key, columnName, StringComparison.OrdinalIgnoreCase)); + if (dynamicColumn == null || dynamicColumn.Ignore) + { + return prop; + } + + prop.Nullable = true; + prop.ExcelIgnore = dynamicColumn.Ignore; + prop.ExcelColumnType = dynamicColumn.Type; + prop.ExcelColumnIndex = dynamicColumn.Index; + prop.ExcelColumnWidth = dynamicColumn.Width; + prop.CustomFormatter = dynamicColumn.CustomFormatter; + + if (dynamicColumn.Format != null) + { + prop.ExcelFormat = dynamicColumn.Format; + prop.ExcelFormatId = dynamicColumn.FormatId; + } + + if (dynamicColumn.Aliases != null) + { + prop.ExcelColumnAliases = dynamicColumn.Aliases; + } + + if (dynamicColumn.IndexName != null) + { + prop.ExcelIndexName = dynamicColumn.IndexName; + } + + if (dynamicColumn.Name != null) + { + prop.ExcelColumnName = dynamicColumn.Name; + } + + return prop; + } } } \ No newline at end of file diff --git a/src/MiniExcel/Utils/TypeHelper.cs b/src/MiniExcel/Utils/TypeHelper.cs index 186e40bb..b37294d0 100644 --- a/src/MiniExcel/Utils/TypeHelper.cs +++ b/src/MiniExcel/Utils/TypeHelper.cs @@ -161,5 +161,16 @@ public static bool IsNumericType(Type type, bool isNullableUnderlyingType = fals return newValue; } +#if NETSTANDARD2_0_OR_GREATER || NET + public static bool IsAsyncEnumerable(this Type type, out Type genericArgument) + { + var asyncEnumrableInterfaceType = type + .GetInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>)); + genericArgument = asyncEnumrableInterfaceType?.GetGenericArguments().FirstOrDefault(); + return genericArgument != null; + } +#endif + } } diff --git a/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs b/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs new file mode 100644 index 00000000..5c8811a7 --- /dev/null +++ b/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs @@ -0,0 +1,95 @@ +using MiniExcelLibs.Utils; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +#if NETSTANDARD2_0_OR_GREATER || NET +namespace MiniExcelLibs.WriteAdapter +{ + internal class AsyncEnumerableWriteAdapter : IAsyncMiniExcelWriteAdapter + { + private readonly IAsyncEnumerable _values; + private readonly Configuration _configuration; + private IAsyncEnumerator _enumerator; + private bool _empty; + + public AsyncEnumerableWriteAdapter(IAsyncEnumerable values, Configuration configuration) + { + _values = values; + _configuration = configuration; + } + + public async Task> GetColumnsAsync() + { + if (CustomPropertyHelper.TryGetTypeColumnInfo(typeof(T), _configuration, out var props)) + { + return props; + } + + _enumerator = _values.GetAsyncEnumerator(); + if (!await _enumerator.MoveNextAsync()) + { + _empty = true; + return null; + } + return CustomPropertyHelper.GetColumnInfoFromValue(_enumerator.Current, _configuration); + } + + public async IAsyncEnumerable> GetRowsAsync(List props, [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (_empty) + { + yield break; + } + + if (_enumerator is null) + { + _enumerator = _values.GetAsyncEnumerator(); + if (!await _enumerator.MoveNextAsync()) + { + yield break; + } + } + + do + { + cancellationToken.ThrowIfCancellationRequested(); + yield return GetRowValuesAsync(_enumerator.Current, props); + + } 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 + { + var column = 1; + foreach (var prop in props) + { + if (prop == null) + { + column++; + continue; + } + + switch (currentValue) + { + 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; + } + + column++; + } + } + } +} +#endif \ No newline at end of file diff --git a/src/MiniExcel/WriteAdapter/DataReaderWriteAdapter.cs b/src/MiniExcel/WriteAdapter/DataReaderWriteAdapter.cs new file mode 100644 index 00000000..3bf56271 --- /dev/null +++ b/src/MiniExcel/WriteAdapter/DataReaderWriteAdapter.cs @@ -0,0 +1,83 @@ +using MiniExcelLibs.Utils; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading; + +namespace MiniExcelLibs.WriteAdapter +{ + internal class DataReaderWriteAdapter : IMiniExcelWriteAdapter + { + private readonly IDataReader _reader; + private readonly Configuration _configuration; + + public DataReaderWriteAdapter(IDataReader reader, Configuration configuration) + { + _reader = reader; + _configuration = configuration; + } + + public bool TryGetKnownCount(out int count) + { + count = 0; + return false; + } + + public List GetColumns() + { + var props = new List(); + for (var i = 0; i < _reader.FieldCount; i++) + { + var columnName = _reader.GetName(i); + + if (!_configuration.DynamicColumnFirst) + { + var prop = CustomPropertyHelper.GetColumnInfosFromDynamicConfiguration(columnName, _configuration); + props.Add(prop); + continue; + } + + if (_configuration + .DynamicColumns + .Any(a => string.Equals( + a.Key, + columnName, + StringComparison.OrdinalIgnoreCase))) + + { + var prop = CustomPropertyHelper.GetColumnInfosFromDynamicConfiguration(columnName, _configuration); + props.Add(prop); + } + } + return props; + } + + public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) + { + while (_reader.Read()) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return GetRowValues(props); + } + } + + private IEnumerable GetRowValues(List props) + { + for (int i = 0, column = 1; i < _reader.FieldCount; i++, column++) + { + if (_configuration.DynamicColumnFirst) + { + var columnIndex = _reader.GetOrdinal(props[i].Key.ToString()); + yield return new CellWriteInfo(_reader.GetValue(columnIndex), column, props[i]); + } + else + { + yield return new CellWriteInfo(_reader.GetValue(i), column, props[i]); + } + } + } + } +} + + diff --git a/src/MiniExcel/WriteAdapter/DataTableWriteAdapter.cs b/src/MiniExcel/WriteAdapter/DataTableWriteAdapter.cs new file mode 100644 index 00000000..796c6546 --- /dev/null +++ b/src/MiniExcel/WriteAdapter/DataTableWriteAdapter.cs @@ -0,0 +1,56 @@ +using MiniExcelLibs.Utils; +using System.Collections.Generic; +using System.Data; +using System.Threading; + +namespace MiniExcelLibs.WriteAdapter +{ + internal class DataTableWriteAdapter : IMiniExcelWriteAdapter + { + private readonly DataTable _dataTable; + private readonly Configuration _configuration; + + public DataTableWriteAdapter(DataTable dataTable, Configuration configuration) + { + _dataTable = dataTable; + _configuration = configuration; + } + + public bool TryGetKnownCount(out int count) + { + count = _dataTable.Rows.Count; + return true; + } + + public List GetColumns() + { + var props = new List(); + for (var i = 0; i < _dataTable.Columns.Count; i++) + { + var columnName = _dataTable.Columns[i].Caption ?? _dataTable.Columns[i].ColumnName; + var prop = CustomPropertyHelper.GetColumnInfosFromDynamicConfiguration(columnName, _configuration); + props.Add(prop); + } + return props; + } + + public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) + { + for (int row = 0; row < _dataTable.Rows.Count; row++) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return GetRowValues(row, props); + } + } + + private IEnumerable GetRowValues(int row, List props) + { + for (int i = 0, column = 1; i < _dataTable.Columns.Count; i++, column++) + { + yield return new CellWriteInfo(_dataTable.Rows[row][i], column, props[i]); + } + } + } +} + + diff --git a/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs b/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs new file mode 100644 index 00000000..489d1ee2 --- /dev/null +++ b/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs @@ -0,0 +1,105 @@ +using MiniExcelLibs.Utils; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace MiniExcelLibs.WriteAdapter +{ + internal class EnumerableWriteAdapter : IMiniExcelWriteAdapter + { + private readonly IEnumerable _values; + private readonly Configuration _configuration; + private readonly Type _genericType; + + private IEnumerator _enumerator; + private bool _empty; + + public EnumerableWriteAdapter(IEnumerable values, Configuration configuration) + { + _values = values; + _configuration = configuration; + _genericType = TypeHelper.GetGenericIEnumerables(values).FirstOrDefault(); + } + + public bool TryGetKnownCount(out int count) + { + count = 0; + if (_values is ICollection collection) + { + count = collection.Count; + return true; + } + + return false; + } + + public List GetColumns() + { + if (CustomPropertyHelper.TryGetTypeColumnInfo(_genericType, _configuration, out var props)) + { + return props; + } + + _enumerator = _values.GetEnumerator(); + if (!_enumerator.MoveNext()) + { + _empty = true; + return null; + } + return CustomPropertyHelper.GetColumnInfoFromValue(_enumerator.Current, _configuration); + } + + public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) + { + if (_empty) + { + yield break; + } + + if (_enumerator is null) + { + _enumerator = _values.GetEnumerator(); + if (!_enumerator.MoveNext()) + { + yield break; + } + } + + do + { + cancellationToken.ThrowIfCancellationRequested(); + yield return GetRowValues(_enumerator.Current, props); + } while (_enumerator.MoveNext()); + } + + + public static IEnumerable GetRowValues(object currentValue, List props) + { + var column = 1; + foreach (var prop in props) + { + object cellValue; + if (prop == null) + { + cellValue = null; + } + else if (currentValue is IDictionary genericDictionary) + { + cellValue = genericDictionary[prop.Key.ToString()]; + } + else if (currentValue is IDictionary dictionary) + { + cellValue = dictionary[prop.Key]; + } + else + { + cellValue = prop.Property.GetValue(currentValue); + } + yield return new CellWriteInfo(cellValue, column, prop); + column++; + } + } + } +} diff --git a/src/MiniExcel/WriteAdapter/IAsyncMiniExcelWriteAdapter.cs b/src/MiniExcel/WriteAdapter/IAsyncMiniExcelWriteAdapter.cs new file mode 100644 index 00000000..ce417bf1 --- /dev/null +++ b/src/MiniExcel/WriteAdapter/IAsyncMiniExcelWriteAdapter.cs @@ -0,0 +1,16 @@ +using MiniExcelLibs.Utils; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +#if NETSTANDARD2_0_OR_GREATER || NET +namespace MiniExcelLibs.WriteAdapter +{ + internal interface IAsyncMiniExcelWriteAdapter + { + Task> GetColumnsAsync(); + + IAsyncEnumerable> GetRowsAsync(List props, CancellationToken cancellationToken); + } +} +#endif diff --git a/src/MiniExcel/WriteAdapter/IMiniExcelWriteAdapter.cs b/src/MiniExcel/WriteAdapter/IMiniExcelWriteAdapter.cs new file mode 100644 index 00000000..adee88d2 --- /dev/null +++ b/src/MiniExcel/WriteAdapter/IMiniExcelWriteAdapter.cs @@ -0,0 +1,31 @@ +using MiniExcelLibs.Utils; +using System.Collections.Generic; +using System.Threading; + +namespace MiniExcelLibs.WriteAdapter +{ + internal interface IMiniExcelWriteAdapter + { + bool TryGetKnownCount(out int count); + + List GetColumns(); + + IEnumerable> GetRows(List props, CancellationToken cancellationToken = default); + } + + internal readonly struct CellWriteInfo + { + public CellWriteInfo(object value, int cellIndex, ExcelColumnInfo prop) + { + Value = value; + CellIndex = cellIndex; + Prop = prop; + } + + public object Value { get; } + public int CellIndex { get; } + public ExcelColumnInfo Prop { get; } + } +} + + diff --git a/src/MiniExcel/WriteAdapter/MiniExcelDataReaderWriteAdapter.cs b/src/MiniExcel/WriteAdapter/MiniExcelDataReaderWriteAdapter.cs new file mode 100644 index 00000000..8e800f63 --- /dev/null +++ b/src/MiniExcel/WriteAdapter/MiniExcelDataReaderWriteAdapter.cs @@ -0,0 +1,78 @@ +using MiniExcelLibs.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +#if NETSTANDARD2_0_OR_GREATER || NET +namespace MiniExcelLibs.WriteAdapter +{ + internal class MiniExcelDataReaderWriteAdapter : IAsyncMiniExcelWriteAdapter + { + private readonly IMiniExcelDataReader _reader; + private readonly Configuration _configuration; + + public MiniExcelDataReaderWriteAdapter(IMiniExcelDataReader reader, Configuration configuration) + { + _reader = reader; + _configuration = configuration; + } + + public async Task> GetColumnsAsync() + { + var props = new List(); + for (var i = 0; i < _reader.FieldCount; i++) + { + var columnName = await _reader.GetNameAsync(i); + + if (!_configuration.DynamicColumnFirst) + { + var prop = CustomPropertyHelper.GetColumnInfosFromDynamicConfiguration(columnName, _configuration); + props.Add(prop); + continue; + } + + if (_configuration + .DynamicColumns + .Any(a => string.Equals( + a.Key, + columnName, + StringComparison.OrdinalIgnoreCase))) + + { + var prop = CustomPropertyHelper.GetColumnInfosFromDynamicConfiguration(columnName, _configuration); + props.Add(prop); + } + } + return props; + } + + public async IAsyncEnumerable> GetRowsAsync(List props, [EnumeratorCancellation] CancellationToken cancellationToken) + { + while (await _reader.ReadAsync()) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return GetRowValuesAsync(props); + } + } + + private async IAsyncEnumerable GetRowValuesAsync(List props) + { + for (int i = 0, column = 1; i < _reader.FieldCount; i++, column++) + { + if (_configuration.DynamicColumnFirst) + { + var columnIndex = _reader.GetOrdinal(props[i].Key.ToString()); + yield return new CellWriteInfo(await _reader.GetValueAsync(columnIndex), column, props[i]); + } + else + { + yield return new CellWriteInfo(await _reader.GetValueAsync(i), column, props[i]); + } + } + } + } +} +#endif diff --git a/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs b/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs new file mode 100644 index 00000000..7f6eb97b --- /dev/null +++ b/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs @@ -0,0 +1,45 @@ +using MiniExcelLibs.Utils; +using System; +using System.Collections; +using System.Data; + +namespace MiniExcelLibs.WriteAdapter +{ + internal static class MiniExcelWriteAdapterFactory + { +#if NETSTANDARD2_0_OR_GREATER || NET + public static bool TryGetAsyncWriteAdapter(object values, Configuration configuration, out IAsyncMiniExcelWriteAdapter writeAdapter) + { + writeAdapter = null; + if (values.GetType().IsAsyncEnumerable(out var genericArgument)) + { + var writeAdapterType = typeof(AsyncEnumerableWriteAdapter<>).MakeGenericType(genericArgument); + writeAdapter = (IAsyncMiniExcelWriteAdapter)Activator.CreateInstance(writeAdapterType, values, configuration); + return true; + } + if (values is IMiniExcelDataReader miniExcelDataReader) + { + writeAdapter = new MiniExcelDataReaderWriteAdapter(miniExcelDataReader, configuration); + return true; + } + + return false; + } +#endif + + public static IMiniExcelWriteAdapter GetWriteAdapter(object values, Configuration configuration) + { + switch (values) + { + case IDataReader dataReader: + return new DataReaderWriteAdapter(dataReader, configuration); + case IEnumerable enumerable: + return new EnumerableWriteAdapter(enumerable, configuration); + case DataTable dataTable: + return new DataTableWriteAdapter(dataTable, configuration); + default: + throw new NotImplementedException(); + } + } + } +} diff --git a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs b/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs index d6913d95..2114c11f 100644 --- a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs +++ b/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs @@ -59,9 +59,7 @@ public async Task SaveAsByDictionary() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); var table = new Dictionary(); //TODO - await MiniExcel.SaveAsAsync(path, table); - //Assert.Throws(() => MiniExcel.SaveAs(path, table)); - Assert.Equal("\r\n", File.ReadAllText(path)); + Assert.Throws(() => MiniExcel.SaveAs(path, table)); File.Delete(path); } @@ -319,5 +317,32 @@ await MiniExcel.SaveAsAsync(path, new[] { File.Delete(path); } + + [Fact] + public async Task SaveAsByAsyncEnumerable() + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + static async IAsyncEnumerable GetValues() + { + yield return new Test { c1 = "A1", c2 = "B1" }; + yield return new Test { c1 = "A2", c2 = "B2" }; + } +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + + await MiniExcel.SaveAsAsync(path, GetValues()); + + var results = MiniExcel.Query(path); + + Assert.True(results.Count() == 2); + Assert.True(results.First().c1 == "A1"); + Assert.True(results.First().c2 == "B1"); + Assert.True(results.Last().c1 == "A2"); + Assert.True(results.Last().c2 == "B2"); + + File.Delete(path); + } } } \ No newline at end of file diff --git a/tests/MiniExcelTests/MiniExcelCsvTests.cs b/tests/MiniExcelTests/MiniExcelCsvTests.cs index d17959ff..119efdb0 100644 --- a/tests/MiniExcelTests/MiniExcelCsvTests.cs +++ b/tests/MiniExcelTests/MiniExcelCsvTests.cs @@ -89,9 +89,7 @@ public void SaveAsByDictionary() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv"); var table = new Dictionary(); //TODO - MiniExcel.SaveAs(path, table); - //Assert.Throws(() => MiniExcel.SaveAs(path, table)); - Assert.Equal("\r\n", File.ReadAllText(path)); + Assert.Throws(() => MiniExcel.SaveAs(path, table)); File.Delete(path); } diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs index 815006e4..b4de8f3f 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs @@ -635,12 +635,10 @@ public async Task SaveAsFileWithDimension() } File.Delete(path); } - await MiniExcel.SaveAsAsync(path, table, printHeader: false); Assert.Equal("A1", Helpers.GetFirstSheetDimensionRefValue(path)); File.Delete(path); } - { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); var table = new DataTable(); @@ -1695,5 +1693,32 @@ public async Task InsertCsvTest() ", content); } } + + [Fact] + public async Task SaveAsByAsyncEnumerable() + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + static async IAsyncEnumerable GetValues() + { + yield return new Demo { Column1 = "MiniExcel", Column2 = 1 }; + yield return new Demo { Column1 = "Github", Column2 = 2 }; + } +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + + await MiniExcel.SaveAsAsync(path, GetValues()); + + var results = MiniExcel.Query(path); + + Assert.True(results.Count() == 2); + Assert.True(results.First().Column1 == "MiniExcel"); + Assert.True(results.First().Column2 == 1); + Assert.True(results.Last().Column1 == "Github"); + Assert.True(results.Last().Column2 == 2); + + File.Delete(path); + } } } \ No newline at end of file