From 786e2a82bd818fac1124b512231779fab4264c6a Mon Sep 17 00:00:00 2001 From: wei Date: Thu, 18 Mar 2021 11:41:22 +0800 Subject: [PATCH] 0.2.2 - SavaAs support xl/sheet dimension - [Breaking Changes] SaveAs value type from object to DataTable & ICollection - Bug fix: ICollection with type but no data error (https://github.com/shps951023/MiniExcel/issues/105) --- docs/README.md | 5 + ...ection】Get ICollection Generic Type.linq | 108 ++++++++++++++++++ src/MiniExcel/MiniExcel.cs | 71 +++++++----- src/MiniExcel/MiniExcelLibs.csproj | 4 +- src/MiniExcel/Utils/Helpers.cs | 19 ++- tests/MiniExcelTests/MiniExcelHelperTests.cs | 43 +++++++ 6 files changed, 220 insertions(+), 30 deletions(-) create mode 100644 drafts/【Reflection】Get ICollection Generic Type.linq diff --git a/docs/README.md b/docs/README.md index 0b4692c..6edd6c9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,11 @@ ## Release Notes +### 0.2.2 +- SavaAs support xl/sheet dimension +- [Breaking Changes] SaveAs value type from object to DataTable & ICollection +- Bug fix: ICollection with type but no data error (https://github.com/shps951023/MiniExcel/issues/105) + ### 0.2.1 - Optimize type mapping bool and datetime auto check - Query Support xl/worksheets/Sheet Xml Xml `` without `r` attribute or without `` but `` with `r` attribute, but now performance is slow than with dimension ([](https://github.com/shps951023/MiniExcel/issues/2)) diff --git a/drafts/【Reflection】Get ICollection Generic Type.linq b/drafts/【Reflection】Get ICollection Generic Type.linq new file mode 100644 index 0000000..f4654c6 --- /dev/null +++ b/drafts/【Reflection】Get ICollection Generic Type.linq @@ -0,0 +1,108 @@ + + + 5fffb9dc-a56f-4ffa-a582-f9da6bc9fdad + true + 192.168.1.4 + true + sa + AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAyumoRWrbXEqda8ynsoawYAAAAAACAAAAAAAQZgAAAAEAACAAAACumZoBhp4lj0R4mTg98suX0pykwksNIARbRIh49xu5/QAAAAAOgAAAAAIAACAAAADoNABocqodkXYmDtdW0GqBvGuMfAJeL++I3kdCYqM4rxAAAADANn2PCQ6OByhczsa8iMQPQAAAAKx4dlXxPcHN4uDHZRcYbnhkQZ52tjk6YEm+q+GruBVhVPrtz22hjCT4VMaK2N6EtZF2Rfr2P8fUTQH/ZPns5GA= + kn2015 + + Dapper + MiniExcel + System.Data.SqlClient + Xunit + System.Data + System.Diagnostics + System.IO + System.Linq.Expressions + System.Text + System.Text.RegularExpressions + System.Threading + System.Transactions + System.Xml + System.Xml.Linq + System.Xml.XPath + + +//[c# Reflection - Find the Generic Type of a Collection - Stack Overflow](https://stackoverflow.com/questions/2561070/c-sharp-reflection-find-the-generic-type-of-a-collection) +#load "xunit" + +#region private::Tests + +[Fact] +void ValueGenericTypeTest() +{ + var strings = new List(); + var props = Helpers.GetSubtypeGetProperties(strings); + Assert.Equal(0,props.Length); +} + +public class TestType +{ + public string A { get; set; } + public string B { get; set; } +} + +[Fact()] +void IListUpcastingTest() +{ + IList datas = new List(); + var props = Helpers.GetSubtypeGetProperties(datas).ToList(); + Assert.Equal(2, props.Count()); + Assert.Equal("A", props[0].Name); + Assert.Equal("B", props[1].Name); +} + + +[Fact()] +void ArrayTest() +{ + ICollection datas = new[] { new { A = "1", B = "2" } }; + var props = Helpers.GetSubtypeGetProperties(datas); + Assert.Equal(2, props.Count()); +} + +[Fact()] +void OnlyValidOGenericTypes_Test() +{ + ICollection datas = new[] { new { A = "1", B = "2" } }; + var df = datas.GetType().GetGenericTypeDefinition(); //InvalidOperationException: This operation is only valid on generic types. +} + +[Fact()] +void DictionaryTest() +{ + ICollection datas = new[] { new Dictionary() { { "A", "A" }, { "B", "B" } } }; + var props = Helpers.GetSubtypeGetProperties(datas); +} +#endregion + +internal static class Helpers +{ + public static PropertyInfo[] GetSubtypeGetProperties(ICollection value) + { + var collectionType = value.GetType(); + + Type gType; + if (collectionType.IsGenericTypeDefinition || collectionType.IsGenericType) + gType = collectionType.GetGenericArguments().Single(); + else if (collectionType.IsArray) + gType = collectionType.GetElementType(); + else + throw new NotImplementedException($"{collectionType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues"); + if (typeof(IDictionary).IsAssignableFrom(gType)) + throw new NotImplementedException($"{gType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues"); + var props = gType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + return props; + } +} + + +void Main() +{ + //RunTests(); // Call RunTests() or press Alt+Shift+T to initiate testing. +} + +// You can define other methods, fields, classes and namespaces here + diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index c54a363..1e8d3c5 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -12,6 +12,7 @@ using System.Reflection; using MiniExcelLibs.Utils; using System.Globalization; + using System.Collections; public static partial class MiniExcel { @@ -26,13 +27,24 @@ private readonly static UTF8Encoding Utf8WithBom = new System.Text.UTF8Encoding(true); - public static void SaveAs(this Stream stream,object value, string startCell = "A1", bool printHeader = true) + public static void SaveAs(this Stream stream, DataTable value, string startCell = "A1", bool printHeader = true) { - SaveAsImpl(stream,GetCreateXlsxInfos(value, startCell, printHeader)); + SaveAsImpl(stream, GetCreateXlsxInfos(value, startCell, printHeader)); stream.Position = 0; } - public static void SaveAs(string filePath, object value, string startCell = "A1", bool printHeader = true) + public static void SaveAs(this Stream stream, ICollection value, string startCell = "A1", bool printHeader = true) + { + SaveAsImpl(stream, GetCreateXlsxInfos(value, startCell, printHeader)); + stream.Position = 0; + } + + public static void SaveAs(string filePath, DataTable value, string startCell = "A1", bool printHeader = true) + { + SaveAsImpl(filePath, GetCreateXlsxInfos(value, startCell, printHeader)); + } + + public static void SaveAs(string filePath, ICollection value, string startCell = "A1", bool printHeader = true) { SaveAsImpl(filePath, GetCreateXlsxInfos(value, startCell, printHeader)); } @@ -42,7 +54,12 @@ var xy = ExcelOpenXmlUtils.ConvertCellToXY(startCell); var defaultFiles = GetDefaultFiles(); - var dimensionRef = string.Empty; + + // dimension + var dimensionRef = "A1"; + var maxRowIndex = 0; + var maxColumnIndex = 0; + { var sb = new StringBuilder(); @@ -52,17 +69,10 @@ { var dt = value as DataTable; - var maxRowIndex = dt.Rows.Count; - var maxColumnIndex = dt.Columns.Count; // dimension - { - if (maxRowIndex == 0 && maxColumnIndex == 0) - dimensionRef = "A1"; - else if ( maxColumnIndex == 1) - dimensionRef = $"A{maxRowIndex}"; - else - dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex-1)}{maxRowIndex}"; - } + maxRowIndex = dt.Rows.Count; + maxColumnIndex = dt.Columns.Count; + if (printHeader) { @@ -119,16 +129,12 @@ else if (value is System.Collections.ICollection) { var collection = value as System.Collections.ICollection; - object firstValue = null; - { - foreach (var v in collection) - { - firstValue = v; - break; - } - } - var type = firstValue.GetType(); - var props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); + + var props = Helpers.GetSubtypeProperties(collection); + maxColumnIndex = props.Length; + if (props.Length == 0) + throw new InvalidOperationException($"Properties count is 0"); + if (printHeader) { sb.AppendLine($""); @@ -179,6 +185,17 @@ sb.AppendLine($""); yIndex++; } + maxRowIndex = yIndex-1; + } + + // dimension + { + if (maxRowIndex == 0 && maxColumnIndex == 0) + dimensionRef = "A1"; + else if (maxColumnIndex == 1) + dimensionRef = $"A{maxRowIndex}"; + else + dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}"; } defaultFiles[@"xl/worksheets/sheet1.xml"].Xml = $@" @@ -191,7 +208,7 @@ return defaultFiles; } - public static IEnumerable Query(this Stream stream) where T : class , new() + public static IEnumerable Query(this Stream stream) where T : class, new() { return QueryImpl(stream); } @@ -294,10 +311,10 @@ private static void SaveAsImpl(string path, Dictionary zipPackageInfos) { using (FileStream stream = new FileStream(path, FileMode.CreateNew)) - using(ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, false, Utf8WithBom)) + using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, false, Utf8WithBom)) CreteXlsxImpl(zipPackageInfos, archive); } - private static void SaveAsImpl(Stream stream,Dictionary zipPackageInfos) + private static void SaveAsImpl(Stream stream, Dictionary zipPackageInfos) { using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true, Utf8WithBom)) { diff --git a/src/MiniExcel/MiniExcelLibs.csproj b/src/MiniExcel/MiniExcelLibs.csproj index 6d59379..ee63312 100644 --- a/src/MiniExcel/MiniExcelLibs.csproj +++ b/src/MiniExcel/MiniExcelLibs.csproj @@ -5,7 +5,7 @@ MiniExcel excel;xlsx;micro-helper;mini;openxml;helper; - A high performance Excel Xlsx Micro-Helper without any third party library and supporting create and dynamic/type mapping query etc.. + A high performance Excel Xlsx Micro-Helper without any third party library to create and dynamic/type mapping query etc.. Github : https://github.com/shps951023/MiniExcel Issues : https://github.com/shps951023/MiniExcel/issues @@ -19,7 +19,7 @@ https://github.com/shps951023/MiniExcel https://raw.githubusercontent.com/shps951023/ImageHosting/master/img/2019-01-17.13.18.32-image.png net461;netstandard2.0;net5.0 - 0.2.1 + 0.2.2 Please Check [Release Notes](https://github.com/shps951023/MiniExcel/tree/master/docs) Github diff --git a/src/MiniExcel/Utils/Helpers.cs b/src/MiniExcel/Utils/Helpers.cs index afbda8f..a99ba2d 100644 --- a/src/MiniExcel/Utils/Helpers.cs +++ b/src/MiniExcel/Utils/Helpers.cs @@ -4,6 +4,7 @@ namespace MiniExcelLibs.Utils { using System; + using System.Collections; using System.Collections.Generic; using System.Dynamic; using System.Globalization; @@ -64,13 +65,29 @@ namespace MiniExcelLibs.Utils return cell; } - public static IEnumerable GetPropertiesWithSetter(Type type) + public static IEnumerable GetPropertiesWithSetter(this Type type) { return type.GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance).Where(prop => prop.GetSetMethod() != null); } + public static PropertyInfo[] GetSubtypeProperties(ICollection value) + { + var collectionType = value.GetType(); + + Type gType; + if (collectionType.IsGenericTypeDefinition || collectionType.IsGenericType) + gType = collectionType.GetGenericArguments().Single(); + else if (collectionType.IsArray) + gType = collectionType.GetElementType(); + else + throw new NotImplementedException($"{collectionType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues"); + if (typeof(IDictionary).IsAssignableFrom(gType)) + throw new NotImplementedException($"{gType.Name} type not implemented,please issue for me, https://github.com/shps951023/MiniExcel/issues"); + var props = gType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + return props; + } public static string ConvertEscapeChars(string input) { diff --git a/tests/MiniExcelTests/MiniExcelHelperTests.cs b/tests/MiniExcelTests/MiniExcelHelperTests.cs index 3273a3e..a063305 100644 --- a/tests/MiniExcelTests/MiniExcelHelperTests.cs +++ b/tests/MiniExcelTests/MiniExcelHelperTests.cs @@ -270,6 +270,48 @@ namespace MiniExcelLibs.Tests } } + public class SaveAsFileWithDimensionByICollectionTestType + { + public string A { get; set; } + public string B { get; set; } + } + [Fact()] + public void SaveAsFileWithDimensionByICollection() + { + //List + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var values = new List() + { + new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, + new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, + }; + MiniExcel.SaveAs(path, values); + Assert.Equal("A1:B3", GetFirstSheetDimensionRefValue(path)); + File.Delete(path); + } + + //Array + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var values = new [] + { + new {A="A",B="B"}, + new {A="A",B="B"}, + }; + MiniExcel.SaveAs(path, values); + Assert.Equal("A1:B3", GetFirstSheetDimensionRefValue(path)); + File.Delete(path); + } + + // without properties + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var values = new List(); + Assert.Throws(() => MiniExcel.SaveAs(path, values)); + } + } + [Fact()] public void SaveAsFileWithDimension() { @@ -368,6 +410,7 @@ namespace MiniExcelLibs.Tests { var now = DateTime.Now; var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var table = new DataTable(); { table.Columns.Add("a", typeof(string));