21 Commits 553f4b1b2a ... a5121bed88

Auteur SHA1 Message Date
  Piotr Czajkowski a5121bed88 More checks il y a 3 semaines
  Piotr Czajkowski 16928bb02a Cleanup il y a 3 semaines
  Piotr Czajkowski 772fa48c0d Don't throw on empty il y a 3 semaines
  Piotr Czajkowski e3138544a6 Let's be safe il y a 3 semaines
  Piotr Czajkowski 509116a88e Modified notes il y a 3 semaines
  Piotr Czajkowski c4b38364d4 Updated dependencies il y a 3 semaines
  Piotr Czajkowski ce6873718a Let's be safe il y a 4 mois
  Piotr Czajkowski 14a0f417d6 Clearer il y a 4 mois
  Piotr Czajkowski 9db4a5b7b7 Redundant il y a 4 mois
  Piotr Czajkowski 4620baefea Cosmetics il y a 4 mois
  Piotr Czajkowski 88d36c7967 Simpler il y a 4 mois
  Piotr Czajkowski bb2a474c83 Additional checks il y a 9 mois
  Piotr Czajkowski ecc3e8591d More consise il y a 10 mois
  Piotr Czajkowski fd3def7108 Cleaner il y a 10 mois
  Piotr Czajkowski 685384cbaa Let's keep everything together il y a 10 mois
  Piotr Czajkowski f44327a440 Cosmetics il y a 10 mois
  Piotr Czajkowski fb0d9f638b It needs a version bump il y a 10 mois
  Piotr Czajkowski 2964cedb09 Info about 2.9.0 il y a 10 mois
  Piotr Czajkowski 021af2943b Added ToObject_EnumAsString il y a 10 mois
  Piotr Czajkowski 3360e5e219 Added ToObject_GuidAsString il y a 10 mois
  Piotr Czajkowski 19965eb4b1 A bit of order il y a 10 mois

+ 5 - 16
ExcelORM/ExcelORM/ExcelDynamicReader.cs

@@ -3,7 +3,7 @@ using ExcelORM.Models;
 
 namespace ExcelORM;
 
-public class ExcelDynamicReader : IDisposable
+public sealed class ExcelDynamicReader : IDisposable
 {
     private readonly IXLWorkbook xlWorkbook;
     public bool SkipHidden { get; set; }
@@ -14,7 +14,7 @@ public class ExcelDynamicReader : IDisposable
         xlWorkbook = new XLWorkbook(path);
     }
 
-    public ExcelDynamicReader(IXLWorkbook workbook)
+    public ExcelDynamicReader(IXLWorkbook? workbook)
     {
         xlWorkbook = workbook ?? throw new ArgumentNullException(nameof(workbook));
     }
@@ -31,11 +31,10 @@ public class ExcelDynamicReader : IDisposable
                 var cell = row.Cell(item.Position);
                 if (cell == null || cell.Value.IsBlank)
                 {
-                    dynamicRow.Add(item);
+                    dynamicRow.Add(item with { Value = null });
                     continue;
                 }
 
-                item.Type ??= cell.Value.ValueType();
                 var cellItem = item with
                 {
                     Value = cell.Value.ToObject()
@@ -88,7 +87,7 @@ public class ExcelDynamicReader : IDisposable
 
     public IEnumerable<List<DynamicCell>> Read(int worksheetIndex = 1, uint startFrom = 1, uint skip = 0)
     {
-        if (worksheetIndex > xlWorkbook.Worksheets.Count) yield break;
+        if (worksheetIndex < 1 || worksheetIndex > xlWorkbook.Worksheets.Count) yield break;
 
         var worksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Position == worksheetIndex);
         if (worksheet == null) yield break;
@@ -109,16 +108,6 @@ public class ExcelDynamicReader : IDisposable
 
     public void Dispose()
     {
-        Dispose(true);
-        GC.SuppressFinalize(this);
+        xlWorkbook.Dispose();
     }
-
-    protected virtual void Dispose(bool disposing)
-    {
-        if (disposing)
-        {
-            xlWorkbook.Dispose();
-        }
-    }
-    ~ExcelDynamicReader() => Dispose(false);
 }

