diff options
Diffstat (limited to 'docker/crupest-api/CrupestApi/CrupestApi.Commons')
7 files changed, 185 insertions, 13 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs index c3d118c..1329c99 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs @@ -1,8 +1,12 @@ +using System.Data; using System.Reflection; using System.Text; namespace CrupestApi.Commons.Crud; +public delegate Task EntityPreSave(object? entity, ColumnInfo column, TableInfo table, IDbConnection connection); +public delegate Task EntityPostGet(object? entity, ColumnInfo column, TableInfo table, IDbConnection connection); + public class ColumnInfo { private Type ExtractRealTypeFromNullable(Type type) @@ -24,7 +28,7 @@ public class ColumnInfo } EntityType = entityType; - PropertyName = null; + PropertyName = sqlColumnName; PropertyType = typeof(int); PropertyRealType = typeof(int); SqlColumnName = sqlColumnName; @@ -45,36 +49,55 @@ public class ColumnInfo EntityType = entityType; PropertyName = entityPropertyName; + PropertyInfo = entityType.GetProperty(entityPropertyName); - var property = entityType.GetProperty(entityPropertyName); - - if (property is null) + if (PropertyInfo is null) throw new Exception("Public property with given name does not exist."); - PropertyType = property.PropertyType; + PropertyType = PropertyInfo.PropertyType; PropertyRealType = ExtractRealTypeFromNullable(PropertyType); - var columnAttribute = property.GetCustomAttribute<ColumnAttribute>(); + var columnAttribute = PropertyInfo.GetCustomAttribute<ColumnAttribute>(); if (columnAttribute is null) { SqlColumnName = PropertyName; Nullable = true; IndexType = ColumnIndexType.None; + DefaultEmptyForString = false; } else { SqlColumnName = columnAttribute.DatabaseName ?? PropertyName; Nullable = !columnAttribute.NonNullable; IndexType = columnAttribute.IndexType; + DefaultEmptyForString = columnAttribute.DefaultEmptyForString; } ColumnTypeInfo = typeRegistry.GetRequiredByDataType(PropertyRealType); TypeRegistry = typeRegistry; + + if (DefaultEmptyForString) + { + EntityPostGet += (entity, column, _, _) => + { + var pi = column.PropertyInfo; + if (pi is not null && column.ColumnTypeInfo.GetDatabaseType() == typeof(string)) + { + var value = pi.GetValue(entity); + if (value is null) + { + pi.SetValue(entity, string.Empty); + } + } + return Task.CompletedTask; + }; + } } public Type EntityType { get; } // If null, there is no corresponding property. - public string? PropertyName { get; } + public PropertyInfo? PropertyInfo { get; } = null; + public string PropertyName { get; } public Type PropertyType { get; } public Type PropertyRealType { get; } public string SqlColumnName { get; } @@ -84,6 +107,10 @@ public class ColumnInfo public bool IsPrimaryKey { get; } public bool IsAutoIncrement { get; } public ColumnIndexType IndexType { get; } + public bool DefaultEmptyForString { get; } + + public event EntityPreSave? EntityPreSave; + public event EntityPostGet? EntityPostGet; public string SqlType => TypeRegistry.GetSqlType(ColumnTypeInfo); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs index 6f46cd5..c31a13e 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs @@ -28,4 +28,7 @@ public class ColumnAttribute : Attribute, IColumnMetadata public bool IsAutoIncrement { get; set; } public ColumnIndexType IndexType { get; set; } = ColumnIndexType.None; + + // Use empty string for default value of string type. + public bool DefaultEmptyForString { get; set; } } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs index ff8ccea..d7adb33 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnTypeInfo.cs @@ -1,9 +1,16 @@ using System.Diagnostics; +using System.Text.Json; +using System.Text.Json.Serialization; namespace CrupestApi.Commons.Crud; public interface IColumnTypeInfo { + JsonConverter? GetOptionalJsonConverter() + { + return null; + } + Type GetDataType(); Type GetDatabaseType(); object ConvertToDatabase(object data); @@ -122,6 +129,46 @@ public abstract class CustomColumnTypeInfo<TDataType, TDatabaseType> : ICustomCo } } +public class DateTimeColumnTypeInfo : CustomColumnTypeInfo<DateTime, long> +{ + private readonly DateTimeJsonConverter _jsonConverter = new DateTimeJsonConverter(); + + public JsonConverter GetJsonConverter() + { + return _jsonConverter; + } + + public override long ConvertToDatabase(DateTime data) + { + return new DateTimeOffset(data).ToUnixTimeSeconds(); + } + + public override DateTime ConvertFromDatabase(long databaseData) + { + return DateTimeOffset.FromUnixTimeSeconds(databaseData).LocalDateTime; + } +} + +public class DateTimeJsonConverter : JsonConverter<DateTime> +{ + public override bool HandleNull => false; + + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert == typeof(DateTime); + } + + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()).LocalDateTime; + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteNumberValue(new DateTimeOffset(value).ToUnixTimeSeconds()); + } +} + public class ColumnTypeInfoRegistry { public static IReadOnlyList<IColumnTypeInfo> BuiltinList = new List<IColumnTypeInfo>() @@ -139,7 +186,7 @@ public class ColumnTypeInfoRegistry public static IReadOnlyList<IColumnTypeInfo> CustomList = new List<IColumnTypeInfo>() { - // TODO: Add custom ones. + new DateTimeColumnTypeInfo(), }; public static ColumnTypeInfoRegistry Singleton { get; } @@ -229,4 +276,13 @@ public class ColumnTypeInfoRegistry } } + public IEnumerable<JsonConverter> GetJsonConverters() + { + foreach (var columnTypeInfo in _list) + { + var converter = columnTypeInfo.GetOptionalJsonConverter(); + if (converter is not null) + yield return converter; + } + } } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs index bbd5e9a..7da6ac7 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/CrudService.cs @@ -8,13 +8,13 @@ public class CrudService<TEntity> { protected readonly TableInfo _table; protected readonly IOptionsSnapshot<CrupestApiConfig> _crupestApiOptions; - protected readonly ILogger<CrudService<TEntity>> _logger; + private readonly ILogger<CrudService<TEntity>> _logger; - public CrudService(IOptionsSnapshot<CrupestApiConfig> crupestApiOptions, ILogger<CrudService<TEntity>> logger) + public CrudService(ServiceProvider services) { _table = new TableInfo(typeof(TEntity)); - _crupestApiOptions = crupestApiOptions; - _logger = logger; + _crupestApiOptions = services.GetRequiredService<IOptionsSnapshot<CrupestApiConfig>>(); + _logger = services.GetRequiredService<ILogger<CrudService<TEntity>>>(); } public virtual string GetDbConnectionString() @@ -78,4 +78,12 @@ public class CrudService<TEntity> var sql = _table.GenerateUpdateSql(where, update, out parameters); return await connection.ExecuteAsync(sql, parameters); } + + public virtual async Task<int> DeleteAsync(WhereClause? where) + { + var connection = await EnsureDatabase(); + DynamicParameters parameters; + var sql = _table.GenerateDeleteSql(where, out parameters); + return await connection.ExecuteAsync(sql, parameters); + } } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs index 9610e40..c58897c 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -190,6 +190,46 @@ CREATE TABLE {tableName}( return result.ToString(); } + public InsertClause GenerateInsertClauseFromObject(object value) + { + var insertClause = InsertClause.Create(); + + foreach (var column in ColumnInfos) + { + var propertyInfo = column.PropertyInfo; + if (propertyInfo is null) + { + propertyInfo = EntityType.GetProperty(column.PropertyName); + } + if (propertyInfo is null) + { + if (column.IsAutoIncrement) + { + continue; + } + else + { + throw new Exception($"Property {column.PropertyName} not found."); + } + } + + var propertyValue = propertyInfo.GetValue(value); + if (propertyValue is null) + { + if (column.IsAutoIncrement) + { + continue; + } + else + { + insertClause.Add(column.SqlColumnName, propertyValue); + } + } + } + + return insertClause; + } + public string GenerateInsertSql(InsertClause insertClause, out DynamicParameters parameters) { var relatedColumns = insertClause.GetRelatedColumns(); @@ -244,4 +284,32 @@ CREATE TABLE {tableName}( return sb.ToString(); } -}
\ No newline at end of file + + public string GenerateDeleteSql(WhereClause? whereClause, out DynamicParameters parameters) + { + if (whereClause is not null) + { + var relatedColumns = ((IWhereClause)whereClause).GetRelatedColumns() ?? new List<string>(); + foreach (var column in relatedColumns) + { + if (!ColumnNameList.Contains(column)) + { + throw new ArgumentException($"Column {column} is not in the table."); + } + } + } + + parameters = new DynamicParameters(); + + StringBuilder sb = new StringBuilder("DELETE FROM "); + sb.Append(TableName); + if (whereClause is not null) + { + sb.Append(" WHERE "); + sb.Append(whereClause.GenerateSql(parameters)); + } + sb.Append(';'); + + return sb.ToString(); + } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs index 674ac9d..2352e7e 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs @@ -213,6 +213,11 @@ public class WhereClause : IWhereClause return new WhereClause(clauses); } + public WhereClause Add(string column, string op, object value) + { + return Add(CompareWhereClause.Create(column, op, value)); + } + public WhereClause Eq(string column, object value) { return Add(CompareWhereClause.Eq(column, value)); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs index 1d8106c..bbb6efe 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Json.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using CrupestApi.Commons.Crud; using Microsoft.Extensions.Options; namespace CrupestApi.Commons; @@ -13,6 +14,10 @@ public static class CrupestApiJsonExtensions config.AllowTrailingCommas = true; config.PropertyNameCaseInsensitive = true; config.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + foreach (var converter in ColumnTypeInfoRegistry.Singleton.GetJsonConverters()) + { + config.Converters.Add(converter); + } }); return services; |