diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoDateAndTimeOnlyMapper.cs b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoDateAndTimeOnlyMapper.cs
new file mode 100644
index 0000000000..4e3ec6a411
--- /dev/null
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoDateAndTimeOnlyMapper.cs
@@ -0,0 +1,64 @@
+using NPoco;
+
+namespace Umbraco.Cms.Persistence.Sqlite.Mappers;
+
+///
+/// Provides a custom POCO mapper for handling date and time only values when working with SQLite databases.
+///
+public class SqlitePocoDateAndTimeOnlyMapper : DefaultMapper
+{
+ ///
+ public override Func GetFromDbConverter(Type destType, Type sourceType)
+ {
+ if (IsDateOnlyType(destType))
+ {
+ return value => ConvertToDateOnly(value, IsNullableType(destType));
+ }
+
+ if (IsTimeOnlyType(destType))
+ {
+ return value => ConvertToTimeOnly(value, IsNullableType(destType));
+ }
+
+ return base.GetFromDbConverter(destType, sourceType);
+ }
+
+ private static bool IsDateOnlyType(Type type) =>
+ type == typeof(DateOnly) || type == typeof(DateOnly?);
+
+ private static bool IsTimeOnlyType(Type type) =>
+ type == typeof(TimeOnly) || type == typeof(TimeOnly?);
+
+ private static bool IsNullableType(Type type) =>
+ Nullable.GetUnderlyingType(type) != null;
+
+ private static object? ConvertToDateOnly(object? value, bool isNullable)
+ {
+ if (value is null)
+ {
+ return isNullable ? null : default(DateOnly);
+ }
+
+ if (value is DateTime dt)
+ {
+ return DateOnly.FromDateTime(dt);
+ }
+
+ return DateOnly.Parse(value.ToString()!);
+ }
+
+ private static object? ConvertToTimeOnly(object? value, bool isNullable)
+ {
+ if (value is null)
+ {
+ return isNullable ? null : default(TimeOnly);
+ }
+
+ if (value is DateTime dt)
+ {
+ return TimeOnly.FromDateTime(dt);
+ }
+
+ return TimeOnly.Parse(value.ToString()!);
+ }
+}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoDecimalMapper.cs b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoDecimalMapper.cs
new file mode 100644
index 0000000000..567b1cbcb8
--- /dev/null
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoDecimalMapper.cs
@@ -0,0 +1,26 @@
+using System.Globalization;
+using NPoco;
+
+namespace Umbraco.Cms.Persistence.Sqlite.Mappers;
+
+///
+/// Provides a custom POCO mapper for handling decimal values when working with SQLite databases.
+///
+public class SqlitePocoDecimalMapper : DefaultMapper
+{
+ ///
+ public override Func GetFromDbConverter(Type destType, Type sourceType)
+ {
+ if (destType == typeof(decimal))
+ {
+ return value => Convert.ToDecimal(value, CultureInfo.InvariantCulture);
+ }
+
+ if (destType == typeof(decimal?))
+ {
+ return value => Convert.ToDecimal(value, CultureInfo.InvariantCulture);
+ }
+
+ return base.GetFromDbConverter(destType, sourceType);
+ }
+}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs
index ab62b4b1d1..ffb96a6b2b 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Mappers/SqlitePocoGuidMapper.cs
@@ -1,19 +1,18 @@
-using System.Globalization;
using NPoco;
namespace Umbraco.Cms.Persistence.Sqlite.Mappers;
+///
+/// Provides a custom POCO mapper for handling GUID values when working with SQLite databases.
+///
public class SqlitePocoGuidMapper : DefaultMapper
{
+ ///
public override Func GetFromDbConverter(Type destType, Type sourceType)
{
if (destType == typeof(Guid))
{
- return value =>
- {
- var result = Guid.Parse($"{value}");
- return result;
- };
+ return value => Guid.Parse($"{value}");
}
if (destType == typeof(Guid?))
@@ -29,24 +28,6 @@ public class SqlitePocoGuidMapper : DefaultMapper
};
}
- if (destType == typeof(decimal))
- {
- return value =>
- {
- var result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
- return result;
- };
- }
-
- if (destType == typeof(decimal?))
- {
- return value =>
- {
- var result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
- return result;
- };
- }
-
return base.GetFromDbConverter(destType, sourceType);
}
}
diff --git a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs
index 66f542712a..9c577d6329 100644
--- a/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs
+++ b/src/Umbraco.Cms.Persistence.Sqlite/Services/SqliteSpecificMapperFactory.cs
@@ -12,5 +12,5 @@ public class SqliteSpecificMapperFactory : IProviderSpecificMapperFactory
public string ProviderName => Constants.ProviderName;
///
- public NPocoMapperCollection Mappers => new(() => new[] { new SqlitePocoGuidMapper() });
+ public NPocoMapperCollection Mappers => new(() => [new SqlitePocoGuidMapper(), new SqlitePocoDecimalMapper(), new SqlitePocoDateAndTimeOnlyMapper()]);
}
diff --git a/src/Umbraco.Infrastructure/Mapping/UmbracoDefaultMapper.cs b/src/Umbraco.Infrastructure/Mapping/UmbracoDefaultMapper.cs
index 986bb19d39..0afb677ee9 100644
--- a/src/Umbraco.Infrastructure/Mapping/UmbracoDefaultMapper.cs
+++ b/src/Umbraco.Infrastructure/Mapping/UmbracoDefaultMapper.cs
@@ -1,30 +1,76 @@
-using System.Globalization;
+using System.Globalization;
using NPoco;
namespace Umbraco.Cms.Core.Mapping;
+///
+/// Provides default type conversion logic for mapping Umbraco database values to .NET types, extending the base mapping
+/// behavior with support for additional types such as decimal, DateOnly, and TimeOnly.
+///
public class UmbracoDefaultMapper : DefaultMapper
{
+ ///
public override Func GetFromDbConverter(Type destType, Type sourceType)
{
if (destType == typeof(decimal))
{
- return value =>
- {
- var result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
- return result;
- };
+ return value => Convert.ToDecimal(value, CultureInfo.InvariantCulture);
}
if (destType == typeof(decimal?))
{
- return value =>
- {
- var result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
- return result;
- };
+ return value => Convert.ToDecimal(value, CultureInfo.InvariantCulture);
+ }
+
+ if(IsDateOnlyType(destType))
+ {
+ return value => ConvertToDateOnly(value, IsNullableType(destType));
+ }
+
+ if (IsTimeOnlyType(destType))
+ {
+ return value => ConvertToTimeOnly(value, IsNullableType(destType));
}
return base.GetFromDbConverter(destType, sourceType);
}
+
+ private static bool IsDateOnlyType(Type type) =>
+ type == typeof(DateOnly) || type == typeof(DateOnly?);
+
+ private static bool IsTimeOnlyType(Type type) =>
+ type == typeof(TimeOnly) || type == typeof(TimeOnly?);
+
+ private static bool IsNullableType(Type type) =>
+ Nullable.GetUnderlyingType(type) != null;
+
+ private static object? ConvertToDateOnly(object? value, bool isNullable)
+ {
+ if (value is null)
+ {
+ return isNullable ? null : default(DateOnly);
+ }
+
+ if (value is DateTime dt)
+ {
+ return DateOnly.FromDateTime(dt);
+ }
+
+ return DateOnly.Parse(value.ToString()!);
+ }
+
+ private static object? ConvertToTimeOnly(object? value, bool isNullable)
+ {
+ if (value is null)
+ {
+ return isNullable ? null : default(TimeOnly);
+ }
+
+ if (value is DateTime dt)
+ {
+ return TimeOnly.FromDateTime(dt);
+ }
+
+ return TimeOnly.Parse(value.ToString()!);
+ }
}
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
index 9cefd32b5b..82bbf26f81 100644
--- a/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
+++ b/src/Umbraco.Infrastructure/Persistence/SqlSyntax/SqlSyntaxProviderBase.cs
@@ -100,6 +100,10 @@ public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider
public string TimeColumnDefinition { get; protected set; } = "DATETIME";
+ public string DateOnlyColumnDefinition { get; protected set; } = "DATE";
+
+ public string TimeOnlyColumnDefinition { get; protected set; } = "TIME";
+
protected IList> ClauseOrder { get; }
protected DbTypes DbTypeMap => _dbTypes.Value;
@@ -531,6 +535,10 @@ public abstract class SqlSyntaxProviderBase : ISqlSyntaxProvider
dbTypeMap.Set(DbType.Time, TimeColumnDefinition);
dbTypeMap.Set(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
dbTypeMap.Set(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
+ dbTypeMap.Set(DbType.Date, DateOnlyColumnDefinition);
+ dbTypeMap.Set(DbType.Date, DateOnlyColumnDefinition);
+ dbTypeMap.Set(DbType.Time, TimeOnlyColumnDefinition);
+ dbTypeMap.Set(DbType.Time, TimeOnlyColumnDefinition);
dbTypeMap.Set(DbType.Byte, IntColumnDefinition);
dbTypeMap.Set(DbType.Byte, IntColumnDefinition);
diff --git a/tests/Umbraco.Tests.Integration/Testing/SqliteTestDatabase.cs b/tests/Umbraco.Tests.Integration/Testing/SqliteTestDatabase.cs
index a20b00ebe5..73b9c444a7 100644
--- a/tests/Umbraco.Tests.Integration/Testing/SqliteTestDatabase.cs
+++ b/tests/Umbraco.Tests.Integration/Testing/SqliteTestDatabase.cs
@@ -96,6 +96,8 @@ public class SqliteTestDatabase : BaseTestDatabase, ITestDatabase
database.Mappers.Add(new NullableDateMapper());
database.Mappers.Add(new SqlitePocoGuidMapper());
+ database.Mappers.Add(new SqlitePocoDecimalMapper());
+ database.Mappers.Add(new SqlitePocoDateAndTimeOnlyMapper());
foreach (var dbCommand in _cachedDatabaseInitCommands)
{