+ 14 - 17
ExcelORM/ExcelORM/ExcelDynamicWriter.cs

@@ -4,7 +4,7 @@ using ExcelORM.Models;
 
 namespace ExcelORM;
 
-public class ExcelDynamicWriter : IDisposable
+public sealed class ExcelDynamicWriter : IDisposable
 {
     private readonly IXLWorkbook xlWorkbook;
     public ExcelDynamicWriter(string? path = null)
@@ -12,7 +12,7 @@ public class ExcelDynamicWriter : IDisposable
         xlWorkbook = File.Exists(path) ? new XLWorkbook(path) : new XLWorkbook();
     }
 
-    public ExcelDynamicWriter(IXLWorkbook workbook)
+    public ExcelDynamicWriter(IXLWorkbook? workbook)
     {
         xlWorkbook = workbook ?? throw new ArgumentNullException(nameof(workbook));
     }
@@ -28,16 +28,23 @@ public class ExcelDynamicWriter : IDisposable
 
     private static void Write(IEnumerable<List<DynamicCell>> values, IXLWorksheet worksheet, bool append)
     {
-        var lastRow = worksheet.LastRowUsed();
-        if (lastRow == null) append = false;
+        var valuesList = values.ToList();
+        if (valuesList.Count == 0) return;
         
+        var lastRow = worksheet.LastRowUsed();
+        if (lastRow == null)
+        {
+            append = false;
+            lastRow = worksheet.FirstRow();
+        }
+
         var rowIndex = append switch
         {
             true => lastRow.RowNumber() + 1,
-            false => GenerateHeader(worksheet, values.First()),
+            false => GenerateHeader(worksheet, valuesList.First()),
         };
 
-        foreach (var row in values)
+        foreach (var row in valuesList)
         {
             foreach (var cell in row)
             {
@@ -79,16 +86,6 @@ public class ExcelDynamicWriter : IDisposable
 
     public void Dispose()
     {
-        Dispose(true);
-        GC.SuppressFinalize(this);
-    }
-
-    protected virtual void Dispose(bool disposing)
-    {
-        if (disposing)
-        {
-            xlWorkbook.Dispose();
-        }
+        xlWorkbook.Dispose();
     }
-    ~ExcelDynamicWriter() => Dispose(false);
 }

+ 3 - 4
ExcelORM/ExcelORM/ExcelORM.csproj

@@ -4,7 +4,7 @@
         <TargetFramework>net8.0</TargetFramework>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
-        <Version>2.9.0</Version>
+        <Version>3.0.2</Version>
         <PackageProjectUrl>https://git.liox.eu/pczajkowski/ExcelORM</PackageProjectUrl>
         <RepositoryUrl>https://github.com/pczajkowski/ExcelORM</RepositoryUrl>
         <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
@@ -14,12 +14,11 @@
 	<Authors>Piotr Czajkowski</Authors>
 	<Description>Simple library to read/write C# objects from/to Excel files. </Description>
 	<RepositoryType>GitHub</RepositoryType>
-	<PackageReleaseNotes>Ability to start writing from given row.
-Trying to handle dates saved as text without throwing.</PackageReleaseNotes>
+	<PackageReleaseNotes>Few small improvements.</PackageReleaseNotes>
     </PropertyGroup>
 
     <ItemGroup>
-	    <PackageReference Include="ClosedXML" Version="0.105.0" />
+	    <PackageReference Include="ClosedXML" Version="0.105.1" />
 	    <None Include="../../README.md" Pack="true" PackagePath="\" />
 	    <None Include="../../LICENSE" Pack="true" PackagePath="" />
     </ItemGroup>

+ 4 - 14
ExcelORM/ExcelORM/ExcelReader.cs

@@ -4,13 +4,13 @@ using ExcelORM.Models;
 
 namespace ExcelORM;
 
-public class ExcelReader : IDisposable
+public sealed class ExcelReader : IDisposable
 {
     private readonly IXLWorkbook xlWorkbook;
     public bool SkipHidden { get; set; }
     public bool ObeyFilter { get; set; }
 
-    public ExcelReader(IXLWorkbook workbook)
+    public ExcelReader(IXLWorkbook? workbook)
     {
         xlWorkbook = workbook ?? throw new ArgumentNullException(nameof(workbook));
     }
@@ -101,7 +101,7 @@ public class ExcelReader : IDisposable
 
     public IEnumerable<T> Read<T>(int worksheetIndex = 1, uint startFrom = 1, uint skip = 0) where T : class
     {
-        if (worksheetIndex > xlWorkbook.Worksheets.Count) yield break;
+        if (worksheetIndex < 1 || worksheetIndex > xlWorkbook.Worksheets.Count) yield break;
 
         var worksheet = xlWorkbook.Worksheets.FirstOrDefault(x => x.Position == worksheetIndex);
         if (worksheet == null) yield break;
@@ -117,16 +117,6 @@ public class ExcelReader : IDisposable
 
     public void Dispose()
     {
-        Dispose(true);
-        GC.SuppressFinalize(this);
+        xlWorkbook.Dispose();
     }
-
-    protected virtual void Dispose(bool disposing)
-    {
-        if (disposing)
-        {
-            xlWorkbook.Dispose();
-        }
-    }
-    ~ExcelReader() => Dispose(false);
 }

+ 3 - 13
ExcelORM/ExcelORM/ExcelWriter.cs

@@ -6,7 +6,7 @@ using ExcelORM.Models;
 
 namespace ExcelORM;
 
-public class ExcelWriter : IDisposable
+public sealed class ExcelWriter : IDisposable
 {
     private readonly IXLWorkbook xlWorkbook;
     public ExcelWriter(string? path = null)
@@ -14,7 +14,7 @@ public class ExcelWriter : IDisposable
         xlWorkbook = File.Exists(path) ? new XLWorkbook(path) : new XLWorkbook();
     }
 
-    public ExcelWriter(IXLWorkbook workbook)
+    public ExcelWriter(IXLWorkbook? workbook)
     {
         xlWorkbook = workbook ?? throw new ArgumentNullException(nameof(workbook));
     }
@@ -131,16 +131,6 @@ public class ExcelWriter : IDisposable
 
     public void Dispose()
     {
-        Dispose(true);
-        GC.SuppressFinalize(this);
+        xlWorkbook.Dispose();
     }
-
-    protected virtual void Dispose(bool disposing)
-    {
-        if (disposing)
-        {
-            xlWorkbook.Dispose();
-        }
-    }
-    ~ExcelWriter() => Dispose(false);
 }

+ 11 - 1
ExcelORM/ExcelORM/Models/DynamicCell.cs

@@ -7,7 +7,17 @@ namespace ExcelORM.Models
         public int Position { get; set; }
         public string? Header { get; set; }
         public Type? Type { get; set; }
-        public object? Value { get; set; }
+
+        private object? value;
+        public object? Value
+        {
+            get => value;
+            set
+            {
+                this.value = value;
+                Type = value?.GetType();
+            }
+        }
 
         public static List<DynamicCell>? MapHeader(IXLCells? headerCells)
         {

+ 39 - 36
ExcelORM/ExcelORM/TypeExtensions.cs

@@ -13,7 +13,7 @@ public static class TypeExtensions
             return guid;
 
         if (property.PropertyType == typeof(Guid?)) return null;
-        return Guid.Empty; 
+        return Guid.Empty;
     }
 
     private static object? HandleEnum(XLCellValue value, PropertyInfo property, Type? nullableUnderlyingType)
@@ -23,29 +23,32 @@ public static class TypeExtensions
             return Enum.TryParse(nullableUnderlyingType, value.GetText(), true, out var enumNullableValue)
                 ? enumNullableValue : null;
         }
-        
+
         return Enum.TryParse(property.PropertyType, value.GetText(), true, out var enumValue)
             ? enumValue : Enum.GetValues(property.PropertyType).GetValue(0);
-    } 
-    
+    }
+
     private static object? GetAdditionalTypeFromText(XLCellValue value, PropertyInfo? property = null)
     {
         if (property == null) return value.GetText();
-        
-        if (property.PropertyType == typeof(Guid) || property.PropertyType == typeof(Guid?))
-            return HandleGuid(value, property);
 
-        if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?))
-            return DateTime.TryParse(value.GetText(), out var dateTime) ? dateTime : default;
-
-        if (property.PropertyType == typeof(DateOnly) || property.PropertyType == typeof(DateOnly?))
-            return DateOnly.TryParse(value.GetText(), out var dateOnly) ? dateOnly : default;
+        var pt = property.PropertyType;
+        switch (pt)
+        {
+            case var _ when pt == typeof(Guid) || pt == typeof(Guid?):
+                return HandleGuid(value, property);
+            case var _ when pt == typeof(DateTime) || pt == typeof(DateTime?):
+                DateTime.TryParse(value.GetText(), out var dateValue);
+                return dateValue;
+            case var _ when pt == typeof(DateOnly) || pt == typeof(DateOnly?):
+                DateOnly.TryParse(value.GetText(), out var dateOnlyValue);
+                return dateOnlyValue;
+            case { IsEnum: true }:
+            case var _ when Nullable.GetUnderlyingType(pt) is { IsEnum: true }:
+                return HandleEnum(value, property, Nullable.GetUnderlyingType(property.PropertyType));
+        }
 
