diff options
Diffstat (limited to 'docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud')
4 files changed, 68 insertions, 77 deletions
| diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs index e91c777..aa3e4f8 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs @@ -50,7 +50,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 => (bool?)Metadata.GetValueOrDefault(ColumnMetadataKeys.CanBeGenerated) ?? (DefaultValueGeneratorMethod is not null); +    public bool CanBeGenerated => (bool?)Metadata.GetValueOrDefault(ColumnMetadataKeys.CanBeGenerated) ?? (DefaultValueGeneratorMethod is not null || IsAutoIncrement);      /// <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> diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs index 6119396..7db843b 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/EntityJsonHelper.cs @@ -12,35 +12,33 @@ namespace CrupestApi.Commons.Crud;  public class EntityJsonHelper<TEntity> where TEntity : class  {      private readonly TableInfo _table; -    private readonly JsonSerializerOptions _jsonSerializerOptions; +    private readonly IOptionsMonitor<JsonSerializerOptions> _jsonSerializerOptions; -    public EntityJsonHelper(TableInfoFactory tableInfoFactory) +    public EntityJsonHelper(TableInfoFactory tableInfoFactory, IOptionsMonitor<JsonSerializerOptions> jsonSerializerOptions)      {          _table = tableInfoFactory.Get(typeof(TEntity)); -        _jsonSerializerOptions = new JsonSerializerOptions(); -        _jsonSerializerOptions.AllowTrailingCommas = true; -        _jsonSerializerOptions.PropertyNameCaseInsensitive = true; -        _jsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; -        foreach (var type in _table.Columns.Select(c => c.ColumnType)) -        { -            if (type.JsonConverter is not null) -            { -                _jsonSerializerOptions.Converters.Add(type.JsonConverter); -            } -        } +        _jsonSerializerOptions = jsonSerializerOptions;      } -    public virtual Dictionary<string, object?> ConvertEntityToDictionary(object? entity) +    public virtual Dictionary<string, object?> ConvertEntityToDictionary(object? entity, bool includeNonColumnProperties = false)      {          Debug.Assert(entity is null || entity is TEntity);          var result = new Dictionary<string, object?>(); -        foreach (var column in _table.PropertyColumns) +        foreach (var propertyInfo in _table.ColumnProperties)          { -            var propertyInfo = column.PropertyInfo; -            var value = propertyInfo!.GetValue(entity); -            result[column.ColumnName] = value; +            var value = propertyInfo.GetValue(entity); +            result[propertyInfo.Name] = value; +        } + +        if (includeNonColumnProperties) +        { +            foreach (var propertyInfo in _table.NonColumnProperties) +            { +                var value = propertyInfo.GetValue(entity); +                result[propertyInfo.Name] = value; +            }          }          return result; @@ -51,50 +49,53 @@ public class EntityJsonHelper<TEntity> where TEntity : class          Debug.Assert(entity is null || entity is TEntity);          var dictionary = ConvertEntityToDictionary(entity); -        return JsonSerializer.Serialize(dictionary, _jsonSerializerOptions); +        return JsonSerializer.Serialize(dictionary, _jsonSerializerOptions.CurrentValue);      } -    public virtual TEntity ConvertDictionaryToEntityForInsert(IReadOnlyDictionary<string, object?> dictionary) +    public virtual IInsertClause ConvertJsonElementToInsertClauses(JsonElement rootElement)      { -        var result = Activator.CreateInstance<TEntity>()!; +        var insertClause = InsertClause.Create(); + +        if (rootElement.ValueKind != JsonValueKind.Object) +        { +            throw new UserException("The root element must be an object."); +        }          foreach (var column in _table.PropertyColumns)          { -            var propertyInfo = column.PropertyInfo!; -            var value = dictionary.GetValueOrDefault(column.ColumnName); -            if (column.IsGenerated) +            object? value = null; +            if (rootElement.TryGetProperty(column.ColumnName, out var propertyElement))              { -                if (value is not null) +                value = propertyElement.ValueKind switch                  { -                    throw new UserException($"{propertyInfo.Name} is auto generated. Don't specify it."); -                } +                    JsonValueKind.Null or JsonValueKind.Undefined => null, +                    JsonValueKind.Number => propertyElement.GetDouble(), +                    JsonValueKind.True => true, +                    JsonValueKind.False => false, +                    JsonValueKind.String => propertyElement.GetString(), +                    _ => throw new Exception($"Bad json value of property {column.ColumnName}.") +                };              } -            if (value is null) +            if (column.IsGenerated && value is not null)              { -                if (column.IsNotNull && !column.CanBeGenerated) -                { -                    throw new UserException($"{propertyInfo.Name} can't be null."); -                } -                propertyInfo.SetValue(result, null); +                throw new UserException($"The property {column.ColumnName} is generated. You cannot specify its value.");              } -            else + +            if (column.IsNotNull && !column.CanBeGenerated && value is null)              { -                // 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."); +                throw new UserException($"The property {column.ColumnName} can't be null or generated. But you specify a null value.");              } + +            insertClause.Add(column.ColumnName, value);          } -        return result; +        return insertClause;      } -    public TEntity ConvertJsonToEntityForInsert(string json) +    public IInsertClause ConvertJsonToEntityForInsert(string json)      { -        var dictionary = JsonSerializer.Deserialize<Dictionary<string, object?>>(json, _jsonSerializerOptions)!; -        return ConvertDictionaryToEntityForInsert(dictionary); +        var document = JsonSerializer.Deserialize<JsonDocument>(json, _jsonSerializerOptions.CurrentValue)!; +        return ConvertJsonElementToInsertClauses(document.RootElement);      }  } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md index ca8a6f1..b008ea7 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/README.md @@ -19,24 +19,29 @@ The ultimate CRUD scaffold finally comes.  1. Create insert clause.  2. Check clauses' related columns are valid. -3. For each column: +3. Create a real empty insert clause. +4. For each column:      1. If insert item exists and value is not null but the column `IsGenerated` is true, throw exception.      2. If insert item does not exist or value is `null`, use default value generator to generate value. However, `DbNullValue` always means use `NULL` for that column. -    3. Coerce null to `DbNullValue`. -    4. Run validator to validate the value. -    5. If value is `DbNullValue`, `IsNotNull` is true and `IsAutoIncrement` is false, throw exception. -4. Generate sql string and param list. -5. Convert param list to `Dapper` dynamic params with proper type conversion in `IColumnTypeInfo`. -6. Execute sql and return `KeyColumn` value. +    3. If value is `null` and the column `IsAutoIncrement` is true, skip to next column. +    4. Coerce null to `DbNullValue`. +    5. Run validator to validate the value. +    6. If value is `DbNullValue`, `IsNotNull` is true, throw exception. +    7. Add column and value to real insert clause. +5. Generate sql string and param list. +6. Convert param list to `Dapper` dynamic params with proper type conversion in `IColumnTypeInfo`. +7. Execute sql and return `KeyColumn` value.  ### Update  1. Create update clause, where clause.  2. Check clauses' related columns are valid. Then generate sql string and param list. -3. For each column: +3. Create a real empty update clause. +4. For each column:      1. If update item exists and value is not null but the column `IsNoUpdate` is true, throw exception.      2. Invoke validator to validate the value.      3. If `IsNotNull` is true and value is `DbNullValue`, throw exception. -4. Generate sql string and param list. -5. Convert param list to `Dapper` dynamic params with proper type conversion in `IColumnTypeInfo`. -6. Execute sql and return count of affected rows. +    4. Add column and value to real update clause. +5. Generate sql string and param list. +6. Convert param list to `Dapper` dynamic params with proper type conversion in `IColumnTypeInfo`. +7. Execute sql and return 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 869e987..b552e6b 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -285,25 +285,6 @@ CREATE TABLE {tableName}(          return (result.ToString(), parameters);      } -    public void CheckInsertClause(IInsertClause insertClause) -    { -        var columnNameSet = new HashSet<string>(Columns.Select(c => c.ColumnName)); - -        foreach (var item in insertClause.Items) -        { -            columnNameSet.Remove(item.ColumnName); -        } - -        foreach (var columnName in columnNameSet) -        { -            var column = GetColumn(columnName); -            if (!column.IsAutoIncrement) -            { -                throw new Exception($"Column {columnName} is not specified and is not auto increment."); -            } -        } -    } -      /// <summary>      /// If you call this manually, it's your duty to call hooks.      /// </summary> @@ -311,7 +292,6 @@ CREATE TABLE {tableName}(      public (string sql, ParamList parameters) GenerateInsertSql(IInsertClause insertClause, string? dbProviderId = null)      {          CheckRelatedColumns(insertClause); -        CheckInsertClause(insertClause);          var parameters = new ParamList(); @@ -484,6 +464,11 @@ CREATE TABLE {tableName}(                  value = column.InvokeDefaultValueGenerator();              } +            if (value is null && column.IsAutoIncrement) +            { +                continue; +            } +              if (value is null)              {                  value = DbNullValue.Instance; @@ -493,7 +478,7 @@ CREATE TABLE {tableName}(              if (value is DbNullValue)              { -                if (column.IsNotNull && !column.IsAutoIncrement) +                if (column.IsNotNull)                  {                      throw new Exception($"Column '{column.ColumnName}' is not nullable. Please specify a non-null value.");                  } | 
