21 Achegas 553f4b1b2a ... a5121bed88

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

+ 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.|