-        var nullableUnderlyingType = Nullable.GetUnderlyingType(property.PropertyType);
-        if (property.PropertyType.IsEnum || (nullableUnderlyingType is { IsEnum: true }))
-            return HandleEnum(value, property, nullableUnderlyingType);
-        
-        return value.GetText(); 
+        return value.GetText();
     }
 
     private static object? GetSpecificNumberType(XLCellValue value, PropertyInfo? property)
@@ -70,7 +73,7 @@ public static class TypeExtensions
             throw;
         }
     }
-    
+
     // Borrowed from https://github.com/ClosedXML/ClosedXML/blob/develop/ClosedXML/Excel/XLCellValue.cs#L361
     public static object? ToObject(this XLCellValue value, PropertyInfo? property = null)
     {
@@ -87,33 +90,33 @@ public static class TypeExtensions
         };
     }
 
-    public static Type ValueType(this XLCellValue value)
-    {
-        return value.Type switch
-        {
-            XLDataType.Blank => typeof(string),
-            XLDataType.Boolean => typeof(bool),
-            XLDataType.Number => typeof(double?),
-            XLDataType.Text => typeof(string),
-            XLDataType.DateTime => typeof(DateTime?),
-            XLDataType.TimeSpan => typeof(TimeSpan?),
-            _ => throw new InvalidCastException()
-        };
-    }
-
     public static void SetPropertyValue<T>(this T currentObject, PropertyInfo property, XLCellValue value)
     {
         var valueToSet = value.ToObject(property);
-        if (valueToSet == null) return;
 
         try
         {
             property.SetValue(currentObject, valueToSet);
         }
-        catch
+        catch (ArgumentException ex) // Catches issues like type mismatch or null for non-nullable
         {
-            valueToSet = value.ToString();
-            property.SetValue(currentObject, valueToSet);
+            // If the property type is string, try to set it directly from the XLCellValue's string representation.
+            if (property.PropertyType == typeof(string))
+            {
+                property.SetValue(currentObject, value.ToString());
+            }
+            else
+            {
+                // If it's not a string property, and the initial assignment failed,
+                // re-throw with more context.
+                throw new InvalidCastException($"Could not set property '{property.Name}' of type '{property.PropertyType.Name}' with value '{valueToSet}' (original XLCellValue: '{value}'). " +
+                                               $"The value returned by ToObject was incompatible, and the property type is not string for fallback conversion. See inner exception for details.", ex);
+            }
+        }
+        catch (Exception ex) // Catch any other unexpected exceptions from SetValue
+        {
+            throw new InvalidOperationException($"An unexpected error occurred while setting property '{property.Name}' of type '{property.PropertyType.Name}'. " +
+                                                $"Value attempted to set: '{valueToSet}' (original XLCellValue: '{value}'). See inner exception for details.", ex);
         }
     }
 

