فهرست منبع

Starting with it

Piotr Czajkowski 1 سال پیش
والد
کامیت
f8f5203ec7

+ 1 - 0
.gitignore

@@ -396,3 +396,4 @@ FodyWeavers.xsd
 
 # JetBrains Rider
 *.sln.iml
+ExcelORM/.idea/

+ 16 - 0
ExcelORM/ExcelORM.sln

@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelORM", "ExcelORM\ExcelORM.csproj", "{D6FA68D9-2FBA-4C04-9103-EFCFF2C27F73}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{D6FA68D9-2FBA-4C04-9103-EFCFF2C27F73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D6FA68D9-2FBA-4C04-9103-EFCFF2C27F73}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D6FA68D9-2FBA-4C04-9103-EFCFF2C27F73}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D6FA68D9-2FBA-4C04-9103-EFCFF2C27F73}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal

+ 18 - 0
ExcelORM/ExcelORM/Attributes/ColumnAttribute.cs

@@ -0,0 +1,18 @@
+namespace ExcelORM
+{
+    [AttributeUsage(AttributeTargets.Property)]
+    public class ColumnAttribute : Attribute
+    {
+        public string[]? Names { get; init; }
+
+        public ColumnAttribute(string name)
+        {
+            Names = new string[] { name };
+        }
+
+        public ColumnAttribute(string[] names)
+        {
+            Names = names;
+        }
+    }
+}

+ 13 - 0
ExcelORM/ExcelORM/ExcelORM.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net7.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <PackageReference Include="ClosedXML" Version="0.102.1" />
+    </ItemGroup>
+
+</Project>

+ 85 - 0
ExcelORM/ExcelORM/ExcelReader.cs

@@ -0,0 +1,85 @@
+using ClosedXML.Excel;
+
+namespace ExcelORM;
+
+public class ExcelReader
+{
+    private readonly IXLWorkbook xlWorkbook;
+    public uint SkipFirstNRows { get; set; } = 1;
+    public bool SkipHidden { get; set; }
+    public bool ObeyFilter { get; set; }
+
+    public ExcelReader(string? path)
+    {
+        xlWorkbook = new XLWorkbook(path);
+    }
+
+    private IEnumerable<T> ProcessRows<T>(IEnumerable<IXLRow> rows, List<Mapping> mapping) where T : class, new()
+    {
+        foreach (var row in rows)
+        {
+            if (SkipHidden && row.IsHidden) continue;
+            if (row.RowNumber() <= SkipFirstNRows) continue;
+
+            var current = new T();
+            foreach (var item in mapping)
+            {
+                if (item.Position == null || item.PropertyName == null) continue;
+
+                var cell = row.Cell(item.Position.Value);
+                if (cell == null || cell.Value.IsBlank) continue;
+
+                var property = current.GetType().GetProperty(item.PropertyName);
+                if (property == null) continue;
+
+                switch (property.PropertyType)
+                {
+                    case Type _ when property.PropertyType == typeof(string):
+                        property.SetValue(current, cell.Value.ToString());
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            yield return current;
+        }
+    }
+
+    public IEnumerable<T> Read<T>() where T : class, new()
+    {
+        foreach (var worksheet in xlWorkbook.Worksheets)
+        {
+            foreach (var value in Read<T>(worksheet))
+                yield return value;
+        }
+    }
+
+    public IEnumerable<T> Read<T>(string? worksheetName) where T : class, new()
+    {
+        var worksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Name.Equals(worksheetName, StringComparison.InvariantCultureIgnoreCase));
+        if (worksheet == null) yield break;
+
+        foreach (var value in Read<T>(worksheet))
+            yield return value;
+    }
+
+    public IEnumerable<T> Read<T>(IXLWorksheet? worksheet) where T : class, new()
+    {
+        if (worksheet == null) yield break;
+
+        var mapping = Mapping.MapProperties<T>(worksheet.FirstRowUsed().CellsUsed());
+        if (mapping == null) yield break;
+
+        if (ObeyFilter && worksheet.AutoFilter.IsEnabled)
+        {
+            foreach (var item in ProcessRows<T>(worksheet.AutoFilter.VisibleRows.Select(x => x.WorksheetRow()), mapping))
+                yield return item;
+        }
+        else
+        {
+            foreach (var item in ProcessRows<T>(worksheet.RowsUsed(), mapping))
+                yield return item;
+        }
+    } 
+}

+ 70 - 0
ExcelORM/ExcelORM/ExcelWriter.cs

@@ -0,0 +1,70 @@
+using ClosedXML.Excel;
+
+namespace ExcelORM;
+
+public class ExcelWriter
+{
+    private readonly IXLWorkbook xlWorkbook;
+    public bool WriteHeader { get; set; } = true;
+
+    public ExcelWriter(string? path = null)
+    {
+        if (File.Exists(path))
+            xlWorkbook = new XLWorkbook(path);
+        else
+            xlWorkbook = new XLWorkbook();
+    }
+
+    private uint GenerateHeader<T>(T value, IXLWorksheet worksheet, uint rowIndex = 1) where T : class, new()
+    {
+        var cellIndex = 1;
+        var properties = value.GetType().GetProperties();
+        foreach (var property in properties)
+        {
+            var columnAttribute = property.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() as ColumnAttribute;
+            worksheet.Cell((int)rowIndex, cellIndex).Value = columnAttribute is { Names.Length: > 0 } ? columnAttribute.Names.First() : property.Name;
+            cellIndex++;
+        }
+
+        return ++rowIndex;
+    }
+
+    public void Write<T>(IEnumerable<T> values, string? worksheetName, bool append = false, uint rowIndex = 1) where T : class, new()
+    {
+        var xlWorksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Name.Equals(worksheetName, StringComparison.InvariantCultureIgnoreCase));
+        xlWorksheet ??= !string.IsNullOrWhiteSpace(worksheetName) ?
+            xlWorkbook.AddWorksheet(worksheetName) : xlWorkbook.AddWorksheet();
+
+        Write(values, xlWorksheet, append, rowIndex);
+    }
+
+    public void Write<T>(IEnumerable<T> values, IXLWorksheet worksheet, bool append = false, uint rowIndex = 1) where T : class, new()
+    {
+        if (!values.Any()) return;
+
+        if (append)
+            rowIndex = (uint)worksheet.LastRowUsed().RowNumber() + 1;
+
+        if (!append && WriteHeader)
+            rowIndex = GenerateHeader(values.First(), worksheet);
+
+        foreach (var value in values)
+        {
+            var cellIndex = 1;
+            var properties = value.GetType().GetProperties();
+            foreach (var property in properties)
+            {
+                worksheet.Cell((int)rowIndex, cellIndex).Value = property.GetValue(value) as string;
+                cellIndex++;
+            }
+
+            rowIndex++;
+        }
+    }
+
+    public void SaveAs(string path, IExcelConverter? converter = null)
+    {
+        xlWorkbook.SaveAs(path);
+        converter?.MakeCompatible(path);
+    } 
+}

