aboutsummaryrefslogtreecommitdiff
path: root/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs
diff options
context:
space:
mode:
Diffstat (limited to 'dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs')
-rw-r--r--dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs628
1 files changed, 0 insertions, 628 deletions
diff --git a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs b/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs
deleted file mode 100644
index 4a7ea95..0000000
--- a/dropped/docker/crupest-api/CrupestApi/CrupestApi.Commons/Crud/TableInfo.cs
+++ /dev/null
@@ -1,628 +0,0 @@
-using System.Data;
-using System.Diagnostics;
-using System.Reflection;
-using System.Text;
-using Dapper;
-
-namespace CrupestApi.Commons.Crud;
-
-/// <summary>
-/// Contains all you need to manipulate a table.
-/// </summary>
-public class TableInfo
-{
- private readonly IColumnTypeProvider _columnTypeProvider;
- private readonly Lazy<List<string>> _lazyColumnNameList;
- private readonly ILoggerFactory _loggerFactory;
- private readonly ILogger<TableInfo> _logger;
-
- public TableInfo(Type entityType, IColumnTypeProvider columnTypeProvider, ILoggerFactory loggerFactory)
- : this(entityType.Name, entityType, columnTypeProvider, loggerFactory)
- {
- }
-
- public TableInfo(string tableName, Type entityType, IColumnTypeProvider columnTypeProvider, ILoggerFactory loggerFactory)
- {
- _loggerFactory = loggerFactory;
- _logger = loggerFactory.CreateLogger<TableInfo>();
-
- _logger.LogInformation("Create TableInfo for entity type '{}'.", entityType.Name);
-
- _columnTypeProvider = columnTypeProvider;
-
- TableName = tableName;
- EntityType = entityType;
-
-
- var properties = entityType.GetProperties();
- _logger.LogInformation("Find following properties: {}", string.Join(", ", properties.Select(p => p.Name)));
-
- var columnInfos = new List<ColumnInfo>();
-
- bool hasId = false;
- ColumnInfo? primaryKeyColumn = null;
- ColumnInfo? keyColumn = null;
-
- List<PropertyInfo> nonColumnProperties = new();
-
- foreach (var property in properties)
- {
- _logger.LogInformation("Check property '{}'.", property.Name);
- if (CheckPropertyIsColumn(property))
- {
- _logger.LogInformation("{} is a column, create ColumnInfo for it.", property.Name);
- var columnInfo = new ColumnInfo(this, property, _columnTypeProvider, _loggerFactory);
- columnInfos.Add(columnInfo);
- if (columnInfo.IsPrimaryKey)
- {
- _logger.LogInformation("Column {} is a primary key.", property.Name);
- primaryKeyColumn = columnInfo;
- }
- if (columnInfo.ColumnName.Equals("id", StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogInformation("Column {} has name id.", property.Name);
- hasId = true;
- }
- if (columnInfo.IsSpecifiedAsKey)
- {
- if (keyColumn is not null)
- {
- throw new Exception("Already exists a key column.");
- }
- _logger.LogInformation("Column {} is specified as key.", property.Name);
- keyColumn = columnInfo;
- }
- }
- else
- {
- _logger.LogInformation("{} is not a column.", property.Name);
- nonColumnProperties.Add(property);
- }
- }
-
- if (primaryKeyColumn is null)
- {
- if (hasId) throw new Exception("A column named id already exists but is not primary key.");
- _logger.LogInformation("No primary key column found, create one automatically.");
- primaryKeyColumn = CreateAutoIdColumn();
- columnInfos.Add(primaryKeyColumn);
- }
-
- if (keyColumn is null)
- {
- _logger.LogInformation("No key column is specified, will use primary key.");
- keyColumn = primaryKeyColumn;
- }
-
- Columns = columnInfos;
- PrimaryKeyColumn = primaryKeyColumn;
- KeyColumn = keyColumn;
- NonColumnProperties = nonColumnProperties;
-
- _logger.LogInformation("Check table validity.");
- CheckValidity();
-
- _logger.LogInformation("TableInfo succeeded to create.");
-
- _lazyColumnNameList = new Lazy<List<string>>(() => Columns.Select(c => c.ColumnName).ToList());
- }
-
- private ColumnInfo CreateAutoIdColumn()
- {
- return new ColumnInfo(this,
- new ColumnAttribute
- {
- ColumnName = "Id",
- NotNull = true,
- IsPrimaryKey = true,
- },
- typeof(long), _columnTypeProvider, _loggerFactory);
- }
-
- public Type EntityType { get; }
- public string TableName { get; }
- public IReadOnlyList<ColumnInfo> Columns { get; }
- public IReadOnlyList<ColumnInfo> PropertyColumns => Columns.Where(c => c.PropertyInfo is not null).ToList();
- public ColumnInfo PrimaryKeyColumn { get; }
- /// <summary>
- /// Maybe not the primary key. But acts as primary key.
- /// </summary>
- /// <seealso cref="ColumnMetadataKeys.ActAsKey"/>
- public ColumnInfo KeyColumn { get; }
- public IReadOnlyList<PropertyInfo> ColumnProperties => PropertyColumns.Select(c => c.PropertyInfo!).ToList();
- public IReadOnlyList<PropertyInfo> NonColumnProperties { get; }
- public IReadOnlyList<string> ColumnNameList => _lazyColumnNameList.Value;
-
- protected bool CheckPropertyIsColumn(PropertyInfo property)
- {
- var columnAttribute = property.GetCustomAttribute<ColumnAttribute>();
- if (columnAttribute is null) return false;
- return true;
- }
-
- public ColumnInfo GetColumn(string columnName)
- {
- foreach (var column in Columns)
- {
- if (column.ColumnName.Equals(columnName, StringComparison.OrdinalIgnoreCase))
- {
- return column;
- }
- }
- throw new KeyNotFoundException("No such column with given name.");
- }
-
- public void CheckGeneratedColumnHasGenerator()
- {
- foreach (var column in Columns)
- {
- if (column.IsOnlyGenerated && column.DefaultValueGeneratorMethod is null)
- {
- throw new Exception($"Column '{column.ColumnName}' is generated but has no generator.");
- }
- }
- }
-
- public void CheckValidity()
- {
- // Check if there is only one primary key.
- bool hasPrimaryKey = false;
- bool hasKey = false;
- foreach (var column in Columns)
- {
- if (column.IsPrimaryKey)
- {
- 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.");
-
- // Check two columns have the same sql name.
- HashSet<string> sqlNameSet = new HashSet<string>();
-
- foreach (var column in Columns)
- {
- if (sqlNameSet.Contains(column.ColumnName))
- throw new Exception($"Two columns have the same sql name '{column.ColumnName}'.");
- sqlNameSet.Add(column.ColumnName);
- }
-
- CheckGeneratedColumnHasGenerator();
- }
-
- public string GenerateCreateIndexSql(string? dbProviderId = null)
- {
- var sb = new StringBuilder();
-
- foreach (var column in Columns)
- {
- if (column.Index == ColumnIndexType.None) continue;
-
- sb.Append($"CREATE {(column.Index == ColumnIndexType.Unique ? "UNIQUE" : "")} INDEX {TableName}_{column.ColumnName}_index ON {TableName} ({column.ColumnName});\n");
- }
-
- return sb.ToString();
- }
-
- public string GenerateCreateTableSql(bool createIndex = true, string? dbProviderId = null)
- {
- var tableName = TableName;
- var columnSql = string.Join(",\n", Columns.Select(c => c.GenerateCreateTableColumnString(dbProviderId)));
-
- var sql = $@"
-CREATE TABLE {tableName}(
- {columnSql}
-);
- ";
-
- if (createIndex)
- {
- sql += GenerateCreateIndexSql(dbProviderId);
- }
-
- return sql;
- }
-
- public void CheckColumnName(string columnName)
- {
- if (!ColumnNameList.Contains(columnName))
- {
- throw new ArgumentException($"Column {columnName} is not in the table.");
- }
- }
-
- public void CheckRelatedColumns(IClause? clause)
- {
- if (clause is not null)
- {
- var relatedColumns = clause.GetRelatedColumns();
- foreach (var column in relatedColumns)
- {
- CheckColumnName(column);
- }
- }
- }
-
- /// <summary>
- /// If you call this manually, it's your duty to call hooks.
- /// </summary>
- /// <seealso cref="SelectDynamic"/>
- 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 ParamList();
-
- StringBuilder result = new StringBuilder()
- .Append($"SELECT {selectWhat ?? "*"} FROM ")
- .Append(TableName);
-
- if (whereClause is not null)
- {
- result.Append(" WHERE ");
- var (whereSql, whereParameters) = whereClause.GenerateSql(dbProviderId);
- parameters.AddRange(whereParameters);
- result.Append(whereSql);
- }
-
- if (orderByClause is not null)
- {
- result.Append(' ');
- var orderBySql = orderByClause.GenerateSql(dbProviderId);
- result.Append(orderBySql);
- }
-
- if (limit is not null)
- {
- result.Append(" LIMIT @Limit");
- parameters.Add("Limit", limit.Value);
- }
-
- if (skip is not null)
- {
- result.Append(" OFFSET @Skip");
- parameters.Add("Skip", skip.Value);
- }
-
- result.Append(';');
-
- return (result.ToString(), parameters);
- }
-
- /// <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 ParamList();
-
- var result = new StringBuilder()
- .Append("INSERT INTO ")
- .Append(TableName)
- .Append(" (")
- .Append(insertClause.GenerateColumnListSql(dbProviderId))
- .Append(") VALUES (");
-
- var (valueSql, valueParameters) = insertClause.GenerateValueListSql(dbProviderId);
- result.Append(valueSql).Append(");");
-
- parameters.AddRange(valueParameters);
-
- return (result.ToString(), parameters);
- }
-
- /// <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 ParamList();
-
- StringBuilder sb = new StringBuilder("UPDATE ");
- sb.Append(TableName);
- sb.Append(" SET ");
- var (updateSql, updateParameters) = updateClause.GenerateSql();
- sb.Append(updateSql);
- parameters.AddRange(updateParameters);
- if (whereClause is not null)
- {
- sb.Append(" WHERE ");
- var (whereSql, whereParameters) = whereClause.GenerateSql();
- sb.Append(whereSql);
- parameters.AddRange(whereParameters);
- }
- sb.Append(';');
-
- return (sb.ToString(), parameters);
- }
-
- /// <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 ParamList();
-
- StringBuilder sb = new StringBuilder("DELETE FROM ");
- sb.Append(TableName);
- if (whereClause is not null)
- {
- sb.Append(" WHERE ");
- var (whereSql, whereParameters) = whereClause.GenerateSql();
- parameters.AddRange(whereParameters);
- sb.Append(whereSql);
- }
- sb.Append(';');
-
- return (sb.ToString(), parameters);
- }
-
- private DynamicParameters ConvertParameters(ParamList parameters)
- {
- var result = new DynamicParameters();
- foreach (var param in parameters)
- {
- if (param.Value is null || param.Value is DbNullValue)
- {
- result.Add(param.Name, null);
- continue;
- }
-
- 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;
- }
-
- /// <summary>
- /// ConvertParameters. Select. Call hooks.
- /// </summary>
- public virtual List<dynamic> SelectDynamic(IDbConnection dbConnection, string? what = null, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null)
- {
- var (sql, parameters) = GenerateSelectSql(what, where, orderBy, skip, limit);
- var queryResult = dbConnection.Query<dynamic>(sql, ConvertParameters(parameters));
- return queryResult.ToList();
- }
-
- public virtual int SelectCount(IDbConnection dbConnection, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null)
- {
- var (sql, parameters) = GenerateSelectSql("COUNT(*)", where, orderBy, skip, limit);
- var result = dbConnection.QuerySingle<int>(sql, ConvertParameters(parameters));
- return result;
- }
-
- public virtual TResult MapDynamicTo<TResult>(dynamic d)
- {
- var dict = (IDictionary<string, object?>)d;
-
- var result = Activator.CreateInstance<TResult>();
- Type resultType = typeof(TResult);
-
- foreach (var column in Columns)
- {
- var resultProperty = resultType.GetProperty(column.ColumnName);
- if (dict.ContainsKey(column.ColumnName) && resultProperty is not null)
- {
- if (dict[column.ColumnName] is null)
- {
- resultProperty.SetValue(result, null);
- continue;
- }
- object? value = Convert.ChangeType(dict[column.ColumnName], column.ColumnType.DatabaseClrType);
- value = column.ColumnType.ConvertFromDatabase(value);
- resultProperty.SetValue(result, value);
- }
- }
-
- return result;
- }
-
- /// <summary>
- /// Select and call hooks.
- /// </summary>
- public virtual List<TResult> Select<TResult>(IDbConnection dbConnection, string? what = null, IWhereClause? where = null, IOrderByClause? orderBy = null, int? skip = null, int? limit = null)
- {
- List<dynamic> queryResult = SelectDynamic(dbConnection, what, where, orderBy, skip, limit).ToList();
-
- return queryResult.Select(MapDynamicTo<TResult>).ToList();
- }
-
- public IInsertClause ConvertEntityToInsertClause(object entity)
- {
- Debug.Assert(EntityType.IsInstanceOfType(entity));
- var result = new InsertClause();
- foreach (var column in PropertyColumns)
- {
- var value = column.PropertyInfo!.GetValue(entity);
- result.Add(column.ColumnName, value);
- }
- return result;
- }
-
- /// <summary>
- /// Insert a entity and call hooks.
- /// </summary>
- /// <returns>The key of insert entity.</returns>
- public int Insert(IDbConnection dbConnection, IInsertClause insert, out object key)
- {
- object? finalKey = null;
-
- var realInsert = InsertClause.Create();
-
- foreach (var column in Columns)
- {
- InsertItem? item = insert.Items.SingleOrDefault(i => i.ColumnName == column.ColumnName);
-
- var value = item?.Value;
-
- if (column.IsOnlyGenerated && value is not null)
- {
- throw new Exception($"The column '{column.ColumnName}' is auto generated. You can't specify it explicitly.");
- }
-
- if (value is null)
- {
- value = column.GenerateDefaultValue();
- }
-
- if (value is null && column.IsAutoIncrement)
- {
- continue;
- }
-
- if (value is null)
- {
- value = DbNullValue.Instance;
- }
-
- column.InvokeValidator(value);
-
- InsertItem realInsertItem;
-
- if (value is DbNullValue)
- {
- if (column.IsNotNull)
- {
- throw new Exception($"Column '{column.ColumnName}' is not nullable. Please specify a non-null value.");
- }
-
- realInsertItem = new InsertItem(column.ColumnName, null);
- }
- else
- {
- realInsertItem = new InsertItem(column.ColumnName, value);
- }
-
- realInsert.Add(realInsertItem);
-
- if (realInsertItem.ColumnName == KeyColumn.ColumnName)
- {
- finalKey = realInsertItem.Value;
- }
- }
-
- if (finalKey is null) throw new Exception("No key???");
- key = finalKey;
-
- var (sql, parameters) = GenerateInsertSql(realInsert);
-
- var affectedRowCount = dbConnection.Execute(sql, ConvertParameters(parameters));
-
- if (affectedRowCount != 1)
- throw new Exception("Failed to insert.");
-
- return affectedRowCount;
- }
-
- /// <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, out object? newKey)
- {
- newKey = null;
-
- var realUpdate = UpdateClause.Create();
-
- foreach (var column in Columns)
- {
- UpdateItem? item = update.Items.FirstOrDefault(i => i.ColumnName == column.ColumnName);
- object? value = item?.Value;
-
- if (value is not null)
- {
- if (column.IsNoUpdate)
- {
- throw new Exception($"The column '{column.ColumnName}' can't be update.");
- }
-
- column.InvokeValidator(value);
-
- realUpdate.Add(column.ColumnName, value);
-
- if (column.ColumnName == KeyColumn.ColumnName)
- {
- newKey = value;
- }
- }
- }
-
- var (sql, parameters) = GenerateUpdateSql(where, realUpdate);
- return dbConnection.Execute(sql, ConvertParameters(parameters));
- }
-
- public virtual int Delete(IDbConnection dbConnection, IWhereClause? where)
- {
- var (sql, parameters) = GenerateDeleteSql(where);
- return dbConnection.Execute(sql, ConvertParameters(parameters));
- }
-}
-
-public interface ITableInfoFactory
-{
- TableInfo Get(Type type);
-}
-
-public class TableInfoFactory : ITableInfoFactory
-{
- private readonly Dictionary<Type, TableInfo> _cache = new Dictionary<Type, TableInfo>();
- private readonly IColumnTypeProvider _columnTypeProvider;
- private readonly ILoggerFactory _loggerFactory;
- private readonly ILogger<TableInfoFactory> _logger;
-
- public TableInfoFactory(IColumnTypeProvider columnTypeProvider, ILoggerFactory loggerFactory)
- {
- _columnTypeProvider = columnTypeProvider;
- _loggerFactory = loggerFactory;
- _logger = loggerFactory.CreateLogger<TableInfoFactory>();
- }
-
- // This is thread-safe.
- public TableInfo Get(Type type)
- {
- lock (_cache)
- {
- if (_cache.TryGetValue(type, out var tableInfo))
- {
- _logger.LogDebug("Table info of type '{}' is cached, return it.", type.Name);
- return tableInfo;
- }
- else
- {
- _logger.LogDebug("Table info for type '{}' is not in cache, create it.", type.Name);
- tableInfo = new TableInfo(type, _columnTypeProvider, _loggerFactory);
- _logger.LogDebug("Table info for type '{}' is created, add it to cache.", type.Name);
- _cache.Add(type, tableInfo);
- return tableInfo;
- }
- }
- }
-}