+ 4 - 4
ExcelORM/ExcelORMTests/DynamicReaderTests.cs

@@ -26,10 +26,10 @@ public class DynamicReaderTests
 
         var first = results.First();
         Assert.Equal(typeof(string), first[0].Type);
-        Assert.Equal(typeof(DateTime?), first[1].Type);
-        Assert.Equal(typeof(TimeSpan?), first[2].Type);
-        Assert.Equal(typeof(double?), first[3].Type);
-        Assert.Equal(typeof(double?), first[4].Type);
+        Assert.Equal(typeof(DateTime), first[1].Type);
+        Assert.Equal(typeof(TimeSpan), first[2].Type);
+        Assert.Equal(typeof(double), first[3].Type);
+        Assert.Equal(typeof(double), first[4].Type);
     }
 
     [Fact]

+ 2 - 2
ExcelORM/ExcelORMTests/ExcelORMTests.csproj

@@ -10,13 +10,13 @@
     </PropertyGroup>
 
     <ItemGroup>
-        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
         <PackageReference Include="xunit" Version="2.9.3" />
         <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
             <PrivateAssets>all</PrivateAssets>
         </PackageReference>
-        <PackageReference Include="coverlet.collector" Version="6.0.4">
+        <PackageReference Include="coverlet.collector" Version="10.0.1">
             <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
             <PrivateAssets>all</PrivateAssets>
         </PackageReference>

