diff options
| author | crupest <crupest@outlook.com> | 2022-12-11 10:43:56 +0800 | 
|---|---|---|
| committer | crupest <crupest@outlook.com> | 2022-12-20 20:32:53 +0800 | 
| commit | bd523a6a7cac09fe580223c3d75e41e1e100f603 (patch) | |
| tree | 8dcc9fd28c2b7281eb40fa143e96c70518aeefd8 /docker/crupest-api | |
| parent | c53adadcbf93a3b5c1f9c8e2b88bdd0efb122709 (diff) | |
| download | crupest-bd523a6a7cac09fe580223c3d75e41e1e100f603.tar.gz crupest-bd523a6a7cac09fe580223c3d75e41e1e100f603.tar.bz2 crupest-bd523a6a7cac09fe580223c3d75e41e1e100f603.zip  | |
Develop secret api. v26
Diffstat (limited to 'docker/crupest-api')
11 files changed, 271 insertions, 192 deletions
diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs index 545397d..800594d 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnInfo.cs @@ -6,6 +6,9 @@ namespace CrupestApi.Commons.Crud;  public class ColumnHooks  { +    /// <summary> +    /// If value is null, then it might because the column does not designated a value or it is designated null. +    /// </summary>      public delegate void ColumnHookAction(ColumnInfo column, ref object? value);      public ColumnHooks(ColumnHookAction afterSelect, ColumnHookAction beforeInsert, ColumnHookAction beforeUpdate) @@ -15,13 +18,13 @@ public class ColumnHooks          BeforeUpdate = beforeUpdate;      } -    // Called after SELECT. +    /// <summary>Called after SELECT. Please use multicast if you want to customize it because there are many default behavior in it.</summary       public ColumnHookAction AfterSelect; -    // Called before INSERT. +    /// <summary>Called before INSERT. Please use multicast if you want to customize it because there are many default behavior in it.</summary       public ColumnHookAction BeforeInsert; -    // Called before UPDATE +    /// <summary>Called before UPDATE. Please use multicast if you want to customize it because there are many default behavior in it.</summary       public ColumnHookAction BeforeUpdate;  } @@ -29,6 +32,9 @@ public class ColumnInfo  {      private readonly AggregateColumnMetadata _metadata = new AggregateColumnMetadata(); +    /// <summary> +    /// Initialize a column without corresponding property. +    /// </summary>      public ColumnInfo(TableInfo table, IColumnMetadata metadata, Type clrType, IColumnTypeProvider typeProvider)      {          Table = table; @@ -42,6 +48,9 @@ public class ColumnInfo          );      } +    /// <summary> +    /// Initialize a column with corresponding property. +    /// </summary>      public ColumnInfo(TableInfo table, PropertyInfo propertyInfo, IColumnTypeProvider typeProvider)      {          Table = table; @@ -62,6 +71,7 @@ public class ColumnInfo      }      public TableInfo Table { get; } +      // If null, there is no corresponding property.      public PropertyInfo? PropertyInfo { get; } = null; @@ -71,6 +81,56 @@ public class ColumnInfo      public ColumnHooks Hooks { get; } +    public bool IsPrimaryKey => Metadata.GetValueOrDefault(ColumnMetadataKeys.IsPrimaryKey) is true; +    public bool IsAutoIncrement => Metadata.GetValueOrDefault(ColumnMetadataKeys.IsAutoIncrement) is true; +    public bool IsNotNull => IsPrimaryKey || Metadata.GetValueOrDefault(ColumnMetadataKeys.NotNull) is true; +    public bool IsClientGenerate => Metadata.GetValueOrDefault(ColumnMetadataKeys.ClientGenerate) is true; +    public bool IsNoUpdate => Metadata.GetValueOrDefault(ColumnMetadataKeys.NoUpdate) is true; +    /// <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> +    /// <seealso cref="ColumnMetadataKeys.ActAsKey"/> +    /// <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. +    /// </summary> +    /// <value></value> +    public string ColumnName +    { +        get +        { +            object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.ColumnName); +            Debug.Assert(value is null || value is string); +            return ((string?)value ?? PropertyInfo?.Name) ?? throw new Exception("Failed to get column name."); +        } +    } + +    public MethodInfo? DefaultValueGeneratorMethod +    { +        get +        { +            object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.DefaultValueGenerator); +            Debug.Assert(value is null || value is string); +            MethodInfo? result; +            if (value is null) +            { +                string methodName = ColumnName + "DefaultValueGenerator"; +                result = Table.EntityType.GetMethod(methodName, BindingFlags.Static); +            } +            else +            { +                string methodName = (string)value; +                result = Table.EntityType.GetMethod(methodName, BindingFlags.Static) ?? throw new Exception("The default value generator does not exist."); +            } + +            return result; +        } +    } +      private void TryCoerceStringFromNullToEmpty(ref object? value)      {          if (ColumnType.ClrType == typeof(string) && (Metadata.GetValueOrDefault<bool?>(ColumnMetadataKeys.DefaultEmptyForString) ?? false) && value is null) @@ -88,85 +148,39 @@ public class ColumnInfo      {          if (column.IsClientGenerate && value is not null)          { -            throw new Exception($"Column {column.ColumnName} can't be set manually."); -        } - -        var defaultValueGeneratorMethod = DefaultValueGeneratorMethod; -        if (defaultValueGeneratorMethod is not null) -        { -            value = defaultValueGeneratorMethod.Invoke(null, new object[] { }); +            throw new UserException($"'{column.ColumnName}' can't be set manually. It is auto generated.");          } +        DefaultValueGeneratorMethod?.Invoke(null, new object[] { });          OnBeforeSet(column, ref value);      }      protected void OnBeforeUpdate(ColumnInfo column, ref object? value)      { -        OnBeforeSet(column, ref value); -          if (column.IsNoUpdate)          { -            throw new Exception($"Column {column.ColumnName} not updatable."); +            throw new UserException($"'{column.ColumnName}' is not updatable.");          } -    } - -    protected void OnBeforeSet(ColumnInfo column, ref object? value) -    { -        TryCoerceStringFromNullToEmpty(ref value); -        if (value is null && column.IsNotNull) +        if ((column.UpdateBehavior & UpdateBehavior.NullIsSetNull) != 0 && value is null)          { -            throw new Exception($"Column {column.ColumnName} can't be null."); +            value = DbNullValue.Instance;          } -    } -    public string ColumnName -    { -        get -        { -            object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.ColumnName); -            Debug.Assert(value is null || value is string); -            return ((string?)value ?? PropertyInfo?.Name) ?? throw new Exception("Failed to get column name."); -        } +        OnBeforeSet(column, ref value);      } -    public MethodInfo? DefaultValueGeneratorMethod +    protected void OnBeforeSet(ColumnInfo column, ref object? value)      { -        get -        { -            object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.DefaultValueGenerator); -            Debug.Assert(value is null || value is string); -            MethodInfo? result; -            if (value is null) -            { -                string methodName = ColumnName + "DefaultValueGenerator"; -                result = Table.EntityType.GetMethod(methodName, BindingFlags.Static); -            } -            else -            { -                string methodName = (string)value; -                result = Table.EntityType.GetMethod(methodName, BindingFlags.Static) ?? throw new Exception("The default value generator does not exist."); -            } +        TryCoerceStringFromNullToEmpty(ref value); -            return result; +        if (value is null && column.IsNotNull) +        { +            throw new UserException($"{column.ColumnName} can't be null.");          }      } -    public bool IsPrimaryKey => Metadata.GetValueOrDefault(ColumnMetadataKeys.IsPrimaryKey) is true; -    public bool IsAutoIncrement => Metadata.GetValueOrDefault(ColumnMetadataKeys.IsAutoIncrement) is true; -    public bool IsNotNull => IsPrimaryKey || Metadata.GetValueOrDefault(ColumnMetadataKeys.NotNull) is true; -    public bool IsClientGenerate => Metadata.GetValueOrDefault(ColumnMetadataKeys.ClientGenerate) is true; -    public bool IsNoUpdate => Metadata.GetValueOrDefault(ColumnMetadataKeys.NoUpdate) is true; -    /// <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> -    /// <seealso cref="ColumnMetadataKeys.ActAsKey"/> -    /// <seealso cref="TableInfo.KeyColumn"/> -    public bool IsSpecifiedAsKey => Metadata.GetValueOrDefault(ColumnMetadataKeys.ActAsKey) is true; - -    public ColumnIndexType Index => Metadata.GetValueOrDefault<ColumnIndexType?>(ColumnMetadataKeys.Index) ?? ColumnIndexType.None; -      public string GenerateCreateTableColumnString(string? dbProviderId = null)      {          StringBuilder result = new StringBuilder(); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs index 9fb3999..e7c74f3 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ColumnMetadata.cs @@ -34,6 +34,24 @@ 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 @@ -98,22 +116,23 @@ public class ColumnAttribute : Attribute, IColumnMetadata      // default None      public ColumnIndexType Index { get; init; } = ColumnIndexType.None; -    /// <see cref="ColumnMetadataKeys.DefaultEmptyForString"/> +    /// <seealso cref="ColumnMetadataKeys.DefaultEmptyForString"/>      public bool DefaultEmptyForString { get; init; } -    /// <see cref="ColumnMetadataKeys.ClientGenerate"/> +    /// <seealso cref="ColumnMetadataKeys.ClientGenerate"/>      public bool ClientGenerate { get; init; } -    /// <see cref="ColumnMetadataKeys.DefaultValueGenerator"/> +    /// <seealso cref="ColumnMetadataKeys.DefaultValueGenerator"/>      public string? DefaultValueGenerator { get; init; } -    /// <see cref="ColumnMetadataKeys.NoUpdate"/> +    /// <seealso cref="ColumnMetadataKeys.NoUpdate"/>      public bool NoUpdate { get; init; } -    /// <see cref="ColumnMetadataKeys.ActAsKey"/> +    /// <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)      { diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DbNullValue.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DbNullValue.cs new file mode 100644 index 0000000..5dc5a61 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DbNullValue.cs @@ -0,0 +1,9 @@ +namespace CrupestApi.Commons.Crud; + +/// <summary> +/// This will always represent null value in database. +/// </summary> +public class DbNullValue +{ +    public static DbNullValue Instance { get; } = new DbNullValue(); +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DynamicParametersExtensions.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DynamicParametersExtensions.cs deleted file mode 100644 index 956206d..0000000 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/DynamicParametersExtensions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Data; -using System.Diagnostics; -using Dapper; - -namespace CrupestApi.Commons.Crud; - -public static class DynamicParametersExtensions -{ -    private static Random random = new Random(); -    private const string chars = "abcdefghijklmnopqrstuvwxyz"; - -    public static string GenerateRandomKey(int length) -    { -        lock (random) -        { -            var result = new string(Enumerable.Repeat(chars, length) -                .Select(s => s[random.Next(s.Length)]).ToArray()); -            return result; -        } -    } - -    public static string GenerateRandomParameterName(DynamicParameters parameters) -    { -        var parameterName = GenerateRandomKey(10); -        int retryTimes = 1; -        while (parameters.ParameterNames.Contains(parameterName)) -        { -            retryTimes++; -            Debug.Assert(retryTimes <= 100); -            parameterName = GenerateRandomKey(10); -        } -        return parameterName; -    } - -    public static string AddRandomNameParameter(this DynamicParameters parameters, object? value) -    { -        var parameterName = GenerateRandomParameterName(parameters); -        parameters.Add(parameterName, value); -        return parameterName; -    } -} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/InsertClause.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/InsertClause.cs index b5f9f38..7f248cf 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/InsertClause.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/InsertClause.cs @@ -19,7 +19,7 @@ public interface IInsertClause : IClause  {      List<InsertItem> Items { get; }      string GenerateColumnListSql(string? dbProviderId = null); -    (string sql, DynamicParameters parameters) GenerateValueListSql(string? dbProviderId = null); +    (string sql, ParamList parameters) GenerateValueListSql(string? dbProviderId = null);  }  public class InsertClause : IInsertClause @@ -57,14 +57,14 @@ public class InsertClause : IInsertClause          return string.Join(", ", Items.Select(i => i.ColumnName));      } -    public (string sql, DynamicParameters parameters) GenerateValueListSql(string? dbProviderId = null) +    public (string sql, ParamList parameters) GenerateValueListSql(string? dbProviderId = null)      { -        var parameters = new DynamicParameters(); +        var parameters = new ParamList();          var sb = new StringBuilder();          for (var i = 0; i < Items.Count; i++)          {              var item = Items[i]; -            var parameterName = parameters.AddRandomNameParameter(item.Value); +            var parameterName = parameters.AddRandomNameParameter(item.Value, item.ColumnName);              sb.Append($"@{parameterName}");              if (i != Items.Count - 1)                  sb.Append(", "); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/InternalException.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/InternalException.cs new file mode 100644 index 0000000..1a10b97 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/InternalException.cs @@ -0,0 +1,15 @@ +namespace CrupestApi.Commons.Crud; + +/// <summary> +/// This exception means the exception is caused by user and can be safely shown to user. +/// </summary> +[System.Serializable] +public class UserException : Exception +{ +    public UserException() { } +    public UserException(string message) : base(message) { } +    public UserException(string message, System.Exception inner) : base(message, inner) { } +    protected UserException( +        System.Runtime.Serialization.SerializationInfo info, +        System.Runtime.Serialization.StreamingContext context) : base(info, context) { } +}
\ No newline at end of file diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/OrderByClause.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/OrderByClause.cs index 68b5d60..a1aaa45 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/OrderByClause.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/OrderByClause.cs @@ -22,7 +22,7 @@ public class OrderByItem  public interface IOrderByClause : IClause  {      List<OrderByItem> Items { get; } -    (string sql, DynamicParameters parameters) GenerateSql(string? dbProviderId = null); +    (string sql, ParamList parameters) GenerateSql(string? dbProviderId = null);  }  public class OrderByClause : IOrderByClause @@ -44,8 +44,8 @@ public class OrderByClause : IOrderByClause          return Items.Select(x => x.ColumnName).ToList();      } -    public (string sql, DynamicParameters parameters) GenerateSql(string? dbProviderId = null) +    public (string sql, ParamList parameters) GenerateSql(string? dbProviderId = null)      { -        return ("ORDER BY " + string.Join(", ", Items.Select(i => i.GenerateSql())), new DynamicParameters()); +        return ("ORDER BY " + string.Join(", ", Items.Select(i => i.GenerateSql())), new ParamList());      }  } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ParamMap.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ParamMap.cs new file mode 100644 index 0000000..4b253b7 --- /dev/null +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/ParamMap.cs @@ -0,0 +1,62 @@ +using System.Data; +using System.Diagnostics; + +namespace CrupestApi.Commons.Crud; + +public record ParamInfo(string Name, object? Value, string? ColumnName = null); + +public class ParamList : List<ParamInfo> +{ +    private static Random random = new Random(); +    private const string chars = "abcdefghijklmnopqrstuvwxyz"; +    public static string GenerateRandomKey(int length) +    { +        lock (random) +        { +            var result = new string(Enumerable.Repeat(chars, length) +                .Select(s => s[random.Next(s.Length)]).ToArray()); +            return result; +        } +    } + +    public string GenerateRandomParameterName() +    { +        var parameterName = GenerateRandomKey(10); +        int retryTimes = 1; +        while (ContainsKey(parameterName)) +        { +            retryTimes++; +            Debug.Assert(retryTimes <= 100); +            parameterName = GenerateRandomKey(10); +        } +        return parameterName; +    } + + +    public bool ContainsKey(string name) +    { +        return this.SingleOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) is not null; +    } + +    public object? this[string key] +    { +        get +        { +            return this.SingleOrDefault(p => p.Name.Equals(key, StringComparison.OrdinalIgnoreCase)) ?? throw new KeyNotFoundException("Key not found."); +        } +    } + +    public void Add(string name, object? value, string? columnName = null) +    { +        Add(new ParamInfo(name, value, columnName)); +    } + +    // Return the random name. +    public string AddRandomNameParameter(object? value, string? columnName = null) +    { +        var parameterName = GenerateRandomParameterName(); +        var param = new ParamInfo(parameterName, value, columnName); +        Add(param); +        return parameterName; +    } +} diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs index 498529c..93d02fd 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs @@ -136,13 +136,19 @@ public class TableInfo      {          // Check if there is only one primary key.          bool hasPrimaryKey = false; +        bool hasKey = false;          foreach (var column in ColumnInfos)          {              if (column.IsPrimaryKey)              { -                if (hasPrimaryKey) throw new Exception("Two columns are primary key."); +                if (hasPrimaryKey) throw new Exception("More than one columns are primary key.");                  hasPrimaryKey = true;              } + +            if (column.IsSpecifiedAsKey) +            { +                if (hasKey) throw new Exception("More than one columns are specified as key column."); +            }          }          if (!hasPrimaryKey) throw new Exception("No column is primary key."); @@ -231,22 +237,26 @@ CREATE TABLE {tableName}(          }      } -    public (string sql, DynamicParameters parameters) GenerateSelectSql(string? what, IWhereClause? whereClause, IOrderByClause? orderByClause = null, int? skip = null, int? limit = null, string? dbProviderId = null) +    /// <summary> +    /// If you call this manually, it's your duty to call hooks. +    /// </summary> +    /// <seealso cref="Select"/> +    public (string sql, ParamList parameters) GenerateSelectSql(string? selectWhat, IWhereClause? whereClause, IOrderByClause? orderByClause = null, int? skip = null, int? limit = null, string? dbProviderId = null)      {          CheckRelatedColumns(whereClause);          CheckRelatedColumns(orderByClause); -        var parameters = new DynamicParameters(); +        var parameters = new ParamList();          StringBuilder result = new StringBuilder() -            .Append($"SELECT {what ?? "*"} FROM ") +            .Append($"SELECT {selectWhat ?? "*"} FROM ")              .Append(TableName);          if (whereClause is not null)          {              result.Append(' ');              var (whereSql, whereParameters) = whereClause.GenerateSql(dbProviderId); -            parameters.AddDynamicParams(whereParameters); +            parameters.AddRange(whereParameters);              result.Append(whereSql);          } @@ -254,7 +264,7 @@ CREATE TABLE {tableName}(          {              result.Append(' ');              var (orderBySql, orderByParameters) = orderByClause.GenerateSql(dbProviderId); -            parameters.AddDynamicParams(orderByClause); +            parameters.AddRange(orderByParameters);              result.Append(orderBySql);          } @@ -275,49 +285,15 @@ CREATE TABLE {tableName}(          return (result.ToString(), parameters);      } -    public InsertClause GenerateInsertClauseFromEntity(object entity) -    { -        Debug.Assert(EntityType.IsInstanceOfType(entity)); - -        var insertClause = InsertClause.Create(); - -        foreach (var column in ColumnInfos) -        { -            var propertyInfo = column.PropertyInfo; -            if (propertyInfo is null) -            { -                if (column.IsAutoIncrement) -                { -                    continue; -                } -                else -                { -                    throw new Exception($"Property {column.ColumnName} not found."); -                } -            } - -            var propertyValue = propertyInfo.GetValue(entity); -            if (propertyValue is null) -            { -                if (column.IsAutoIncrement) -                { -                    continue; -                } -                else -                { -                    insertClause.Add(column.ColumnName, propertyValue); -                } -            } -        } - -        return insertClause; -    } - -    public (string sql, DynamicParameters parameters) GenerateInsertSql(IInsertClause insertClause, string? dbProviderId = null) +    /// <summary> +    /// If you call this manually, it's your duty to call hooks. +    /// </summary> +    /// <seealso cref="Insert"/> +    public (string sql, ParamList parameters) GenerateInsertSql(IInsertClause insertClause, string? dbProviderId = null)      {          CheckRelatedColumns(insertClause); -        var parameters = new DynamicParameters(); +        var parameters = new ParamList();          var result = new StringBuilder()              .Append("INSERT INTO ") @@ -329,41 +305,49 @@ CREATE TABLE {tableName}(          var (valueSql, valueParameters) = insertClause.GenerateValueListSql(dbProviderId);          result.Append(valueSql).Append(");"); -        parameters.AddDynamicParams(valueParameters); +        parameters.AddRange(valueParameters);          return (result.ToString(), parameters);      } -    public (string sql, DynamicParameters parameters) GenerateUpdateSql(IWhereClause? whereClause, IUpdateClause updateClause) +    /// <summary> +    /// If you call this manually, it's your duty to call hooks. +    /// </summary> +    /// <seealso cref="Update"/> +    public (string sql, ParamList parameters) GenerateUpdateSql(IWhereClause? whereClause, IUpdateClause updateClause)      {          CheckRelatedColumns(whereClause);          CheckRelatedColumns(updateClause); -        var parameters = new DynamicParameters(); +        var parameters = new ParamList();          StringBuilder sb = new StringBuilder("UPDATE ");          sb.Append(TableName);          sb.Append(" SET ");          var (updateSql, updateParameters) = updateClause.GenerateSql();          sb.Append(updateSql); -        parameters.AddDynamicParams(updateParameters); +        parameters.AddRange(updateParameters);          if (whereClause is not null)          {              sb.Append(" WHERE ");              var (whereSql, whereParameters) = whereClause.GenerateSql();              sb.Append(whereSql); -            parameters.AddDynamicParams(whereParameters); +            parameters.AddRange(whereParameters);          }          sb.Append(';');          return (sb.ToString(), parameters);      } -    public (string sql, DynamicParameters parameters) GenerateDeleteSql(IWhereClause? whereClause) +    /// <summary> +    /// If you call this manually, it's your duty to call hooks. +    /// </summary> +    /// <seealso cref="Delete"/> +    public (string sql, ParamList parameters) GenerateDeleteSql(IWhereClause? whereClause)      {          CheckRelatedColumns(whereClause); -        var parameters = new DynamicParameters(); +        var parameters = new ParamList();          StringBuilder sb = new StringBuilder("DELETE FROM ");          sb.Append(TableName); @@ -371,7 +355,7 @@ CREATE TABLE {tableName}(          {              sb.Append(" WHERE ");              var (whereSql, whereParameters) = whereClause.GenerateSql(); -            parameters.AddDynamicParams(whereParameters); +            parameters.AddRange(whereParameters);              sb.Append(whereSql);          }          sb.Append(';'); @@ -379,19 +363,29 @@ CREATE TABLE {tableName}(          return (sb.ToString(), parameters);      } -    private DynamicParameters ConvertParameters(DynamicParameters parameters) +    private DynamicParameters ConvertParameters(ParamList parameters)      {          var result = new DynamicParameters(); -        foreach (var paramName in parameters.ParameterNames) +        foreach (var param in parameters)          { -            var value = parameters.Get<object?>(paramName); -            if (value is null) +            if (param.Value is null || param.Value is DbNullValue)              { -                result.Add(paramName, null); +                result.Add(param.Name, null);                  continue;              } -            var typeInfo = _columnTypeProvider.Get(value.GetType()); -            result.Add(paramName, typeInfo.ConvertToDatabase(value)); + +            var columnName = param.ColumnName; +            IColumnTypeInfo typeInfo; +            if (columnName is not null) +            { +                typeInfo = GetColumn(columnName).ColumnType; +            } +            else +            { +                typeInfo = _columnTypeProvider.Get(param.Value.GetType()); +            } + +            result.Add(param.Name, typeInfo.ConvertToDatabase(param.Value), typeInfo.DbType);          }          return result;      } @@ -408,6 +402,9 @@ CREATE TABLE {tableName}(          return Select<IEnumerable<object?>>(dbConnection, null, where, orderBy, skip, limit);      } +    /// <summary> +    /// Select and call hooks. +    /// </summary>      public virtual IEnumerable<TResult> Select<TResult>(IDbConnection dbConnection, string? what, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null)      {          var (sql, parameters) = GenerateSelectSql(what, where, orderBy, skip, limit); @@ -422,9 +419,9 @@ CREATE TABLE {tableName}(                  object? value = null;                  var dynamicProperty = dynamicType.GetProperty(column.ColumnName);                  if (dynamicProperty is not null) value = dynamicProperty.GetValue(d); -                column.Hooks.AfterSelect(column, ref value);                  if (value is not null)                      value = column.ColumnType.ConvertFromDatabase(value); +                column.Hooks.AfterSelect(column, ref value);                  var propertyInfo = column.PropertyInfo;                  if (propertyInfo is not null)                  { @@ -436,7 +433,10 @@ CREATE TABLE {tableName}(          });      } -    // Returns the insert entity's key. +    /// <summary> +    /// Insert a entity and call hooks. +    /// </summary> +    /// <returns>The key of insert entity.</returns>      public object Insert(IDbConnection dbConnection, IInsertClause insert)      {          object? key = null; @@ -468,6 +468,10 @@ CREATE TABLE {tableName}(          return key ?? throw new Exception("No key???");      } +    /// <summary> +    /// Upgrade a entity and call hooks. +    /// </summary> +    /// <returns>The key of insert entity.</returns>      public virtual int Update(IDbConnection dbConnection, IWhereClause? where, IUpdateClause update)      {          var (sql, parameters) = GenerateUpdateSql(where, update); diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/UpdateClause.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/UpdateClause.cs index b9cafee..de5c6c3 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/UpdateClause.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/UpdateClause.cs @@ -1,5 +1,4 @@  using System.Text; -using Dapper;  namespace CrupestApi.Commons.Crud; @@ -18,7 +17,7 @@ public class UpdateItem  public interface IUpdateClause : IClause  {      List<UpdateItem> Items { get; } -    (string sql, DynamicParameters parameters) GenerateSql(); +    (string sql, ParamList parameters) GenerateSql();  }  public class UpdateClause : IUpdateClause @@ -56,9 +55,9 @@ public class UpdateClause : IUpdateClause          return Items.Select(i => i.ColumnName).ToList();      } -    public (string sql, DynamicParameters parameters) GenerateSql() +    public (string sql, ParamList parameters) GenerateSql()      { -        var parameters = new DynamicParameters(); +        var parameters = new ParamList();          StringBuilder result = new StringBuilder(); @@ -69,7 +68,7 @@ public class UpdateClause : IUpdateClause                  result.Append(", ");              } -            var parameterName = parameters.AddRandomNameParameter(item.Value); +            var parameterName = parameters.AddRandomNameParameter(item.Value, item.ColumnName);              result.Append($"{item.ColumnName} = @{parameterName}");          } diff --git a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs index 98fe49d..bf3d8b1 100644 --- a/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs +++ b/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/WhereClause.cs @@ -1,12 +1,10 @@ -using System.Data;  using System.Text; -using Dapper;  namespace CrupestApi.Commons.Crud;  public interface IWhereClause : IClause  { -    (string sql, DynamicParameters parameters) GenerateSql(string? dbProviderId = null); +    (string sql, ParamList parameters) GenerateSql(string? dbProviderId = null);  }  public class CompositeWhereClause : IWhereClause @@ -28,12 +26,12 @@ public class CompositeWhereClause : IWhereClause          return this;      } -    public (string sql, DynamicParameters parameters) GenerateSql(string? dbProviderId = null) +    public (string sql, ParamList parameters) GenerateSql(string? dbProviderId = null)      { -        var parameters = new DynamicParameters(); +        var parameters = new ParamList();          var sql = new StringBuilder();          var subclauses = GetSubclauses(); -        if (subclauses is null) return ("", parameters); +        if (subclauses is null) return ("", new());          var first = true;          foreach (var subclause in Subclauses)          { @@ -56,7 +54,7 @@ public class CompositeWhereClause : IWhereClause              {                  sql.Append(")");              } -            parameters.AddDynamicParams(subParameters); +            parameters.AddRange(subParameters);          }          return (sql.ToString(), parameters);      } @@ -162,10 +160,10 @@ public class SimpleCompareWhereClause : IWhereClause          return new SimpleCompareWhereClause(column, "<=", value);      } -    public (string sql, DynamicParameters parameters) GenerateSql(string? dbProviderId = null) +    public (string sql, ParamList parameters) GenerateSql(string? dbProviderId = null)      { -        var parameters = new DynamicParameters(); -        var parameterName = parameters.AddRandomNameParameter(Value); +        var parameters = new ParamList(); +        var parameterName = parameters.AddRandomNameParameter(Value, Column);          return ($"{Column} {Operator} @{parameterName}", parameters);      }  }  | 
