diff options
Diffstat (limited to 'docker/crupest-api/CrupestApi/CrupestApi.Commons')
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."); } |