+ 0 - 0
ExcelORM/ExcelORMTests/Test.cs → ExcelORM/ExcelORMTests/Models/Test.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestAdditionalTypes.cs → ExcelORM/ExcelORMTests/Models/TestAdditionalTypes.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestAdditionalTypesNullable.cs → ExcelORM/ExcelORMTests/Models/TestAdditionalTypesNullable.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestEnum.cs → ExcelORM/ExcelORMTests/Models/TestEnum.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestNumbersWithFormula.cs → ExcelORM/ExcelORMTests/Models/TestNumbersWithFormula.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestSkip.cs → ExcelORM/ExcelORMTests/Models/TestSkip.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestSkipMiddle.cs → ExcelORM/ExcelORMTests/Models/TestSkipMiddle.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestTypes.cs → ExcelORM/ExcelORMTests/Models/TestTypes.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestWithFormula.cs → ExcelORM/ExcelORMTests/Models/TestWithFormula.cs


+ 0 - 0
ExcelORM/ExcelORMTests/TestWithHyperlink.cs → ExcelORM/ExcelORMTests/Models/TestWithHyperlink.cs


+ 26 - 0
ExcelORM/ExcelORMTests/TypeExtensionsTests.cs

@@ -28,4 +28,30 @@ public class TypeExtensionsTests
         var readValue = value.ToObject(propertyInfo);
         Assert.IsType<DateOnly>(readValue);
     }
+     
+    public Guid? GuidProperty { get; set; }
+    
+    [Fact]
+    public void ToObject_GuidAsString()
+    {
+        XLCellValue value = "00000000-0000-0000-0000-000000000001";
+        
+        var propertyInfo = typeof(TypeExtensionsTests).GetProperty("GuidProperty");
+        var readValue = value.ToObject(propertyInfo);
+        Assert.IsType<Guid>(readValue);
+        Assert.NotEqual(Guid.Empty, readValue);
+    }
+     
+    public TestEnum? EnumProperty { get; set; }
+    
+    [Fact]
+    public void ToObject_EnumAsString()
+    {
+        XLCellValue value = "Second";
+        
+        var propertyInfo = typeof(TypeExtensionsTests).GetProperty("EnumProperty");
+        var readValue = value.ToObject(propertyInfo);
+        Assert.IsType<TestEnum>(readValue);
+        Assert.NotEqual(TestEnum.First, readValue);
+    }
 }

+ 1 - 1
versions.md

@@ -9,4 +9,4 @@
 | 2.6.0 | Added support for appending starting from given row.|
 | 2.7.0 | Added support for reading properties of type Guid and enum.|
 | 2.8.0 | Handling more number types. Properly handling appending to and reading from empty file.|
-| 2.8.1 | Ability to start writing from given row.|
+| 3.0.0 | Ability to start writing from given row. Trying to handle dates saved as text without throwing.|