+ 6 - 0
ExcelORM/ExcelORM/IExcelConverter.cs

@@ -0,0 +1,6 @@
+namespace ExcelORM;
+
+public interface IExcelConverter
+{
+    void MakeCompatible(string path);
+}

+ 34 - 0
ExcelORM/ExcelORM/Mapping.cs

@@ -0,0 +1,34 @@
+using ClosedXML.Excel;
+
+namespace ExcelORM
+{
+    public class Mapping
+    {
+        public string? PropertyName { get; set; }
+        public Type? PropertyType { get; set; }
+        public int? Position { get; set; }
+
+        public static List<Mapping>? MapProperties<T>(IXLCells? headerCells) where T : new()
+        {
+            if (headerCells == null || !headerCells.Any()) return null;
+
+            var objectToRead = new T();
+            var map = new List<Mapping>();
+            var properties = objectToRead.GetType().GetProperties();
+            foreach (var property in properties)
+            {
+                int? position = null;
+                var columnAttribute = property.GetCustomAttributes(typeof(ColumnAttribute), false).FirstOrDefault() as ColumnAttribute;
+                position = columnAttribute is { Names.Length: > 0 } ? headerCells.FirstOrDefault(x => !x.Value.IsBlank && Array.Exists(columnAttribute.Names, y => y.Equals(x.Value.ToString(), StringComparison.InvariantCultureIgnoreCase)))?.Address.ColumnNumber : headerCells.FirstOrDefault(x => !x.Value.IsBlank && x.Value.ToString().Equals(property.Name, StringComparison.InvariantCultureIgnoreCase))?.Address.ColumnNumber;
+
+                if (position == null) continue;
+                map.Add(new Mapping { PropertyName = property.Name, PropertyType = property.PropertyType, Position = position });
+            }
+
+            if (map.Count == properties.Length)
+                return map;
+
+            return null;
+        }
+    }
+}