diff options
| author | crupest <crupest@outlook.com> | 2022-12-13 11:14:30 +0800 | 
|---|---|---|
| committer | crupest <crupest@outlook.com> | 2022-12-20 20:32:53 +0800 | 
| commit | e42b048abf6c97515686c42175c29191b1527dfd (patch) | |
| tree | 9d2c97e517a36644ac5069469249475a17310433 /docker | |
| parent | bc2a3db855cca16d4f66b23f814528671b5d8591 (diff) | |
| download | crupest-e42b048abf6c97515686c42175c29191b1527dfd.tar.gz crupest-e42b048abf6c97515686c42175c29191b1527dfd.tar.bz2 crupest-e42b048abf6c97515686c42175c29191b1527dfd.zip | |
Develop secret api. v33
Diffstat (limited to 'docker')
5 files changed, 159 insertions, 82 deletions
| diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs index 6e29de0..ae5081d 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs @@ -21,6 +21,7 @@ public class ColumnHooks      /// <summary>Called after SELECT. Please use multicast if you want to customize it because there are many default behavior in it.</summary>      /// <remarks> +    /// Called after column type transformation.      /// value(in):      ///     null => not found in SELECT result      ///     DbNullValue => database NULL @@ -33,6 +34,7 @@ public class ColumnHooks      /// <summary>Called before INSERT. Please use multicast if you want to customize it because there are many default behavior in it.</summary>      /// <remarks> +    /// Called before column type transformation.      /// value(in):      ///     null => not specified by insert clause      ///     DbNullValue => specified as database NULL @@ -45,6 +47,7 @@ public class ColumnHooks      /// <summary>Called before UPDATE. Please use multicast if you want to customize it because there are many default behavior in it.</summary       /// <remarks> +    /// Called before column type transformation.      /// value(in):      ///     null => not specified by update clause      ///     DbNullValue => specified as database NULL @@ -115,6 +118,7 @@ public class ColumnInfo      public bool IsNotNull => IsPrimaryKey || Metadata.GetValueOrDefault(ColumnMetadataKeys.NotNull) is true;      public bool IsGenerated => Metadata.GetValueOrDefault(ColumnMetadataKeys.Generated) is true;      public bool IsNoUpdate => Metadata.GetValueOrDefault(ColumnMetadataKeys.NoUpdate) is true; +    public bool CanBeGenerated => DefaultValueGeneratorMethod is not null;      /// <summary>      /// This only returns metadata value. It doesn't not fall back to primary column. If you want to get the real key column, go to table info.      /// </summary> @@ -122,7 +126,6 @@ public class ColumnInfo      /// <seealso cref="TableInfo.KeyColumn"/>      public bool IsSpecifiedAsKey => Metadata.GetValueOrDefault(ColumnMetadataKeys.ActAsKey) is true;      public ColumnIndexType Index => Metadata.GetValueOrDefault<ColumnIndexType?>(ColumnMetadataKeys.Index) ?? ColumnIndexType.None; -    public UpdateBehavior UpdateBehavior => Metadata.GetValueOrDefault<UpdateBehavior?>(ColumnMetadataKeys.UpdateBehavior) ?? UpdateBehavior.NullIsNotUpdate;      /// <summary>      /// The real column name. Maybe set in metadata or just the property name. @@ -160,53 +163,71 @@ public class ColumnInfo          }      } -    private void TryCoerceStringFromNullToEmpty(ref object? value) +    public MethodInfo ValidatorMethod      { -        if (ColumnType.ClrType == typeof(string) && (Metadata.GetValueOrDefault<bool?>(ColumnMetadataKeys.DefaultEmptyForString) is true) && value is DbNullValue) +        get          { -            value = ""; +            object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.Validator); +            Debug.Assert(value is null || value is string); +            if (value is null) +            { +                return GetType().GetMethod(nameof(DefaultValidator))!; +            } +            else +            { +                string methodName = (string)value; +                return Table.EntityType.GetMethod(methodName, BindingFlags.Static) ?? throw new Exception("The validator does not exist."); +            }          }      } -    protected void OnAfterSelect(ColumnInfo column, ref object? value) +    public void InvokeValidator(object value)      { -        TryCoerceStringFromNullToEmpty(ref value); +        ValidatorMethod.Invoke(null, new object?[] { this, value });      } -    protected void OnBeforeInsert(ColumnInfo column, ref object? value) +    public static void DefaultValidator(ColumnInfo column, object value)      { -        if (column.IsGenerated && value is not null) +        if (column.IsNotNull && value is DbNullValue)          { -            throw new Exception($"'{column.ColumnName}' can't be set manually. It is auto generated."); +            throw new Exception("The column can't be null.");          } +    } + +    public object? InvokeDefaultValueGenerator() +    { +        return DefaultValueGeneratorMethod?.Invoke(null, new object?[] { this }); +    } + +    public static object? DefaultDefaultValueGenerator(ColumnInfo column) +    { +        return DbNullValue.Instance; +    } -        var defaultValueGenerator = DefaultValueGeneratorMethod; -        if (defaultValueGenerator is not null && value is null) +    private void TryCoerceStringFromNullToEmpty(ref object? value) +    { +        if (ColumnType.ClrType == typeof(string) && (Metadata.GetValueOrDefault<bool?>(ColumnMetadataKeys.DefaultEmptyForString) is true) && value is DbNullValue)          { -            value = defaultValueGenerator.Invoke(null, null); +            value = "";          } +    } +    protected void OnAfterSelect(ColumnInfo column, ref object? value) +    {          TryCoerceStringFromNullToEmpty(ref value); +    } -        if (IsNotNull && (value is null || value is DbNullValue)) -        { -            throw new Exception($"'{column.ColumnName}' can't be null."); -        } +    protected void OnBeforeInsert(ColumnInfo column, ref object? value) +    { +        TryCoerceStringFromNullToEmpty(ref value);      }      protected void OnBeforeUpdate(ColumnInfo column, ref object? value)      { -        if (column.IsNoUpdate && value is not null) -        { -            throw new Exception($"'{column.ColumnName}' is not updatable."); -        } +        if (IsNoUpdate && value is not null) +            throw new Exception("The column can't be updated.");          TryCoerceStringFromNullToEmpty(ref value); - -        if (IsNotNull && value is DbNullValue) -        { -            throw new Exception($"'{column.ColumnName}' can't be null."); -        }      }      public string GenerateCreateTableColumnString(string? dbProviderId = null) diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs index c02f776..1082ea4 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs @@ -20,11 +20,18 @@ public static class ColumnMetadataKeys      /// <summary>      /// The default value generator method name in entity type. Default to null, aka, search for ColumnNameDefaultValueGenerator.  +    /// Generator has signature <code>static void DefaultValueGenerator(ColumnInfo column)</code>      /// </summary> -    /// <returns></returns>      public const string DefaultValueGenerator = nameof(ColumnAttribute.DefaultValueGenerator);      /// <summary> +    /// The validator method name in entity type. Default to null, aka, the default validator. +    /// Validator has signature <code>static void Validator(ColumnInfo column, object value)</code> +    /// Value param is never null. If you want to mean NULL, it should be a <see cref="DbNullValue"/>. +    /// </summary> +    public const string Validator = nameof(ColumnAttribute.Validator); + +    /// <summary>      /// The column can only be set when inserted, can't be changed in update.      /// </summary>      /// <returns></returns> @@ -34,24 +41,6 @@ public static class ColumnMetadataKeys      /// This column acts as key when get one entity for http get method in path.       /// </summary>      public const string ActAsKey = nameof(ColumnAttribute.ActAsKey); - -    /// <summary> -    /// Define what to do when update. -    /// </summary> -    public const string UpdateBehavior = nameof(ColumnAttribute.UpdateBehavior); -} - -[Flags] -public enum UpdateBehavior -{ -    /// <summary> -    /// Null value means do not update that column. -    /// </summary> -    NullIsNotUpdate = 0, -    /// <summary> -    /// Null value means set to null. -    /// </summary> -    NullIsSetNull = 1  }  public interface IColumnMetadata @@ -125,15 +114,15 @@ public class ColumnAttribute : Attribute, IColumnMetadata      /// <seealso cref="ColumnMetadataKeys.DefaultValueGenerator"/>      public string? DefaultValueGenerator { get; init; } +    /// <seealso cref="ColumnMetadataKeys.Validator"/> +    public string? Validator { get; init; } +      /// <seealso cref="ColumnMetadataKeys.NoUpdate"/>      public bool NoUpdate { get; init; }      /// <seealso cref="ColumnMetadataKeys.ActAsKey"/>      public bool ActAsKey { get; init; } -    /// <seealso cref="ColumnMetadataKeys.UpdateBehavior"> -    public UpdateBehavior UpdateBehavior { get; init; } = UpdateBehavior.NullIsNotUpdate; -      public bool TryGetValue(string key, out object? value)      {          var property = GetType().GetProperty(key); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs index 19208d8..bbbbb4c 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs @@ -53,4 +53,42 @@ public class EntityJsonHelper<TEntity> where TEntity : class          var dictionary = ConvertEntityToDictionary(entity);          return JsonSerializer.Serialize(dictionary, _jsonSerializerOptions);      } + +    public virtual TEntity ConvertDictionaryToEntityForInsert(IReadOnlyDictionary<string, object?> dictionary) +    { +        var result = Activator.CreateInstance<TEntity>()!; + +        foreach (var column in _table.PropertyColumns) +        { +            var propertyInfo = column.PropertyInfo!; +            var value = dictionary.GetValueOrDefault(column.ColumnName); +            if (column.IsGenerated) +            { +                if (value is not null) +                { +                    throw new UserException($"{propertyInfo.Name} is auto generated. Don't specify it."); +                } +            } + +            if (value is null) +            { +                if (column.IsNotNull && !column.CanBeGenerated) +                { +                    throw new UserException($"{propertyInfo.Name} can't be null."); +                } +                propertyInfo.SetValue(result, null); +            } +            else +            { +                // Check type +                var columnType = column.ColumnType; +                if (columnType.ClrType.IsAssignableFrom(value.GetType())) +                    propertyInfo.SetValue(result, value); +                else +                    throw new UserException($"{propertyInfo.Name} is of wrong type."); +            } +        } + +        return result; +    }  } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md index 589b0a8..22289cb 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md @@ -10,20 +10,33 @@ The ultimate CRUD scaffold finally comes.  1. Create select `what`, where clause, order clause, `Offset` and `Limit`.  2. Check clauses' related columns are valid. Then generate sql string and param list. -3. Convert param list to `Dapper` dynamic params. Execute sql and get `dynamic`s. -4. Run hook `AfterSelect` for every column. +3. Convert param list to `Dapper` dynamic params with proper type conversion in `IColumnTypeInfo`. Execute sql and get `dynamic`s. +4. For each column: +    1. If column not in query result is null, null will be used to call hooks. +    2. If column is `NULL`, `DbNullValue` will be used to call hooks. +    3. Otherwise run conversion in `IColumnTypeInfo`. +    4. Run hook `AfterSelect` for every column.  5. Convert `dynamic`s to `TEntity`s.  ### Insert  1. Create insert clause consisting of insert items.  2. Check clauses' related columns are valid. Then generate sql string and param list. -3. Run hook `BeforeInsert` for every column. -4. Convert param list to `Dapper` dynamic params. Execute sql and return `KeyColumn` value. +3. For each column: +    1. If insert item exits and value is not null but the column `IsGenerated` is true, throw exception. +    2. If insert item does not exist or value is `null` for that column, use default value generator to generate value. However, `DbNullValue` always means use `NULL` for that column. +    3. Coerce null to `DbNullValue`. +    4. Run hook `BeforeInsert`. +    5. Coerce null to `DbNullValue`. +    6. Run validator. +4. Convert param list to `Dapper` dynamic params with proper type conversion in `IColumnTypeInfo`. Execute sql and return `KeyColumn` value.  ### Update -1. Create update clause consisting of update items, where clause. +1. Create update clause consisted of update items, where clause.  2. Check clauses' related columns are valid. Then generate sql string and param list. -3. Run hook `BeforeUpdate` for every column. -4. Convert param list to `Dapper` dynamic params. Execute sql and get count of affected rows. +3. For each column: +    1. If insert item does not exist, `null` will be used to call hooks. However, `DbNullValue` always means use `NULL` for that column. +    2. Run hook `BeforeInsert`. If value is null, it means do not update this column. +    3. Run validator if `value` is not null. +4. Convert param list to `Dapper` dynamic params with proper type conversion in `IColumnTypeInfo`. Execute sql and get count of affected rows. diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs index b511b68..15b6320 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -431,7 +431,7 @@ CREATE TABLE {tableName}(                  else                  {                      value = dynamicProperty.GetValue(d); -                    if (value is null || value is DbNullValue) +                    if (value is null)                          value = DbNullValue.Instance;                      else                          value = column.ColumnType.ConvertFromDatabase(value); @@ -502,25 +502,44 @@ CREATE TABLE {tableName}(          foreach (var column in Columns)          {              InsertItem? item = insert.Items.SingleOrDefault(i => i.ColumnName == column.ColumnName); -            if (item is null) +            object? value; +            if (item is null || item.Value is null)              { -                object? value = null; -                column.Hooks.BeforeInsert(column, ref value); -                if (value is null || value is DbNullValue) -                    realInsert.Add(column.ColumnName, null); -                else -                    realInsert.Add(column.ColumnName, value); +                value = null;              }              else              { -                object? value = item.Value ?? DbNullValue.Instance; -                column.Hooks.BeforeInsert(column, ref value); -                if (value is null || value is DbNullValue) -                    realInsert.Add(column.ColumnName, null); -                else -                    realInsert.Add(column.ColumnName, value); +                value = item.Value; +            } + +            if (column.IsGenerated && value is not null) +            { +                throw new Exception("The column is generated. You can't specify it explicitly."); +            } + +            if (value is null) +            { +                value = column.InvokeDefaultValueGenerator(); +            } + +            if (value is null) +            { +                value = DbNullValue.Instance;              } +            column.Hooks.BeforeInsert(column, ref value); + +            if (value is null) +                value = DbNullValue.Instance; + +            column.InvokeValidator(value); + +            if (value is DbNullValue) +                realInsert.Add(column.ColumnName, null); +            else +                realInsert.Add(column.ColumnName, value); + +              if (item?.ColumnName == KeyColumn.ColumnName)              {                  key = item.Value; @@ -546,25 +565,22 @@ CREATE TABLE {tableName}(          {              UpdateItem? item = update.Items.FirstOrDefault(i => i.ColumnName == column.ColumnName); +            object? value;              if (item is null)              { -                object? value = null; -                column.Hooks.BeforeUpdate(column, ref value); -                if (value is not null) -                    if (value is DbNullValue) -                        realUpdate.Add(column.ColumnName, null); -                    else -                        realUpdate.Add(column.ColumnName, value); +                value = null;              }              else              { -                object? value = item.Value ?? DbNullValue.Instance; -                column.Hooks.BeforeUpdate(column, ref value); -                if (value is not null) -                    if (value is DbNullValue) -                        realUpdate.Add(column.ColumnName, null); -                    else -                        realUpdate.Add(column.ColumnName, value); +                value = item.Value ?? DbNullValue.Instance; +            } + +            column.Hooks.BeforeUpdate(column, ref value); + +            if (value is not null) +            { +                column.InvokeValidator(value); +                realUpdate.Add(column.ColumnName, value);              }          } | 
