Database migrations: Support DateOnly and TimeOnly in syntax providers (#20784)
* sql column type map include dateonly and timeonly * Split Mapper and add check null value * Minor code tidy resolving a few warnings. * add spaces * clean code --------- Co-authored-by: Lan Nguyen Thuy <lnt@umbraco.dk> Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
using NPoco;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.Sqlite.Mappers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a custom POCO mapper for handling date and time only values when working with SQLite databases.
|
||||
/// </summary>
|
||||
public class SqlitePocoDateAndTimeOnlyMapper : DefaultMapper
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Func<object, object?> 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()!);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Globalization;
|
||||
using NPoco;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.Sqlite.Mappers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a custom POCO mapper for handling decimal values when working with SQLite databases.
|
||||
/// </summary>
|
||||
public class SqlitePocoDecimalMapper : DefaultMapper
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Func<object, object?> 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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
using System.Globalization;
|
||||
using NPoco;
|
||||
|
||||
namespace Umbraco.Cms.Persistence.Sqlite.Mappers;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a custom POCO mapper for handling GUID values when working with SQLite databases.
|
||||
/// </summary>
|
||||
public class SqlitePocoGuidMapper : DefaultMapper
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Func<object, object?> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@ public class SqliteSpecificMapperFactory : IProviderSpecificMapperFactory
|
||||
public string ProviderName => Constants.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public NPocoMapperCollection Mappers => new(() => new[] { new SqlitePocoGuidMapper() });
|
||||
public NPocoMapperCollection Mappers => new(() => [new SqlitePocoGuidMapper(), new SqlitePocoDecimalMapper(), new SqlitePocoDateAndTimeOnlyMapper()]);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,76 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using NPoco;
|
||||
|
||||
namespace Umbraco.Cms.Core.Mapping;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class UmbracoDefaultMapper : DefaultMapper
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override Func<object, object?> 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 =>
|
||||
return value => Convert.ToDecimal(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if(IsDateOnlyType(destType))
|
||||
{
|
||||
var result = Convert.ToDecimal(value, CultureInfo.InvariantCulture);
|
||||
return result;
|
||||
};
|
||||
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()!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ public abstract class SqlSyntaxProviderBase<TSyntax> : ISqlSyntaxProvider
|
||||
|
||||
public string TimeColumnDefinition { get; protected set; } = "DATETIME";
|
||||
|
||||
public string DateOnlyColumnDefinition { get; protected set; } = "DATE";
|
||||
|
||||
public string TimeOnlyColumnDefinition { get; protected set; } = "TIME";
|
||||
|
||||
protected IList<Func<ColumnDefinition, string>> ClauseOrder { get; }
|
||||
|
||||
protected DbTypes DbTypeMap => _dbTypes.Value;
|
||||
@@ -531,6 +535,10 @@ public abstract class SqlSyntaxProviderBase<TSyntax> : ISqlSyntaxProvider
|
||||
dbTypeMap.Set<TimeSpan?>(DbType.Time, TimeColumnDefinition);
|
||||
dbTypeMap.Set<DateTimeOffset>(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
|
||||
dbTypeMap.Set<DateTimeOffset?>(DbType.DateTimeOffset, DateTimeOffsetColumnDefinition);
|
||||
dbTypeMap.Set<DateOnly>(DbType.Date, DateOnlyColumnDefinition);
|
||||
dbTypeMap.Set<DateOnly?>(DbType.Date, DateOnlyColumnDefinition);
|
||||
dbTypeMap.Set<TimeOnly>(DbType.Time, TimeOnlyColumnDefinition);
|
||||
dbTypeMap.Set<TimeOnly?>(DbType.Time, TimeOnlyColumnDefinition);
|
||||
|
||||
dbTypeMap.Set<byte>(DbType.Byte, IntColumnDefinition);
|
||||
dbTypeMap.Set<byte?>(DbType.Byte, IntColumnDefinition);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user