Initial_commit_SecMPS_v2
This commit is contained in:
@@ -0,0 +1,438 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Text.RegularExpressions;
|
||||
using VolPro.Core.Const;
|
||||
using VolPro.Core.DBManager;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Enums;
|
||||
using VolPro.Core.Utilities;
|
||||
|
||||
namespace VolPro.Builder.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// SQL 表结构信息
|
||||
/// </summary>
|
||||
public class SqlColumnSchemaInfo
|
||||
{
|
||||
/// <summary>字段名</summary>
|
||||
public string ColumnName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>备注(说明)</summary>
|
||||
/// <remarks>对任意 SQL 结果集,通常无法获取扩展属性/注释,此处可能为空</remarks>
|
||||
public string Comment { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>长度/精度</summary>
|
||||
public int? Length { get; set; }
|
||||
|
||||
/// <summary>C# 类型(如 String、Int32、Decimal 等)</summary>
|
||||
public string DataType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>数据库字段类型(如 nvarchar、int、decimal 等)</summary>
|
||||
public string DbDataType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>小数位数(decimal/numeric 等)</summary>
|
||||
public int? Scale { get; set; }
|
||||
|
||||
/// <summary>是否可为空</summary>
|
||||
public bool IsNullable { get; set; } = true;
|
||||
|
||||
/// <summary>列序号</summary>
|
||||
public int Ordinal { get; set; }
|
||||
}
|
||||
public static class SqlSchemaHelper
|
||||
{
|
||||
public static (bool IsValid, string ErrorMessage) IsValidSql(string sql, string dbServer = "SysDbContext")
|
||||
{
|
||||
var dbType = DbRelativeCache.GetDbType(dbServer);
|
||||
var sanitized = sql;
|
||||
|
||||
if (ContainsForbiddenKeywords(sanitized, dbType, out var keyword))
|
||||
{
|
||||
return (false, $"sql包含禁止操作:{keyword}");
|
||||
}
|
||||
|
||||
if (ContainsDbSpecificForbiddenPatterns(sanitized, dbType, out var pattern))
|
||||
{
|
||||
return (false, $"sql包含禁止模式:{pattern}");
|
||||
}
|
||||
if (ContainsSysUserUserPwdReference(sanitized))
|
||||
{
|
||||
return (false, "禁止查询 `sys_user`.`UserPwd` 字段。");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(sql))
|
||||
return (true, string.Empty);
|
||||
var connection = GetDbConnection(dbServer);
|
||||
if (connection.State == ConnectionState.Closed)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
try
|
||||
{
|
||||
using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
using var reader = cmd.ExecuteReader(CommandBehavior.SchemaOnly);
|
||||
return (true, string.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (false, $"sql执行未通过,请检查sql,{ex.InnerException?.Message ?? ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ContainsForbiddenKeywords(string statement, string dbType, out string keyword)
|
||||
{
|
||||
keyword = string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(statement)) return false;
|
||||
// 全字匹配:SQL 任意位置出现关键字即拦截
|
||||
var forbiddenWords = new (string Word, string Label)[]
|
||||
{
|
||||
("delete", "DELETE"),
|
||||
("drop", "DROP"),
|
||||
("truncate", "TRUNCATE"),
|
||||
("alter", "ALTER"),
|
||||
("create", "CREATE"),
|
||||
("grant", "GRANT"),
|
||||
("revoke", "REVOKE"),
|
||||
("call", "CALL"),
|
||||
("exec", "EXEC"),
|
||||
("execute", "EXECUTE"),
|
||||
("commit", "COMMIT"),
|
||||
("rollback", "ROLLBACK"),
|
||||
("use", "USE"),
|
||||
("into", "INTO")
|
||||
};
|
||||
|
||||
foreach (var (word, label) in forbiddenWords)
|
||||
{
|
||||
if (Regex.IsMatch(statement, $@"\b{Regex.Escape(word)}\b", RegexOptions.IgnoreCase))
|
||||
{
|
||||
keyword = label;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// MySQL:SELECT ... INTO OUTFILE|DUMPFILE(导出文件/写出)
|
||||
if (string.Equals(dbType, "MySql", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Regex.IsMatch(statement, @"\binto\s+(?:out|dump)file\b", RegexOptions.IgnoreCase))
|
||||
{
|
||||
keyword = "INTO OUTFILE/DUMPFILE";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ContainsSysUserUserPwdReference(string statement)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(statement)) return false;
|
||||
bool hasSysUser = Regex.IsMatch(statement, @"\bsys_user\b", RegexOptions.IgnoreCase);
|
||||
if (!hasSysUser) return false;
|
||||
|
||||
bool hasUserPwd =
|
||||
Regex.IsMatch(statement, @"\bUserPwd\b", RegexOptions.IgnoreCase)
|
||||
|| Regex.IsMatch(statement, @"\[`UserPwd`\]", RegexOptions.IgnoreCase)
|
||||
|| Regex.IsMatch(statement, @"\[\s*UserPwd\s*\]", RegexOptions.IgnoreCase)
|
||||
|| Regex.IsMatch(statement, @"""UserPwd""", RegexOptions.IgnoreCase);
|
||||
|
||||
return hasUserPwd;
|
||||
}
|
||||
|
||||
private static bool ContainsDbSpecificForbiddenPatterns(string statement, string dbType, out string pattern)
|
||||
{
|
||||
pattern = string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(statement)) return false;
|
||||
|
||||
if (string.Equals(dbType, "MsSql", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(dbType, "PgSql", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// SELECT ... INTO <table> ... (创建表语义)
|
||||
// SQL Server:允许临时表 INTO #temp;拒绝其它 into 创建表
|
||||
// PostgreSQL:允许 INTO TEMP|TEMPORARY;拒绝其它 into 创建表
|
||||
if (string.Equals(dbType, "MsSql", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Regex.IsMatch(statement,
|
||||
@"\bselect\b[\s\S]*?\binto\b\s+(?!#)[^\s]+\s+\bfrom\b",
|
||||
RegexOptions.IgnoreCase))
|
||||
{
|
||||
pattern = "SELECT ... INTO table (non-temp)";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Regex.IsMatch(statement,
|
||||
@"\bselect\b[\s\S]*?\binto\b\s+(?!temp(?:orary)?\b)[^\s]+\s+\bfrom\b",
|
||||
RegexOptions.IgnoreCase))
|
||||
{
|
||||
pattern = "SELECT ... INTO table";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(dbType, "PgSql", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// COPY FROM/COPY ... 会修改数据或导入,禁止
|
||||
if (Regex.IsMatch(statement, @"\bcopy\b", RegexOptions.IgnoreCase))
|
||||
{
|
||||
pattern = "COPY";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(dbType, "Oracle", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static IDbConnection GetDbConnection(string dbServer = "SysDbContext")
|
||||
{
|
||||
var type = DbRelativeCache.GetDbContextType(dbServer);
|
||||
object dbObj = HttpContext.Current.RequestServices.GetService(type);
|
||||
BaseDbContext context = (BaseDbContext)dbObj;
|
||||
var connection = context.SqlSugarClient.Ado.Connection;
|
||||
return connection;
|
||||
}
|
||||
public static List<SqlColumnSchemaInfo> GetSchemaFromSql(string sql, string dbServer = "SysDbContext")
|
||||
{
|
||||
List<SqlColumnSchemaInfo> list = new List<SqlColumnSchemaInfo>();
|
||||
var connection = GetDbConnection(dbServer);
|
||||
if (connection.State == ConnectionState.Closed)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
try
|
||||
{
|
||||
using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = sql;
|
||||
using var reader = cmd.ExecuteReader(CommandBehavior.SchemaOnly);
|
||||
if (reader == null) return list;
|
||||
|
||||
var schemaTable = reader.GetSchemaTable();
|
||||
if (schemaTable == null || schemaTable.Rows.Count == 0)
|
||||
return list;
|
||||
|
||||
int ordinal = 0;
|
||||
foreach (DataRow row in schemaTable.Rows)
|
||||
{
|
||||
var info = RowToColumnInfo(row, reader, ordinal++);
|
||||
if (info != null)
|
||||
list.Add(info);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception(ex.Message, ex.InnerException);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection.State == ConnectionState.Open)
|
||||
connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private static SqlColumnSchemaInfo RowToColumnInfo(DataRow row, IDataReader reader, int ordinal)
|
||||
{
|
||||
var colName = GetRowValue<string>(row, "ColumnName")
|
||||
?? GetRowValue<string>(row, "BaseColumnName");
|
||||
|
||||
var dataType = GetRowValue<Type>(row, "DataType");
|
||||
var netTypeName = dataType?.Name ?? GetRowValue<string>(row, "DataTypeName") ?? string.Empty;
|
||||
|
||||
var dbDataType = string.Empty;
|
||||
if (reader != null && ordinal >= 0 && ordinal < reader.FieldCount)
|
||||
{
|
||||
try { dbDataType = reader.GetDataTypeName(ordinal) ?? string.Empty; }
|
||||
catch { }
|
||||
}
|
||||
if (string.IsNullOrEmpty(dbDataType))
|
||||
dbDataType = GetRowValue<string>(row, "DataTypeName") ?? string.Empty;
|
||||
|
||||
var length = GetRowValue<int?>(row, "ColumnSize");
|
||||
var precision = GetRowValue<int?>(row, "NumericPrecision");
|
||||
var scale = GetRowValue<int?>(row, "NumericScale");
|
||||
|
||||
if (length.HasValue && length.Value <= 0)
|
||||
length = null;
|
||||
if (dataType != null && (netTypeName.Contains("Decimal", StringComparison.OrdinalIgnoreCase) || netTypeName.Contains("Numeric", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
length = precision;
|
||||
}
|
||||
|
||||
var allowNull = true;
|
||||
if (row.Table.Columns.Contains("AllowDBNull"))
|
||||
{
|
||||
var allowNullVal = row["AllowDBNull"];
|
||||
if (allowNullVal != null && allowNullVal != DBNull.Value)
|
||||
allowNull = Convert.ToBoolean(allowNullVal);
|
||||
}
|
||||
|
||||
var csharpTypeName = DbTypeToCSharpType(dbDataType, dataType, netTypeName);
|
||||
|
||||
return new SqlColumnSchemaInfo
|
||||
{
|
||||
ColumnName = colName ?? string.Empty,
|
||||
Comment = string.Empty,
|
||||
Length = length,
|
||||
Scale = scale,
|
||||
DataType = csharpTypeName,
|
||||
DbDataType = dbDataType,
|
||||
IsNullable = allowNull,
|
||||
Ordinal = ordinal
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将数据库字段类型转换为 C# 类型名称(如 int、string、DateTime、decimal 等)
|
||||
/// 参照 Sys_TableInfoService 的 GetSqlServerStructure/GetMySqlStructure/GetPgSqlStructure 映射
|
||||
/// </summary>
|
||||
private static string DbTypeToCSharpType(string dbDataType, Type netType, string netTypeName)
|
||||
{
|
||||
var dbLower = (dbDataType ?? string.Empty).Trim().ToLowerInvariant();
|
||||
if (!string.IsNullOrEmpty(dbLower))
|
||||
{
|
||||
// SQL Server、MySQL、PostgreSQL 等统一映射
|
||||
switch (dbLower)
|
||||
{
|
||||
case "uniqueidentifier":
|
||||
case "uuid":
|
||||
return "guid";
|
||||
case "bit":
|
||||
case "bool":
|
||||
case "boolean":
|
||||
return "bool";
|
||||
case "tinyint":
|
||||
return "byte";
|
||||
case "smallint":
|
||||
case "int2":
|
||||
return "short";
|
||||
case "int":
|
||||
case "integer":
|
||||
case "mediumint":
|
||||
case "int4":
|
||||
case "year":
|
||||
return "int";
|
||||
case "bigint":
|
||||
case "int8":
|
||||
return "long";
|
||||
case "real":
|
||||
case "float4":
|
||||
return "float";
|
||||
case "float":
|
||||
case "double":
|
||||
case "float8":
|
||||
return "double";
|
||||
case "decimal":
|
||||
case "numeric":
|
||||
case "money":
|
||||
case "smallmoney":
|
||||
return "decimal";
|
||||
case "time":
|
||||
return "TimeSpan";
|
||||
case "date":
|
||||
case "datetime":
|
||||
case "datetime2":
|
||||
case "smalldatetime":
|
||||
case "timestamp":
|
||||
return "DateTime";
|
||||
case "char":
|
||||
case "varchar":
|
||||
case "nvarchar":
|
||||
case "nchar":
|
||||
case "text":
|
||||
case "ntext":
|
||||
case "xml":
|
||||
case "varbinary":
|
||||
case "image":
|
||||
case "tinytext":
|
||||
case "mediumtext":
|
||||
case "longtext":
|
||||
case "tinyblob":
|
||||
case "blob":
|
||||
case "mediumblob":
|
||||
case "longblob":
|
||||
case "bytea":
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
// 无 DbDataType 或未匹配时,按 .NET Type 映射
|
||||
if (netType != null)
|
||||
{
|
||||
if (netType == typeof(bool) || netType == typeof(bool?)) return "bool";
|
||||
if (netType == typeof(byte) || netType == typeof(byte?)) return "byte";
|
||||
if (netType == typeof(short) || netType == typeof(short?)) return "short";
|
||||
if (netType == typeof(int) || netType == typeof(int?)) return "int";
|
||||
if (netType == typeof(long) || netType == typeof(long?)) return "long";
|
||||
if (netType == typeof(float) || netType == typeof(float?)) return "float";
|
||||
if (netType == typeof(double) || netType == typeof(double?)) return "double";
|
||||
if (netType == typeof(decimal) || netType == typeof(decimal?)) return "decimal";
|
||||
if (netType == typeof(DateTime) || netType == typeof(DateTime?)) return "DateTime";
|
||||
if (netType == typeof(TimeSpan) || netType == typeof(TimeSpan?)) return "TimeSpan";
|
||||
if (netType == typeof(Guid) || netType == typeof(Guid?)) return "guid";
|
||||
if (netType == typeof(string)) return "string";
|
||||
if (netType == typeof(byte[])) return "byte[]";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(netTypeName))
|
||||
{
|
||||
if (netTypeName.IndexOf("Boolean", StringComparison.OrdinalIgnoreCase) >= 0) return "bool";
|
||||
if (netTypeName.IndexOf("Byte", StringComparison.OrdinalIgnoreCase) >= 0) return "byte";
|
||||
if (netTypeName.IndexOf("Int16", StringComparison.OrdinalIgnoreCase) >= 0) return "short";
|
||||
if (netTypeName.IndexOf("Int32", StringComparison.OrdinalIgnoreCase) >= 0) return "int";
|
||||
if (netTypeName.IndexOf("Int64", StringComparison.OrdinalIgnoreCase) >= 0) return "long";
|
||||
if (netTypeName.IndexOf("Single", StringComparison.OrdinalIgnoreCase) >= 0) return "float";
|
||||
if (netTypeName.IndexOf("Double", StringComparison.OrdinalIgnoreCase) >= 0) return "double";
|
||||
if (netTypeName.IndexOf("Decimal", StringComparison.OrdinalIgnoreCase) >= 0 || netTypeName.IndexOf("Numeric", StringComparison.OrdinalIgnoreCase) >= 0) return "decimal";
|
||||
if (netTypeName.IndexOf("DateTime", StringComparison.OrdinalIgnoreCase) >= 0) return "DateTime";
|
||||
if (netTypeName.IndexOf("TimeSpan", StringComparison.OrdinalIgnoreCase) >= 0) return "TimeSpan";
|
||||
if (netTypeName.IndexOf("Guid", StringComparison.OrdinalIgnoreCase) >= 0) return "guid";
|
||||
if (netTypeName.IndexOf("String", StringComparison.OrdinalIgnoreCase) >= 0) return "string";
|
||||
}
|
||||
|
||||
return "string";
|
||||
}
|
||||
|
||||
private static T GetRowValue<T>(DataRow row, string columnName)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName))
|
||||
return default;
|
||||
|
||||
var val = row[columnName];
|
||||
if (val == null || val == DBNull.Value)
|
||||
return default;
|
||||
|
||||
var targetType = typeof(T);
|
||||
if (targetType == typeof(string))
|
||||
return (T)(object)(val?.ToString() ?? string.Empty);
|
||||
if (targetType == typeof(Type))
|
||||
return val is Type t ? (T)(object)t : default;
|
||||
if (targetType == typeof(int?) || targetType == typeof(int))
|
||||
{
|
||||
try { return (T)(object)Convert.ToInt32(val); }
|
||||
catch { return default; }
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
return (T)Convert.ChangeType(val, underlyingType);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
using VolPro.Builder.IRepositories;
|
||||
using VolPro.Builder.IServices;
|
||||
using VolPro.Core.BaseProvider;
|
||||
using VolPro.Core.Extensions.AutofacManager;
|
||||
using VolPro.Entity.DomainModels;
|
||||
|
||||
namespace VolPro.Builder.Services
|
||||
{
|
||||
public partial class Sys_TableInfoService : ServiceBase<Sys_TableInfo, ISys_TableInfoRepository>, ISys_TableInfoService, IDependency
|
||||
{
|
||||
public Sys_TableInfoService(ISys_TableInfoRepository repository)
|
||||
: base(repository)
|
||||
{
|
||||
Init(repository);
|
||||
}
|
||||
public static ISys_TableInfoService Instance
|
||||
{
|
||||
get { return AutofacContainerModule.GetService<ISys_TableInfoService>(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
public class CreateTableRequest
|
||||
{
|
||||
[Required]
|
||||
public string TableName { get; set; } = string.Empty;
|
||||
|
||||
public List<TableColumnDto> Columns { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
public class TableColumnDto
|
||||
{
|
||||
public string ColumnName { get; set; } = string.Empty;
|
||||
public string DataType { get; set; } = "nvarchar";
|
||||
public int? Length { get; set; }
|
||||
// decimal/numeric 的小数位(scale)
|
||||
public int? Scale { get; set; }
|
||||
public bool IsNullable { get; set; } = true;
|
||||
public bool IsPrimaryKey { get; set; } = false;
|
||||
public bool IsIdentity { get; set; } = false;
|
||||
public string Comment { get; set; }
|
||||
public int Order { get; set; }
|
||||
|
||||
public string DefaultValue { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// 数据库字段(true=来自数据库,false=新增)
|
||||
/// </summary>
|
||||
public bool IsDbField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原始字段名(用于区分重命名与删除:重命名时 OriginalColumnName 为旧名)
|
||||
/// </summary>
|
||||
public string OriginalColumnName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
public class TableInfoDto
|
||||
{
|
||||
public string TableName { get; set; } = string.Empty;
|
||||
public List<TableColumnDto> Columns { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
public class UpdateTableRequest
|
||||
{
|
||||
[Required]
|
||||
public string TableName { get; set; } = string.Empty;
|
||||
|
||||
public List<TableColumnDto> Columns { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using VolPro.Builder.IServices;
|
||||
using VolPro.Core.EFDbContext;
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
/// <summary>
|
||||
/// MySQL 数据库表结构操作实现。
|
||||
/// </summary>
|
||||
public class MySqlTableProvider : ITableDatabaseProvider
|
||||
{
|
||||
private readonly BaseDbContext _context;
|
||||
|
||||
public MySqlTableProvider(BaseDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<bool> TableExistsAsync(string tableName)
|
||||
{
|
||||
const string mysqlSql = @"SELECT COUNT(*) as Value
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @tableName;";
|
||||
var res = await _context.SqlSugarClient.Ado.GetScalarAsync(mysqlSql, new { tableName });
|
||||
return Convert.ToInt32(res) > 0;
|
||||
}
|
||||
|
||||
public async Task CreateTableAsync(CreateTableRequest request)
|
||||
{
|
||||
await CreateTableMySqlAsync(request);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetAllTablesAsync()
|
||||
{
|
||||
const string mysqlSql = @"
|
||||
SELECT TABLE_NAME as Value
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
ORDER BY TABLE_NAME";
|
||||
var rows = await _context.SqlSugarClient.Ado.SqlQueryAsync<string>(mysqlSql);
|
||||
return rows ?? new List<string>();
|
||||
}
|
||||
|
||||
public async Task<TableInfoDto> GetTableInfoAsync(string tableName)
|
||||
{
|
||||
return await GetTableInfoMySqlAsync(tableName);
|
||||
}
|
||||
|
||||
public async Task UpdateTableAsync(UpdateTableRequest request)
|
||||
{
|
||||
await UpdateTableMySqlAsync(request);
|
||||
}
|
||||
|
||||
public async Task DeleteTableAsync(string tableName)
|
||||
{
|
||||
var dropMySql = $"DROP TABLE `{tableName.Replace("`", "``")}`;";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(dropMySql);
|
||||
}
|
||||
|
||||
private static string NormalizeMySqlDefault(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
||||
var s = raw.Trim();
|
||||
// MySQL BIT 类型默认值格式为 b'1' 或 b'0',统一返回 1 或 0
|
||||
if (s.StartsWith("b'", StringComparison.OrdinalIgnoreCase) && s.EndsWith("'") && s.Length >= 4)
|
||||
{
|
||||
var inner = s[2..^1];
|
||||
return inner == "1" ? "1" : inner == "0" ? "0" : inner;
|
||||
}
|
||||
if (s.Length >= 2 && s.StartsWith("'") && s.EndsWith("'"))
|
||||
return s[1..^1].Replace("''", "'");
|
||||
return s;
|
||||
}
|
||||
|
||||
private static string FormatDefaultForMySql(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return string.Empty;
|
||||
var v = value.Trim();
|
||||
var vLower = v.ToLower();
|
||||
if (vLower is "current_timestamp" or "now()" or "now(6)")
|
||||
return $" DEFAULT {v}";
|
||||
if (long.TryParse(v, out _) || decimal.TryParse(v, out _))
|
||||
return $" DEFAULT {v}";
|
||||
if (vLower == "true" || vLower == "1") return " DEFAULT 1";
|
||||
if (vLower == "false" || vLower == "0") return " DEFAULT 0";
|
||||
return $" DEFAULT '{v.Replace("'", "''")}'";
|
||||
}
|
||||
|
||||
// MySQL 类型映射:根据通用 DataType/Length/Scale 生成 MySQL 列类型
|
||||
private static string BuildMySqlColumnType(TableColumnDto column)
|
||||
{
|
||||
var dt = (column.DataType ?? string.Empty).Trim();
|
||||
var dtLower = dt.ToLower();
|
||||
|
||||
return dtLower switch
|
||||
{
|
||||
"nvarchar" or "varchar" or "character varying" => $"VARCHAR({(column.Length is > 0 and <= 65535 ? column.Length : 255)})",
|
||||
"char" or "nchar" => $"CHAR({(column.Length is > 0 and <= 255 ? column.Length : 1)})",
|
||||
"nvarchar(max)" or "varchar(max)" => "LONGTEXT",
|
||||
"int" or "integer" => "INT",
|
||||
"bigint" => "BIGINT",
|
||||
"tinyint" => "TINYINT",
|
||||
"bit" => "BIT",
|
||||
"decimal" or "numeric" =>
|
||||
$"DECIMAL({(column.Length ?? 18)},{(column.Scale ?? 0)})",
|
||||
"double" or "float" => "DOUBLE",
|
||||
"datetime" => "DATETIME",
|
||||
"date" => "DATE",
|
||||
"text" or "ntext" => "TEXT",
|
||||
"longtext" => "LONGTEXT",
|
||||
"uniqueidentifier" or "uuid" => "CHAR(36)",
|
||||
_ => dt // 其他:假设是合法的 MySQL 类型
|
||||
};
|
||||
}
|
||||
|
||||
// MySQL 专用:建表
|
||||
private async Task CreateTableMySqlAsync(CreateTableRequest request)
|
||||
{
|
||||
var columnDefs = new List<string>();
|
||||
foreach (var column in request.Columns.OrderBy(c => c.Order))
|
||||
{
|
||||
var typeSql = BuildMySqlColumnType(column);
|
||||
var isIdentity = column.IsIdentity;
|
||||
var nullSql = isIdentity ? "NOT NULL" : (column.IsNullable ? "NULL" : "NOT NULL");
|
||||
var identitySql = isIdentity ? " AUTO_INCREMENT" : string.Empty;
|
||||
var commentSql = string.IsNullOrWhiteSpace(column.Comment)
|
||||
? string.Empty
|
||||
: $" COMMENT '{column.Comment.Replace("'", "''")}'";
|
||||
var defaultSql = FormatDefaultForMySql(column.DefaultValue);
|
||||
|
||||
columnDefs.Add($"`{column.ColumnName}` {typeSql}{identitySql} {nullSql}{defaultSql}{commentSql}");
|
||||
}
|
||||
|
||||
var pkCols = request.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => $"`{c.ColumnName}`");
|
||||
var pkSql = pkCols.Any() ? $", PRIMARY KEY ({string.Join(", ", pkCols)})" : string.Empty;
|
||||
var createSql = $"CREATE TABLE `{request.TableName}` (\n {string.Join(",\n ", columnDefs)}{pkSql}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
||||
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(createSql);
|
||||
}
|
||||
|
||||
// MySQL 专用:获取表结构
|
||||
private async Task<TableInfoDto> GetTableInfoMySqlAsync(string tableName)
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT
|
||||
c.COLUMN_NAME,
|
||||
c.DATA_TYPE,
|
||||
c.CHARACTER_MAXIMUM_LENGTH,
|
||||
c.IS_NULLABLE,
|
||||
c.ORDINAL_POSITION,
|
||||
c.NUMERIC_SCALE,
|
||||
c.NUMERIC_PRECISION,
|
||||
CASE WHEN k.CONSTRAINT_NAME = 'PRIMARY' THEN 1 ELSE 0 END AS IS_PRIMARY_KEY,
|
||||
CASE WHEN c.EXTRA LIKE '%auto_increment%' THEN 1 ELSE 0 END AS IS_IDENTITY,
|
||||
c.COLUMN_COMMENT,
|
||||
c.COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS c
|
||||
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
|
||||
ON k.TABLE_SCHEMA = c.TABLE_SCHEMA
|
||||
AND k.TABLE_NAME = c.TABLE_NAME
|
||||
AND k.COLUMN_NAME = c.COLUMN_NAME
|
||||
WHERE c.TABLE_SCHEMA = DATABASE() AND c.TABLE_NAME = @tableName
|
||||
ORDER BY c.ORDINAL_POSITION;";
|
||||
|
||||
var dt = await _context.SqlSugarClient.Ado.GetDataTableAsync(sql, new { tableName });
|
||||
var columns = new List<TableColumnDto>();
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
var dataType = row[1]?.ToString() ?? "";
|
||||
var isDecimal = dataType.Equals("decimal", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
int? charLen = null;
|
||||
if (!dataType.Equals("text", StringComparison.OrdinalIgnoreCase) &&
|
||||
!dataType.Equals("longtext", StringComparison.OrdinalIgnoreCase) &&
|
||||
row[2] != DBNull.Value && row[2] != null)
|
||||
{
|
||||
var raw = Convert.ToInt64(row[2]);
|
||||
if (raw > 0 && raw <= int.MaxValue) charLen = (int)raw;
|
||||
}
|
||||
|
||||
var numericScale = row[5] == DBNull.Value || row[5] == null ? (int?)null : Convert.ToInt32(row[5]);
|
||||
var numericPrecision = row[6] == DBNull.Value || row[6] == null ? (int?)null : Convert.ToInt32(row[6]);
|
||||
var colDefault = row[10] == DBNull.Value || row[10] == null ? null : NormalizeMySqlDefault(row[10]?.ToString());
|
||||
|
||||
columns.Add(new TableColumnDto
|
||||
{
|
||||
ColumnName = row[0]?.ToString() ?? "",
|
||||
DataType = dataType,
|
||||
Length = isDecimal ? numericPrecision : charLen,
|
||||
Scale = isDecimal ? numericScale : null,
|
||||
IsNullable = string.Equals(row[3]?.ToString(), "YES", StringComparison.OrdinalIgnoreCase),
|
||||
Order = Convert.ToInt32(row[4]),
|
||||
IsPrimaryKey = row[7] != DBNull.Value && row[7] != null && Convert.ToInt32(row[7]) == 1,
|
||||
IsIdentity = row[8] != DBNull.Value && row[8] != null && Convert.ToInt32(row[8]) == 1,
|
||||
Comment = row[9]?.ToString(),
|
||||
DefaultValue = colDefault ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
return new TableInfoDto { TableName = tableName, Columns = columns };
|
||||
}
|
||||
|
||||
// MySQL 专用:更新表(仅支持新增列、修改类型/长度/小数位/可空、更新主键)
|
||||
private async Task UpdateTableMySqlAsync(UpdateTableRequest request)
|
||||
{
|
||||
var existing = await GetTableInfoMySqlAsync(request.TableName) ?? throw new InvalidOperationException("Table not found.");
|
||||
var existingByName = existing.Columns.ToDictionary(c => c.ColumnName, c => c, StringComparer.Ordinal);
|
||||
var newNames = request.Columns.Select(c => c.ColumnName).ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
// 1) 识别并执行重命名字段(区分大小写)
|
||||
var renames = request.Columns
|
||||
.Where(c => c.IsDbField && !string.IsNullOrWhiteSpace(c.OriginalColumnName) &&
|
||||
c.OriginalColumnName != c.ColumnName &&
|
||||
existingByName.Keys.Any(k => string.Equals(k, c.OriginalColumnName, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
foreach (var r in renames)
|
||||
{
|
||||
var actualKey = existingByName.Keys.FirstOrDefault(k => string.Equals(k, r.OriginalColumnName, StringComparison.OrdinalIgnoreCase));
|
||||
if (actualKey == null) continue;
|
||||
var oldCol = existingByName[actualKey];
|
||||
var typeSql = BuildMySqlColumnType(oldCol);
|
||||
var isIdentity = oldCol.IsIdentity;
|
||||
var nullSql = isIdentity ? "NOT NULL" : (oldCol.IsNullable ? "NULL" : "NOT NULL");
|
||||
var identitySql = isIdentity ? " AUTO_INCREMENT" : string.Empty;
|
||||
var commentSql = string.IsNullOrWhiteSpace(oldCol.Comment) ? string.Empty : $" COMMENT '{oldCol.Comment.Replace("'", "''")}'";
|
||||
var defaultSql = FormatDefaultForMySql(oldCol.DefaultValue);
|
||||
var renameSql = $"ALTER TABLE `{request.TableName}` CHANGE COLUMN `{actualKey.Replace("`", "``")}` `{r.ColumnName.Replace("`", "``")}` {typeSql}{identitySql} {nullSql}{defaultSql}{commentSql};";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(renameSql);
|
||||
existingByName.Remove(actualKey);
|
||||
existingByName[r.ColumnName] = new TableColumnDto { ColumnName = r.ColumnName, DataType = oldCol.DataType, Length = oldCol.Length, Scale = oldCol.Scale, IsNullable = oldCol.IsNullable, IsPrimaryKey = oldCol.IsPrimaryKey, IsIdentity = oldCol.IsIdentity, Comment = oldCol.Comment, DefaultValue = oldCol.DefaultValue ?? "", Order = oldCol.Order };
|
||||
}
|
||||
|
||||
var existingNames = existingByName.Keys.ToHashSet(StringComparer.Ordinal);
|
||||
var columnsToDrop = existingNames.Except(newNames, StringComparer.Ordinal).ToList();
|
||||
|
||||
foreach (var column in request.Columns.Where(c => !existingByName.ContainsKey(c.ColumnName)).OrderBy(c => c.Order))
|
||||
{
|
||||
var typeSql = BuildMySqlColumnType(column);
|
||||
var isIdentity = column.IsIdentity;
|
||||
var nullSql = isIdentity ? "NOT NULL" : (column.IsNullable ? "NULL" : "NOT NULL");
|
||||
var identitySql = isIdentity ? " AUTO_INCREMENT" : string.Empty;
|
||||
var commentSql = string.IsNullOrWhiteSpace(column.Comment) ? string.Empty : $" COMMENT '{column.Comment.Replace("'", "''")}'";
|
||||
var defaultSql = FormatDefaultForMySql(column.DefaultValue);
|
||||
var addColSql = $"ALTER TABLE `{request.TableName}` ADD COLUMN `{column.ColumnName}` {typeSql}{identitySql} {nullSql}{defaultSql}{commentSql};";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(addColSql);
|
||||
}
|
||||
|
||||
foreach (var column in request.Columns.Where(c => existingByName.ContainsKey(c.ColumnName)))
|
||||
{
|
||||
var old = existingByName[column.ColumnName];
|
||||
var typeSql = BuildMySqlColumnType(column);
|
||||
var oldTypeSql = BuildMySqlColumnType(old);
|
||||
var typeChanged = !string.Equals(oldTypeSql, typeSql, StringComparison.OrdinalIgnoreCase);
|
||||
var nullableChanged = old.IsNullable != column.IsNullable;
|
||||
var defaultChanged = (old.DefaultValue ?? "").Trim() != (column.DefaultValue ?? "").Trim();
|
||||
var identityChanged = old.IsIdentity != column.IsIdentity;
|
||||
var commentChanged = (old.Comment ?? "").Trim() != (column.Comment ?? "").Trim();
|
||||
|
||||
if (!typeChanged && !nullableChanged && !defaultChanged && !identityChanged && !commentChanged)
|
||||
continue;
|
||||
|
||||
var isIdentity = column.IsIdentity;
|
||||
var nullSql = isIdentity ? "NOT NULL" : (column.IsNullable ? "NULL" : "NOT NULL");
|
||||
var identitySql = isIdentity ? " AUTO_INCREMENT" : string.Empty;
|
||||
var commentSql = string.IsNullOrWhiteSpace(column.Comment) ? string.Empty : $" COMMENT '{column.Comment.Replace("'", "''")}'";
|
||||
var defaultSql = FormatDefaultForMySql(column.DefaultValue);
|
||||
var modifySql = $"ALTER TABLE `{request.TableName}` MODIFY COLUMN `{column.ColumnName}` {typeSql}{identitySql} {nullSql}{defaultSql}{commentSql};";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(modifySql);
|
||||
}
|
||||
|
||||
foreach (var colName in columnsToDrop)
|
||||
{
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE `{request.TableName}` DROP COLUMN `{colName}`;");
|
||||
}
|
||||
|
||||
var newPkCols = request.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => c.ColumnName).ToList();
|
||||
var existingPkCols = existingByName.Values.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => c.ColumnName).ToList();
|
||||
var pkChanged = !newPkCols.SequenceEqual(existingPkCols);
|
||||
|
||||
if (pkChanged)
|
||||
{
|
||||
try { await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE `{request.TableName}` DROP PRIMARY KEY;"); } catch { }
|
||||
if (newPkCols.Any())
|
||||
{
|
||||
var pkColsSql = string.Join(", ", newPkCols.Select(c => $"`{c}`"));
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE `{request.TableName}` ADD PRIMARY KEY ({pkColsSql});");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Npgsql;
|
||||
using VolPro.Builder.IServices;
|
||||
using VolPro.Core.EFDbContext;
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
/// <summary>
|
||||
/// PostgreSQL 数据库表结构操作实现。
|
||||
/// </summary>
|
||||
public class PgSqlTableProvider : ITableDatabaseProvider
|
||||
{
|
||||
private readonly BaseDbContext _context;
|
||||
private readonly string _schema;
|
||||
|
||||
public PgSqlTableProvider(BaseDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
// 从 PG 连接字符串推断 schema:优先 SearchPath / search_path,其次 schema,默认 public
|
||||
var connStr = _context.SqlSugarClient.Ado.Connection?.ConnectionString;
|
||||
var schema = "public";
|
||||
if (!string.IsNullOrWhiteSpace(connStr))
|
||||
{
|
||||
try
|
||||
{
|
||||
var builder = new NpgsqlConnectionStringBuilder(connStr);
|
||||
if (!string.IsNullOrWhiteSpace(builder.SearchPath))
|
||||
{
|
||||
schema = builder.SearchPath.Split(',')[0].Trim();
|
||||
}
|
||||
else if (builder.TryGetValue("schema", out var sVal) && sVal is string s && !string.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
schema = s.Trim();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 解析失败时回退到 public
|
||||
schema = "public";
|
||||
}
|
||||
}
|
||||
_schema = schema;
|
||||
}
|
||||
|
||||
public async Task<bool> TableExistsAsync(string tableName)
|
||||
{
|
||||
const string pgSql = "SELECT COUNT(*) as value FROM information_schema.tables WHERE table_schema = @schemaName AND table_name = @tableName;";
|
||||
var res = await _context.SqlSugarClient.Ado.GetScalarAsync(pgSql, new { schemaName = _schema, tableName });
|
||||
return Convert.ToInt32(res) > 0;
|
||||
}
|
||||
|
||||
public async Task CreateTableAsync(CreateTableRequest request)
|
||||
{
|
||||
await CreateTablePgSqlAsync(request);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetAllTablesAsync()
|
||||
{
|
||||
const string pgSql = "SELECT table_name as value FROM information_schema.tables WHERE table_schema = @schemaName ORDER BY table_name;";
|
||||
var rows = await _context.SqlSugarClient.Ado.SqlQueryAsync<string>(pgSql, new { schemaName = _schema });
|
||||
return rows ?? new List<string>();
|
||||
}
|
||||
|
||||
public async Task<TableInfoDto> GetTableInfoAsync(string tableName)
|
||||
{
|
||||
return await GetTableInfoPgSqlAsync(tableName);
|
||||
}
|
||||
|
||||
public async Task UpdateTableAsync(UpdateTableRequest request)
|
||||
{
|
||||
await UpdateTablePgSqlAsync(request);
|
||||
}
|
||||
|
||||
public async Task DeleteTableAsync(string tableName)
|
||||
{
|
||||
var dropPg = $"DROP TABLE \"{tableName.Replace("\"", "\"\"")}\";";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(dropPg);
|
||||
}
|
||||
|
||||
private static string NormalizePgSqlDefault(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
||||
var s = raw.Trim();
|
||||
if (s.StartsWith("'") && s.Contains("'::"))
|
||||
{
|
||||
var end = s.IndexOf("'::", StringComparison.Ordinal);
|
||||
if (end > 0)
|
||||
{
|
||||
var inner = s[1..end].Replace("''", "'");
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
if (s.StartsWith("nextval(")) return null;
|
||||
return s;
|
||||
}
|
||||
|
||||
private static string FormatDefaultForPgSql(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return string.Empty;
|
||||
var v = value.Trim();
|
||||
var vLower = v.ToLower();
|
||||
if (vLower is "current_timestamp" or "now()" or "localtimestamp")
|
||||
return $" DEFAULT {v}";
|
||||
if (long.TryParse(v, out _) || decimal.TryParse(v, out _))
|
||||
return $" DEFAULT {v}";
|
||||
if (vLower == "true" || vLower == "1") return " DEFAULT true";
|
||||
if (vLower == "false" || vLower == "0") return " DEFAULT false";
|
||||
return $" DEFAULT '{v.Replace("'", "''")}'";
|
||||
}
|
||||
|
||||
// PGSql 类型映射
|
||||
private static string BuildPgSqlColumnType(TableColumnDto column)
|
||||
{
|
||||
var dt = (column.DataType ?? string.Empty).Trim();
|
||||
var dtLower = dt.ToLower();
|
||||
|
||||
return dtLower switch
|
||||
{
|
||||
"nvarchar" or "varchar" or "character varying" => $"VARCHAR({(column.Length is > 0 ? column.Length : 255)})",
|
||||
"char" or "nchar" => $"CHAR({(column.Length is > 0 ? column.Length : 1)})",
|
||||
"nvarchar(max)" or "varchar(max)" or "text" or "ntext" => "TEXT",
|
||||
"int" or "integer" => "INTEGER",
|
||||
"bigint" => "BIGINT",
|
||||
"tinyint" or "bit" => "SMALLINT",
|
||||
"decimal" or "numeric" =>
|
||||
$"NUMERIC({(column.Length ?? 18)},{(column.Scale ?? 0)})",
|
||||
"float" or "double" => "DOUBLE PRECISION",
|
||||
"datetime" => "TIMESTAMP",
|
||||
"date" => "DATE",
|
||||
"uniqueidentifier" or "uuid" => "UUID",
|
||||
_ => dt
|
||||
};
|
||||
}
|
||||
|
||||
// PGSql 专用:建表
|
||||
private async Task CreateTablePgSqlAsync(CreateTableRequest request)
|
||||
{
|
||||
var columnDefs = new List<string>();
|
||||
foreach (var column in request.Columns.OrderBy(c => c.Order))
|
||||
{
|
||||
var typeSql = BuildPgSqlColumnType(column);
|
||||
|
||||
var isIntLike = typeSql.StartsWith("INTEGER", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeSql.StartsWith("INT4", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeSql.StartsWith("INT8", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeSql.StartsWith("INT4", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeSql.StartsWith("INT2", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeSql.StartsWith("BIGINT", StringComparison.OrdinalIgnoreCase);
|
||||
var wantIdentity = column.IsIdentity && column.IsPrimaryKey && isIntLike;
|
||||
|
||||
// PGSql:自增主键使用 GENERATED BY DEFAULT AS IDENTITY
|
||||
var identitySql = wantIdentity ? " GENERATED ALWAYS AS IDENTITY " : string.Empty;
|
||||
|
||||
var nullSql = column.IsNullable && !column.IsPrimaryKey
|
||||
? "NULL"
|
||||
: "NOT NULL";
|
||||
|
||||
var defaultSql = FormatDefaultForPgSql(column.DefaultValue);
|
||||
|
||||
columnDefs.Add($"\"{column.ColumnName}\" {typeSql}{identitySql} {nullSql}{defaultSql}");
|
||||
}
|
||||
|
||||
var pkCols = request.Columns.Where(c => c.IsPrimaryKey)
|
||||
.OrderBy(c => c.Order)
|
||||
.Select(c => $"\"{c.ColumnName}\"");
|
||||
var pkSql = pkCols.Any() ? $", CONSTRAINT \"PK_{request.TableName}\" PRIMARY KEY ({string.Join(", ", pkCols)})" : string.Empty;
|
||||
|
||||
var createSql = $"CREATE TABLE \"{request.TableName}\" (\n {string.Join(",\n ", columnDefs)}{pkSql}\n);";
|
||||
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(createSql);
|
||||
|
||||
foreach (var column in request.Columns.Where(c => !string.IsNullOrWhiteSpace(c.Comment)))
|
||||
{
|
||||
var commentSql = $"COMMENT ON COLUMN \"{request.TableName}\".\"{column.ColumnName}\" IS '{column.Comment!.Replace("'", "''")}';";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(commentSql);
|
||||
}
|
||||
}
|
||||
|
||||
// PGSql 专用:获取表结构
|
||||
private async Task<TableInfoDto> GetTableInfoPgSqlAsync(string tableName)
|
||||
{
|
||||
const string sql = @"
|
||||
SELECT c.column_name, c.data_type, c.character_maximum_length, c.is_nullable, c.ordinal_position,
|
||||
c.numeric_scale, c.numeric_precision,
|
||||
CASE WHEN pk.column_name IS NOT NULL THEN 1 ELSE 0 END AS is_primary_key,
|
||||
CASE WHEN c.column_default LIKE 'nextval(%' THEN 1 ELSE 0 END AS is_identity,
|
||||
col_description(format('%I.%I', c.table_schema, c.table_name)::regclass::oid, a.attnum) AS comment,
|
||||
c.column_default
|
||||
FROM information_schema.columns c
|
||||
LEFT JOIN (SELECT kcu.table_schema, kcu.table_name, kcu.column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.constraint_type = 'PRIMARY KEY') pk
|
||||
ON pk.table_schema = c.table_schema AND pk.table_name = c.table_name AND pk.column_name = c.column_name
|
||||
JOIN pg_attribute a ON a.attname = c.column_name AND a.attrelid = format('%I.%I', c.table_schema, c.table_name)::regclass
|
||||
WHERE c.table_schema = @schemaName AND c.table_name = @tableName ORDER BY c.ordinal_position;";
|
||||
|
||||
var dt = await _context.SqlSugarClient.Ado.GetDataTableAsync(sql, new { schemaName = _schema, tableName });
|
||||
var columns = new List<TableColumnDto>();
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
var dataType = row[1]?.ToString() ?? "";
|
||||
var isDecimal = dataType.Equals("numeric", StringComparison.OrdinalIgnoreCase) || dataType.Equals("decimal", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
int? charLen = null;
|
||||
if (row[2] != DBNull.Value && row[2] != null)
|
||||
{
|
||||
var raw = Convert.ToInt64(row[2]);
|
||||
if (raw > 0 && raw <= int.MaxValue) charLen = (int)raw;
|
||||
}
|
||||
|
||||
var numericScale = row[5] == DBNull.Value || row[5] == null ? (int?)null : Convert.ToInt32(row[5]);
|
||||
var numericPrecision = row[6] == DBNull.Value || row[6] == null ? (int?)null : Convert.ToInt32(row[6]);
|
||||
var colDefault = row[10] == DBNull.Value || row[10] == null ? null : NormalizePgSqlDefault(row[10]?.ToString());
|
||||
|
||||
columns.Add(new TableColumnDto
|
||||
{
|
||||
ColumnName = row[0]?.ToString() ?? "",
|
||||
DataType = dataType,
|
||||
Length = isDecimal ? numericPrecision : charLen,
|
||||
Scale = isDecimal ? numericScale : null,
|
||||
IsNullable = string.Equals(row[3]?.ToString(), "YES", StringComparison.OrdinalIgnoreCase),
|
||||
Order = Convert.ToInt32(row[4]),
|
||||
IsPrimaryKey = row[7] != DBNull.Value && row[7] != null && Convert.ToInt32(row[7]) == 1,
|
||||
IsIdentity = row[8] != DBNull.Value && row[8] != null && Convert.ToInt32(row[8]) == 1,
|
||||
Comment = row[9]?.ToString(),
|
||||
DefaultValue = colDefault ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
return new TableInfoDto { TableName = tableName, Columns = columns };
|
||||
}
|
||||
|
||||
// PGSql 专用:更新表(新增/删除/修改列 + 主键 & 注释)
|
||||
private async Task UpdateTablePgSqlAsync(UpdateTableRequest request)
|
||||
{
|
||||
var existing = await GetTableInfoPgSqlAsync(request.TableName) ?? throw new InvalidOperationException("Table not found.");
|
||||
var existingByName = existing.Columns.ToDictionary(c => c.ColumnName, c => c, StringComparer.Ordinal);
|
||||
var newNames = request.Columns.Select(c => c.ColumnName).ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
// 1) 识别并执行重命名字段(区分大小写)
|
||||
var renames = request.Columns
|
||||
.Where(c => c.IsDbField && !string.IsNullOrWhiteSpace(c.OriginalColumnName) &&
|
||||
c.OriginalColumnName != c.ColumnName &&
|
||||
existingByName.Keys.Any(k => string.Equals(k, c.OriginalColumnName, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
foreach (var r in renames)
|
||||
{
|
||||
var actualKey = existingByName.Keys.FirstOrDefault(k => string.Equals(k, r.OriginalColumnName, StringComparison.OrdinalIgnoreCase));
|
||||
if (actualKey == null) continue;
|
||||
var renameSql = $"ALTER TABLE \"{request.TableName}\" RENAME COLUMN \"{actualKey.Replace("\"", "\"\"")}\" TO \"{r.ColumnName.Replace("\"", "\"\"")}\";";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(renameSql);
|
||||
var oldCol = existingByName[actualKey];
|
||||
existingByName.Remove(actualKey);
|
||||
existingByName[r.ColumnName] = new TableColumnDto { ColumnName = r.ColumnName, DataType = oldCol.DataType, Length = oldCol.Length, Scale = oldCol.Scale, IsNullable = oldCol.IsNullable, IsPrimaryKey = oldCol.IsPrimaryKey, IsIdentity = oldCol.IsIdentity, Comment = oldCol.Comment, DefaultValue = oldCol.DefaultValue ?? "", Order = oldCol.Order };
|
||||
}
|
||||
|
||||
var existingNames = existingByName.Keys.ToHashSet(StringComparer.Ordinal);
|
||||
var columnsToDrop = existingNames.Except(newNames, StringComparer.Ordinal).ToList();
|
||||
|
||||
// 2) 新增列
|
||||
foreach (var column in request.Columns.Where(c => !existingByName.ContainsKey(c.ColumnName)).OrderBy(c => c.Order))
|
||||
{
|
||||
var typeSql = BuildPgSqlColumnType(column);
|
||||
var isIdentity = column.IsIdentity &&
|
||||
(typeSql.StartsWith("INTEGER", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeSql.StartsWith("BIGINT", StringComparison.OrdinalIgnoreCase));
|
||||
var identitySql = isIdentity ? " GENERATED ALWAYS AS IDENTITY" : string.Empty;
|
||||
var nullSql = column.IsNullable && !column.IsPrimaryKey
|
||||
? "NULL"
|
||||
: "NOT NULL";
|
||||
|
||||
var defaultSql = FormatDefaultForPgSql(column.DefaultValue);
|
||||
|
||||
var addColSql = $"ALTER TABLE \"{request.TableName}\" ADD COLUMN \"{column.ColumnName}\" {typeSql}{identitySql} {nullSql}{defaultSql};";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(addColSql);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(column.Comment))
|
||||
{
|
||||
var commentSql = $"COMMENT ON COLUMN \"{request.TableName}\".\"{column.ColumnName}\" IS '{column.Comment!.Replace("'", "''")}';";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(commentSql);
|
||||
}
|
||||
}
|
||||
|
||||
// 3) 修改已有列:仅当有变更时才执行 ALTER
|
||||
foreach (var column in request.Columns.Where(c => existingByName.ContainsKey(c.ColumnName)))
|
||||
{
|
||||
var typeSql = BuildPgSqlColumnType(column);
|
||||
var old = existingByName[column.ColumnName];
|
||||
var isIntegerLike = typeSql.StartsWith("INTEGER", StringComparison.OrdinalIgnoreCase) ||
|
||||
typeSql.StartsWith("BIGINT", StringComparison.OrdinalIgnoreCase);
|
||||
var wantIdentity = column.IsIdentity && column.IsPrimaryKey && isIntegerLike;
|
||||
|
||||
var oldTypeSql = BuildPgSqlColumnType(old);
|
||||
var typeChanged = !string.Equals(oldTypeSql, typeSql, StringComparison.OrdinalIgnoreCase);
|
||||
var nullableChanged = old.IsNullable != column.IsNullable;
|
||||
var defaultChanged = (old.DefaultValue ?? "").Trim() != (column.DefaultValue ?? "").Trim();
|
||||
var identityChanged = isIntegerLike && old.IsIdentity != wantIdentity;
|
||||
var commentChanged = (old.Comment ?? "").Trim() != (column.Comment ?? "").Trim();
|
||||
|
||||
if (typeChanged)
|
||||
{
|
||||
var alterTypeSql = $"ALTER TABLE \"{request.TableName}\" ALTER COLUMN \"{column.ColumnName}\" TYPE {typeSql};";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(alterTypeSql);
|
||||
}
|
||||
|
||||
if (nullableChanged)
|
||||
{
|
||||
var nullSql = column.IsNullable && !column.IsPrimaryKey ? "DROP NOT NULL" : "SET NOT NULL";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{request.TableName}\" ALTER COLUMN \"{column.ColumnName}\" {nullSql};");
|
||||
}
|
||||
|
||||
if (defaultChanged)
|
||||
{
|
||||
var defaultSql = FormatDefaultForPgSql(column.DefaultValue);
|
||||
if (!string.IsNullOrWhiteSpace(defaultSql))
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{request.TableName}\" ALTER COLUMN \"{column.ColumnName}\" SET {defaultSql.Trim()};");
|
||||
else
|
||||
try { await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{request.TableName}\" ALTER COLUMN \"{column.ColumnName}\" DROP DEFAULT;"); } catch { }
|
||||
}
|
||||
|
||||
if (identityChanged)
|
||||
{
|
||||
if (wantIdentity && !old.IsIdentity)
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{request.TableName}\" ALTER COLUMN \"{column.ColumnName}\" ADD GENERATED BY DEFAULT AS IDENTITY;");
|
||||
else if (!wantIdentity && old.IsIdentity)
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{request.TableName}\" ALTER COLUMN \"{column.ColumnName}\" DROP IDENTITY IF EXISTS;");
|
||||
}
|
||||
|
||||
if (commentChanged)
|
||||
{
|
||||
var commentText = string.IsNullOrWhiteSpace(column.Comment) ? "NULL" : $"'{column.Comment!.Replace("'", "''")}'";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"COMMENT ON COLUMN \"{request.TableName}\".\"{column.ColumnName}\" IS {commentText};");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var colName in columnsToDrop)
|
||||
{
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{request.TableName}\" DROP COLUMN \"{colName}\";");
|
||||
}
|
||||
|
||||
var newPkCols = request.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => c.ColumnName).ToList();
|
||||
var existingPkCols = existingByName.Values.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => c.ColumnName).ToList();
|
||||
var pkChanged = !newPkCols.SequenceEqual(existingPkCols);
|
||||
|
||||
if (pkChanged)
|
||||
{
|
||||
const string findPkSql = "SELECT c.conname FROM pg_constraint c JOIN pg_class t ON c.conrelid = t.oid JOIN pg_namespace n ON n.oid = t.relnamespace WHERE c.contype = 'p' AND n.nspname = @schemaName AND t.relname = @tableName;";
|
||||
var pkDt = await _context.SqlSugarClient.Ado.GetDataTableAsync(findPkSql, new { schemaName = _schema, tableName = request.TableName });
|
||||
foreach (DataRow r in pkDt.Rows)
|
||||
{
|
||||
var pkName = r[0]?.ToString();
|
||||
if (!string.IsNullOrEmpty(pkName))
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{_schema}\".\"{request.TableName}\" DROP CONSTRAINT \"{pkName}\";");
|
||||
}
|
||||
|
||||
if (newPkCols.Any())
|
||||
{
|
||||
var pkColsSql = string.Join(", ", newPkCols.Select(c => $"\"{c}\""));
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"ALTER TABLE \"{_schema}\".\"{request.TableName}\" ADD CONSTRAINT \"PK_{request.TableName}\" PRIMARY KEY ({pkColsSql});");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using VolPro.Builder.IServices;
|
||||
using VolPro.Core.EFDbContext;
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
public class SqlServerTableProvider : ITableDatabaseProvider
|
||||
{
|
||||
private readonly BaseDbContext _context;
|
||||
|
||||
public SqlServerTableProvider(BaseDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
private static string NormalizeSqlServerDefault(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw)) return null;
|
||||
var s = raw.Trim();
|
||||
while (s.StartsWith("(") && s.EndsWith(")")) s = s[1..^1].Trim();
|
||||
return string.IsNullOrWhiteSpace(s) ? null : s;
|
||||
}
|
||||
|
||||
private static string FormatDefaultForSqlServer(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return string.Empty;
|
||||
var v = value.Trim();
|
||||
var vLower = v.ToLower();
|
||||
if (vLower is "getdate()" or "newid()" or "sysdatetime()")
|
||||
return $" DEFAULT {v}";
|
||||
if (long.TryParse(v, out _) || decimal.TryParse(v, out _))
|
||||
return $" DEFAULT {v}";
|
||||
if (vLower == "true" || vLower == "1") return " DEFAULT 1";
|
||||
if (vLower == "false" || vLower == "0") return " DEFAULT 0";
|
||||
return $" DEFAULT N'{v.Replace("'", "''")}'";
|
||||
}
|
||||
|
||||
private static string BuildColumnDefinition(TableColumnDto column)
|
||||
{
|
||||
var definition = $"[{column.ColumnName}] ";
|
||||
var dtLower = (column.DataType ?? "").Trim().ToLower();
|
||||
if (dtLower == "nvarchar(max)") { dtLower = "nvarchar"; }
|
||||
if (dtLower == "varchar(max)") { dtLower = "varchar"; }
|
||||
if (dtLower is "nvarchar" or "varchar")
|
||||
{
|
||||
var length = (column.DataType ?? "").Contains("max", StringComparison.OrdinalIgnoreCase) ? -1 : (column.Length ?? 255);
|
||||
definition += $"{dtLower}({(length == -1 ? "MAX" : length.ToString())})";
|
||||
}
|
||||
else if (dtLower is "char" or "nchar")
|
||||
{
|
||||
var length = column.Length ?? 1;
|
||||
definition += $"{column.DataType}({length})";
|
||||
}
|
||||
else if (dtLower is "decimal" or "numeric")
|
||||
{
|
||||
var precision = column.Length ?? 18;
|
||||
var scale = column.Scale ?? 0;
|
||||
definition += $"{column.DataType}({precision}, {scale})";
|
||||
}
|
||||
else
|
||||
{
|
||||
definition += column.DataType ?? "nvarchar";
|
||||
}
|
||||
if (column.IsIdentity) definition += " IDENTITY(1,1)";
|
||||
if (!column.IsNullable) definition += " NOT NULL";
|
||||
definition += FormatDefaultForSqlServer(column.DefaultValue);
|
||||
return definition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SQL Server 不支持 ALTER COLUMN 修改 IDENTITY,通过临时表重建实现
|
||||
/// </summary>
|
||||
private async Task RebuildTableForIdentityChangeAsync(UpdateTableRequest request, Dictionary<string, TableColumnDto> existingByName)
|
||||
{
|
||||
var tableName = request.TableName;
|
||||
var tmpName = $"Tmp_{tableName}_{Guid.NewGuid().ToString("N")[..8]}";
|
||||
var colDefs = request.Columns.OrderBy(c => c.Order).Select(BuildColumnDefinition).ToList();
|
||||
var createTmpSql = $"CREATE TABLE [{tmpName}] (\n " + string.Join(",\n ", colDefs) + "\n)";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(createTmpSql);
|
||||
|
||||
var colsToCopy = request.Columns.Where(c => existingByName.ContainsKey(c.ColumnName)).OrderBy(c => c.Order).Select(c => c.ColumnName).ToList();
|
||||
var hasIdentity = request.Columns.Any(c => c.IsIdentity);
|
||||
var colList = string.Join(", ", colsToCopy.Select(c => $"[{c}]"));
|
||||
|
||||
// IDENTITY_INSERT 是会话级,必须与 INSERT 在同一连接/同一批中执行
|
||||
if (colsToCopy.Count > 0)
|
||||
{
|
||||
var insertBatch = hasIdentity
|
||||
? $"SET IDENTITY_INSERT [{tmpName}] ON; INSERT INTO [{tmpName}] ({colList}) SELECT {colList} FROM [{tableName}]; SET IDENTITY_INSERT [{tmpName}] OFF"
|
||||
: $"INSERT INTO [{tmpName}] ({colList}) SELECT {colList} FROM [{tableName}]";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(insertBatch);
|
||||
}
|
||||
|
||||
var dropPkSql = $@"
|
||||
DECLARE @pk NVARCHAR(200);
|
||||
SELECT @pk = name FROM sys.key_constraints WHERE type = 'PK' AND parent_object_id = OBJECT_ID('{tableName.Replace("'", "''")}');
|
||||
IF @pk IS NOT NULL EXEC('ALTER TABLE [{tableName}] DROP CONSTRAINT [' + @pk + ']')";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(dropPkSql);
|
||||
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"DROP TABLE [{tableName}]");
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync($"EXEC sp_rename 'dbo.[{tmpName}]', '{tableName.Replace("'", "''")}', 'OBJECT'");
|
||||
|
||||
if (request.Columns.Any(c => c.IsPrimaryKey))
|
||||
{
|
||||
var pkCols = request.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => $"[{c.ColumnName}]");
|
||||
var addPkSql = $"ALTER TABLE [{tableName}] ADD CONSTRAINT [PK_{tableName}] PRIMARY KEY ({string.Join(", ", pkCols)})";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(addPkSql);
|
||||
}
|
||||
|
||||
foreach (var column in request.Columns.Where(c => !string.IsNullOrWhiteSpace(c.Comment)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var commentSql = $@"
|
||||
EXEC sp_addextendedproperty @name = N'MS_Description', @value = N'{column.Comment.Replace("'", "''")}',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo', @level1type = N'TABLE', @level1name = N'{tableName.Replace("'", "''")}', @level2type = N'COLUMN', @level2name = N'{column.ColumnName.Replace("'", "''")}'";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(commentSql);
|
||||
}
|
||||
catch { /* 注释已存在时忽略 */ }
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> TableExistsAsync(string tableName)
|
||||
{
|
||||
const string sql = "SELECT COUNT(*) as Value FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = @tableName";
|
||||
var res = await _context.SqlSugarClient.Ado.GetScalarAsync(sql, new { tableName });
|
||||
return Convert.ToInt32(res) > 0;
|
||||
}
|
||||
|
||||
public async Task CreateTableAsync(CreateTableRequest request)
|
||||
{
|
||||
var columnDefinitions = new List<string>();
|
||||
|
||||
foreach (var column in request.Columns.OrderBy(c => c.Order))
|
||||
{
|
||||
var definition = $"[{column.ColumnName}] ";
|
||||
|
||||
// Data type and length
|
||||
var dtLower = column.DataType.ToLower();
|
||||
if (dtLower == "nvarchar" || dtLower == "varchar")
|
||||
{
|
||||
var length = column.Length ?? 255;
|
||||
definition += $"{column.DataType}({(length == -1 ? "MAX" : length.ToString())})";
|
||||
}
|
||||
else if (dtLower == "char" || dtLower == "nchar")
|
||||
{
|
||||
var length = column.Length ?? 1;
|
||||
definition += $"{column.DataType}({length})";
|
||||
}
|
||||
else if (dtLower == "decimal" || dtLower == "numeric")
|
||||
{
|
||||
var precision = column.Length ?? 18;
|
||||
var scale = column.Scale ?? 0;
|
||||
definition += $"{column.DataType}({precision}, {scale})";
|
||||
}
|
||||
else
|
||||
{
|
||||
definition += column.DataType;
|
||||
}
|
||||
|
||||
// Identity (Auto increment)
|
||||
if (column.IsIdentity)
|
||||
{
|
||||
definition += " IDENTITY(1,1)";
|
||||
}
|
||||
|
||||
// Nullable
|
||||
if (!column.IsNullable)
|
||||
{
|
||||
definition += " NOT NULL";
|
||||
}
|
||||
|
||||
// Default value
|
||||
definition += FormatDefaultForSqlServer(column.DefaultValue);
|
||||
|
||||
columnDefinitions.Add(definition);
|
||||
}
|
||||
|
||||
// Build CREATE TABLE statement
|
||||
var createTableSql = $"CREATE TABLE [{request.TableName}] (\n " +
|
||||
string.Join(",\n ", columnDefinitions) +
|
||||
"\n)";
|
||||
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(createTableSql);
|
||||
|
||||
// Add primary key constraint
|
||||
var pkColumns = request.Columns
|
||||
.Where(c => c.IsPrimaryKey)
|
||||
.OrderBy(c => c.Order)
|
||||
.Select(c => $"[{c.ColumnName}]");
|
||||
|
||||
var pkConstraintName = $"PK_{request.TableName}";
|
||||
var pkSql = $"ALTER TABLE [{request.TableName}] ADD CONSTRAINT [{pkConstraintName}] PRIMARY KEY ({string.Join(", ", pkColumns)})";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(pkSql);
|
||||
|
||||
// Add column comments (MS_Description)
|
||||
foreach (var column in request.Columns.Where(c => !string.IsNullOrWhiteSpace(c.Comment)))
|
||||
{
|
||||
var commentSql = $@"
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'{column.Comment.Replace("'", "''")}',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'{request.TableName}',
|
||||
@level2type = N'COLUMN', @level2name = N'{column.ColumnName}'";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(commentSql);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetAllTablesAsync()
|
||||
{
|
||||
const string sql = "SELECT TABLE_NAME as Value FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME";
|
||||
var rows = await _context.SqlSugarClient.Ado.SqlQueryAsync<string>(sql);
|
||||
return rows ?? new List<string>();
|
||||
}
|
||||
|
||||
public async Task<TableInfoDto> GetTableInfoAsync(string tableName)
|
||||
{
|
||||
const string columnsSql = @"
|
||||
DECLARE @objId INT = OBJECT_ID(QUOTENAME('dbo') + '.' + QUOTENAME(@tableName));
|
||||
SELECT
|
||||
c.COLUMN_NAME,
|
||||
c.DATA_TYPE,
|
||||
c.CHARACTER_MAXIMUM_LENGTH,
|
||||
c.IS_NULLABLE,
|
||||
c.ORDINAL_POSITION,
|
||||
c.NUMERIC_SCALE,
|
||||
c.NUMERIC_PRECISION,
|
||||
COLUMNPROPERTY(@objId, c.COLUMN_NAME, 'IsIdentity') AS IS_IDENTITY,
|
||||
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IS_PRIMARY_KEY,
|
||||
CAST(ep.value AS NVARCHAR(4000)) AS COMMENT,
|
||||
c.COLUMN_DEFAULT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS c
|
||||
LEFT JOIN (
|
||||
SELECT ku.TABLE_NAME, ku.COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
||||
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku
|
||||
ON tc.CONSTRAINT_TYPE = 'PRIMARY KEY' AND tc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME
|
||||
) pk ON c.TABLE_NAME = pk.TABLE_NAME AND c.COLUMN_NAME = pk.COLUMN_NAME
|
||||
LEFT JOIN sys.columns sc ON sc.object_id = @objId AND sc.name = c.COLUMN_NAME
|
||||
LEFT JOIN sys.extended_properties ep ON ep.major_id = sc.object_id AND ep.minor_id = sc.column_id AND ep.name = 'MS_Description'
|
||||
WHERE c.TABLE_SCHEMA = 'dbo' AND c.TABLE_NAME = @tableName
|
||||
ORDER BY c.ORDINAL_POSITION;";
|
||||
|
||||
var dt = await _context.SqlSugarClient.Ado.GetDataTableAsync(columnsSql, new { tableName });
|
||||
var columnsWithComments = new List<TableColumnDto>();
|
||||
foreach (DataRow row in dt.Rows)
|
||||
{
|
||||
var length = row[2] == DBNull.Value || row[2] == null ? (int?)null : Convert.ToInt32(row[2]);
|
||||
var numericScale = row[5] == DBNull.Value || row[5] == null ? (int?)null : Convert.ToInt32(row[5]);
|
||||
var numericPrecision = row[6] == DBNull.Value || row[6] == null ? (int?)null : Convert.ToInt32(row[6]);
|
||||
var dataType = row[1]?.ToString() ?? "";
|
||||
var isDecimal = dataType.Equals("decimal", StringComparison.OrdinalIgnoreCase) || dataType.Equals("numeric", StringComparison.OrdinalIgnoreCase);
|
||||
var colDefault = row[10] == DBNull.Value || row[10] == null ? null : NormalizeSqlServerDefault(row[10]?.ToString());
|
||||
|
||||
columnsWithComments.Add(new TableColumnDto
|
||||
{
|
||||
ColumnName = row[0]?.ToString() ?? "",
|
||||
DataType = dataType,
|
||||
Length = isDecimal ? numericPrecision : (length == -1 ? -1 : length),
|
||||
Scale = isDecimal ? numericScale : null,
|
||||
IsNullable = row[3]?.ToString() == "YES",
|
||||
Order = Convert.ToInt32(row[4]),
|
||||
IsIdentity = row[7] != DBNull.Value && row[7] != null && Convert.ToInt32(row[7]) == 1,
|
||||
IsPrimaryKey = row[8] != DBNull.Value && row[8] != null && Convert.ToInt32(row[8]) == 1,
|
||||
Comment = row[9]?.ToString(),
|
||||
DefaultValue = colDefault ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
return new TableInfoDto { TableName = tableName, Columns = columnsWithComments };
|
||||
}
|
||||
|
||||
public async Task UpdateTableAsync(UpdateTableRequest request)
|
||||
{
|
||||
var existingTable = await GetTableInfoAsync(request.TableName);
|
||||
if (existingTable == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Could not retrieve table information for '{request.TableName}'.");
|
||||
}
|
||||
|
||||
var existingByName = existingTable.Columns.ToDictionary(c => c.ColumnName, c => c, StringComparer.Ordinal);
|
||||
var newColumnNames = request.Columns.Select(c => c.ColumnName).ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
// 1) 识别并执行重命名字段(区分大小写)
|
||||
var renames = request.Columns
|
||||
.Where(c => c.IsDbField && !string.IsNullOrWhiteSpace(c.OriginalColumnName) &&
|
||||
c.OriginalColumnName != c.ColumnName &&
|
||||
existingByName.Keys.Any(k => string.Equals(k, c.OriginalColumnName, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToList();
|
||||
foreach (var r in renames)
|
||||
{
|
||||
var actualKey = existingByName.Keys.FirstOrDefault(k => string.Equals(k, r.OriginalColumnName, StringComparison.OrdinalIgnoreCase));
|
||||
if (actualKey == null) continue;
|
||||
var renameSql = $"EXEC sp_rename 'dbo.[{request.TableName}].[{actualKey}]', '{r.ColumnName.Replace("'", "''")}', 'COLUMN';";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(renameSql);
|
||||
var oldCol = existingByName[actualKey];
|
||||
existingByName.Remove(actualKey);
|
||||
existingByName[r.ColumnName] = new TableColumnDto { ColumnName = r.ColumnName, DataType = oldCol.DataType, Length = oldCol.Length, Scale = oldCol.Scale, IsNullable = oldCol.IsNullable, IsPrimaryKey = oldCol.IsPrimaryKey, IsIdentity = oldCol.IsIdentity, Comment = oldCol.Comment, DefaultValue = oldCol.DefaultValue ?? "", Order = oldCol.Order };
|
||||
}
|
||||
|
||||
var existingColumnNames = existingByName.Keys.ToHashSet(StringComparer.Ordinal);
|
||||
var columnsToDrop = existingColumnNames.Except(newColumnNames);
|
||||
foreach (var columnName in columnsToDrop)
|
||||
{
|
||||
var dropColumnSql = $"ALTER TABLE [{request.TableName}] DROP COLUMN [{columnName}]";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(dropColumnSql);
|
||||
}
|
||||
|
||||
// 2) 新增列
|
||||
var columnsToAdd = request.Columns.Where(c => !existingColumnNames.Contains(c.ColumnName, StringComparer.Ordinal));
|
||||
foreach (var column in columnsToAdd.OrderBy(c => c.Order))
|
||||
{
|
||||
var definition = $"[{column.ColumnName}] ";
|
||||
var dtLowerAdd = column.DataType.ToLower();
|
||||
|
||||
if (dtLowerAdd == "nvarchar" || dtLowerAdd == "varchar")
|
||||
{
|
||||
var length = column.Length ?? 255;
|
||||
definition += $"{column.DataType}({(length == -1 ? "MAX" : length.ToString())})";
|
||||
}
|
||||
else if (dtLowerAdd == "char" || dtLowerAdd == "nchar")
|
||||
{
|
||||
var length = column.Length ?? 1;
|
||||
definition += $"{column.DataType}({length})";
|
||||
}
|
||||
else if (dtLowerAdd == "decimal" || dtLowerAdd == "numeric")
|
||||
{
|
||||
var precision = column.Length ?? 18;
|
||||
var scale = column.Scale ?? 0;
|
||||
definition += $"{column.DataType}({precision}, {scale})";
|
||||
}
|
||||
else
|
||||
{
|
||||
definition += column.DataType;
|
||||
}
|
||||
|
||||
if (column.IsIdentity)
|
||||
{
|
||||
definition += " IDENTITY(1,1)";
|
||||
}
|
||||
|
||||
if (!column.IsNullable)
|
||||
{
|
||||
definition += " NOT NULL";
|
||||
}
|
||||
|
||||
definition += FormatDefaultForSqlServer(column.DefaultValue);
|
||||
|
||||
var addColumnSql = $"ALTER TABLE [{request.TableName}] ADD {definition}";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(addColumnSql);
|
||||
// 新列注释
|
||||
if (!string.IsNullOrWhiteSpace(column.Comment))
|
||||
{
|
||||
try
|
||||
{
|
||||
var commentSql = $@"
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'{column.Comment.Replace("'", "''")}',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'{request.TableName.Replace("'", "''")}',
|
||||
@level2type = N'COLUMN', @level2name = N'{column.ColumnName.Replace("'", "''")}'";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(commentSql);
|
||||
}
|
||||
catch { /* 注释已存在时忽略 */ }
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Alter existing columns: 类型/长度/小数位/可空(existingByName 已在 rename 后更新)
|
||||
var columnsToAlter = request.Columns
|
||||
.Where(c => existingByName.ContainsKey(c.ColumnName))
|
||||
.OrderBy(c => c.Order);
|
||||
|
||||
foreach (var column in columnsToAlter)
|
||||
{
|
||||
var existing = existingByName[column.ColumnName];
|
||||
|
||||
var defaultChanged = (existing.DefaultValue ?? "").Trim() != (column.DefaultValue ?? "").Trim();
|
||||
|
||||
var newTypeRaw = column.DataType?.Trim() ?? "";
|
||||
var newTypeLower = newTypeRaw.ToLower();
|
||||
var oldTypeLower = (existing.DataType ?? "").Trim().ToLower();
|
||||
|
||||
// 兼容传入 nvarchar(max)/varchar(max)
|
||||
if (newTypeLower == "nvarchar(max)") { newTypeLower = "nvarchar"; column.Length = -1; }
|
||||
if (newTypeLower == "varchar(max)") { newTypeLower = "varchar"; column.Length = -1; }
|
||||
|
||||
bool typeChanged = !string.Equals(oldTypeLower, newTypeLower, StringComparison.OrdinalIgnoreCase);
|
||||
bool nullableChanged = existing.IsNullable != column.IsNullable;
|
||||
|
||||
bool lengthChanged = false;
|
||||
bool scaleChanged = false;
|
||||
if (newTypeLower is "nvarchar" or "varchar")
|
||||
{
|
||||
var oldLen = existing.Length ?? 255;
|
||||
var newLen = column.Length ?? 255;
|
||||
lengthChanged = oldLen != newLen;
|
||||
}
|
||||
else if (newTypeLower is "char" or "nchar")
|
||||
{
|
||||
var oldLen = existing.Length ?? 1;
|
||||
var newLen = column.Length ?? 1;
|
||||
lengthChanged = oldLen != newLen;
|
||||
}
|
||||
else if (newTypeLower is "decimal" or "numeric")
|
||||
{
|
||||
var oldPrec = existing.Length ?? 18;
|
||||
var newPrec = column.Length ?? 18;
|
||||
lengthChanged = oldPrec != newPrec;
|
||||
|
||||
var oldScale = existing.Scale ?? 0;
|
||||
var newScale = column.Scale ?? 0;
|
||||
scaleChanged = oldScale != newScale;
|
||||
}
|
||||
|
||||
bool isIdentityChanged = existing.IsIdentity != column.IsIdentity;
|
||||
|
||||
// 仅当字段有实际修改时才执行更新脚本
|
||||
if (!typeChanged && !nullableChanged && !lengthChanged && !scaleChanged && !defaultChanged && !isIdentityChanged)
|
||||
continue;
|
||||
|
||||
// IsIdentity 变更:SQL Server 不支持 ALTER COLUMN 修改 IDENTITY,需通过临时表重建
|
||||
if (isIdentityChanged)
|
||||
{
|
||||
await RebuildTableForIdentityChangeAsync(request, existingByName);
|
||||
var refreshed = await GetTableInfoAsync(request.TableName);
|
||||
foreach (var c in refreshed.Columns)
|
||||
existingByName[c.ColumnName] = c;
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeChanged || nullableChanged || lengthChanged || scaleChanged)
|
||||
{
|
||||
string typeSql;
|
||||
if (newTypeLower is "nvarchar" or "varchar")
|
||||
{
|
||||
var len = column.Length ?? 255;
|
||||
typeSql = $"{newTypeLower}({(len == -1 ? "MAX" : len.ToString())})";
|
||||
}
|
||||
else if (newTypeLower is "char" or "nchar")
|
||||
{
|
||||
var len = column.Length ?? 1;
|
||||
typeSql = $"{newTypeLower}({len})";
|
||||
}
|
||||
else if (newTypeLower is "decimal" or "numeric")
|
||||
{
|
||||
var precision = column.Length ?? 18;
|
||||
var scale = column.Scale ?? 0;
|
||||
typeSql = $"{newTypeLower}({precision}, {scale})";
|
||||
}
|
||||
else
|
||||
{
|
||||
typeSql = newTypeRaw;
|
||||
}
|
||||
|
||||
var nullSql = column.IsNullable ? "NULL" : "NOT NULL";
|
||||
var alterSql = $"ALTER TABLE [{request.TableName}] ALTER COLUMN [{column.ColumnName}] {typeSql} {nullSql}";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(alterSql);
|
||||
}
|
||||
|
||||
if (defaultChanged)
|
||||
{
|
||||
var dfName = $"DF_{request.TableName}_{column.ColumnName}";
|
||||
var dropDfSql = $@"DECLARE @cn NVARCHAR(200);
|
||||
SELECT @cn = dc.name FROM sys.default_constraints dc
|
||||
INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id
|
||||
WHERE c.object_id = OBJECT_ID('{request.TableName.Replace("'", "''")}') AND c.name = N'{column.ColumnName.Replace("'", "''")}';
|
||||
IF @cn IS NOT NULL EXEC('ALTER TABLE [{request.TableName}] DROP CONSTRAINT [' + @cn + ']')";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(dropDfSql);
|
||||
if (!string.IsNullOrWhiteSpace(column.DefaultValue))
|
||||
{
|
||||
var defVal = column.DefaultValue.Trim();
|
||||
var vLower = defVal.ToLower();
|
||||
var innerExpr = vLower is "getdate()" or "newid()" or "sysdatetime()" ? defVal
|
||||
: long.TryParse(defVal, out _) || decimal.TryParse(defVal, out _) ? defVal
|
||||
: vLower == "true" || vLower == "1" ? "1"
|
||||
: vLower == "false" || vLower == "0" ? "0"
|
||||
: $"N'{defVal.Replace("'", "''")}'";
|
||||
var addDfSql = $"ALTER TABLE [{request.TableName}] ADD CONSTRAINT [{dfName}] DEFAULT ({innerExpr}) FOR [{column.ColumnName}]";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(addDfSql);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var existingPkCols = existingTable.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => c.ColumnName).ToList();
|
||||
var newPkCols = request.Columns.Where(c => c.IsPrimaryKey).OrderBy(c => c.Order).Select(c => c.ColumnName).ToList();
|
||||
var pkChanged = !existingPkCols.SequenceEqual(newPkCols);
|
||||
|
||||
if (pkChanged)
|
||||
{
|
||||
// Drop existing PK constraint
|
||||
var dropPkSql = $@"
|
||||
DECLARE @pkConstraintName NVARCHAR(200);
|
||||
SELECT @pkConstraintName = name
|
||||
FROM sys.key_constraints
|
||||
WHERE type = 'PK' AND parent_object_id = OBJECT_ID('{request.TableName}');
|
||||
IF @pkConstraintName IS NOT NULL
|
||||
EXEC('ALTER TABLE [{request.TableName}] DROP CONSTRAINT [' + @pkConstraintName + ']')";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(dropPkSql);
|
||||
|
||||
if (newPkCols.Any())
|
||||
{
|
||||
var pkConstraintName = $"PK_{request.TableName}";
|
||||
var pkColumns = newPkCols.Select(c => $"[{c}]");
|
||||
|
||||
var addPkSql = $"ALTER TABLE [{request.TableName}] ADD CONSTRAINT [{pkConstraintName}] PRIMARY KEY ({string.Join(", ", pkColumns)})";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(addPkSql);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var column in request.Columns.Where(c => !string.IsNullOrWhiteSpace(c.Comment)))
|
||||
{
|
||||
if (existingByName.TryGetValue(column.ColumnName, out var ec) && (ec.Comment ?? "").Trim() == (column.Comment ?? "").Trim())
|
||||
continue;
|
||||
|
||||
var escapedComment = column.Comment!.Replace("'", "''");
|
||||
var tableNameEscaped = request.TableName.Replace("'", "''");
|
||||
var columnNameEscaped = column.ColumnName.Replace("'", "''");
|
||||
try
|
||||
{
|
||||
var updateCommentSql = $@"
|
||||
EXEC sp_updateextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'{escapedComment}',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'{tableNameEscaped}',
|
||||
@level2type = N'COLUMN', @level2name = N'{columnNameEscaped}'";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(updateCommentSql);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var addCommentSql = $@"
|
||||
EXEC sp_addextendedproperty
|
||||
@name = N'MS_Description',
|
||||
@value = N'{escapedComment}',
|
||||
@level0type = N'SCHEMA', @level0name = N'dbo',
|
||||
@level1type = N'TABLE', @level1name = N'{tableNameEscaped}',
|
||||
@level2type = N'COLUMN', @level2name = N'{columnNameEscaped}'";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(addCommentSql);
|
||||
}
|
||||
catch { /* 忽略 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteTableAsync(string tableName)
|
||||
{
|
||||
var dropTableSql = $"DROP TABLE [{tableName}]";
|
||||
await _context.SqlSugarClient.Ado.ExecuteCommandAsync(dropTableSql);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using System;
|
||||
using VolPro.Builder.IServices;
|
||||
using VolPro.Core.Const;
|
||||
using VolPro.Core.DBManager;
|
||||
using VolPro.Core.EFDbContext;
|
||||
using VolPro.Core.Extensions.AutofacManager;
|
||||
using VolPro.Core.Utilities;
|
||||
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
public class TableProviderFactory : ITableProviderFactory, IDependency
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
public TableProviderFactory(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public ITableDatabaseProvider GetProvider(string dbService)
|
||||
{
|
||||
var type = DbRelativeCache.GetDbContextType(dbService);
|
||||
object dbObj = _httpContextAccessor.HttpContext.RequestServices.GetService(type);
|
||||
BaseDbContext _context = dbObj as BaseDbContext;
|
||||
string dbType = DbRelativeCache.GetDbType(dbService);
|
||||
switch (dbType)
|
||||
{
|
||||
case "MySql":
|
||||
return new MySqlTableProvider(_context);
|
||||
case "PgSql":
|
||||
return new PgSqlTableProvider(_context);
|
||||
case "MsSql":
|
||||
return new SqlServerTableProvider(_context);
|
||||
default:
|
||||
throw new Exception("数据库未支持");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
106
api_sqlsugar/VolPro.Builder/Services/DataBase/TableService.cs
Normal file
106
api_sqlsugar/VolPro.Builder/Services/DataBase/TableService.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using VolPro.Builder.IServices;
|
||||
using VolPro.Builder.Services;
|
||||
using VolPro.Core;
|
||||
using VolPro.Core.Extensions.AutofacManager;
|
||||
using VolPro.Core.UserManager;
|
||||
using VolPro.Core.Utilities;
|
||||
|
||||
namespace VolPro.Builder.Services;
|
||||
|
||||
public class TableService : ITableService, IDependency
|
||||
{
|
||||
private readonly ITableProviderFactory _factory;
|
||||
WebResponseContent webResponse = new WebResponseContent();
|
||||
public TableService(ITableProviderFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public async Task<bool> TableExistsAsync(string dbService, string tableName)
|
||||
{
|
||||
var provider = _factory.GetProvider(dbService);
|
||||
return await provider.TableExistsAsync(tableName);
|
||||
}
|
||||
|
||||
public async Task<WebResponseContent> CreateTableAsync(string dbService, CreateTableRequest request)
|
||||
{
|
||||
var provider = _factory.GetProvider(dbService);
|
||||
|
||||
if (await provider.TableExistsAsync(request.TableName))
|
||||
{
|
||||
return webResponse.Error($"{request.TableName}表已存在");
|
||||
}
|
||||
await provider.CreateTableAsync(request);
|
||||
return webResponse.OK("创建成功");
|
||||
}
|
||||
public async Task<object> GetAllTablesAsync(string dbService)
|
||||
{
|
||||
var provider = _factory.GetProvider(dbService);
|
||||
var tables = await provider.GetAllTablesAsync();
|
||||
var tableInfo = TableColumnContext.TableInfo;
|
||||
var list = tables.Select(s => new
|
||||
{
|
||||
table = s,
|
||||
name = tableInfo.Where(x => x.TableName == s).Select(x => x.ColumnCNName).FirstOrDefault() ?? s
|
||||
}).OrderBy(item => !IsStartWithLetter(item.name))
|
||||
.ThenBy(item => item.name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
return list;
|
||||
}
|
||||
private static bool IsStartWithLetter(string str)
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return false;
|
||||
char firstChar = str[0];
|
||||
return (firstChar >= 'a' && firstChar <= 'z') ||
|
||||
(firstChar >= 'A' && firstChar <= 'Z');
|
||||
}
|
||||
|
||||
public async Task<TableInfoDto> GetTableInfoAsync(string dbService, string tableName)
|
||||
{
|
||||
var provider = _factory.GetProvider(dbService);
|
||||
|
||||
var res= await provider.GetTableInfoAsync(tableName);
|
||||
foreach (var item in res.Columns)
|
||||
{
|
||||
item.IsDbField = true;
|
||||
item.OriginalColumnName = item.ColumnName ?? string.Empty;
|
||||
item.Comment = TableColumnContext.Data.Where(x => x.ColumnName == item.ColumnName).Select(s => s.ColumnCnName).FirstOrDefault() ?? item.Comment;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public async Task<WebResponseContent> UpdateTableAsync(string dbService, UpdateTableRequest request)
|
||||
{
|
||||
var provider = _factory.GetProvider(dbService);
|
||||
if (!await provider.TableExistsAsync(request.TableName))
|
||||
{
|
||||
return webResponse.Error($"{request.TableName}不存在");
|
||||
}
|
||||
await provider.UpdateTableAsync(request);
|
||||
return webResponse.OK("修改成功");
|
||||
}
|
||||
|
||||
public async Task<WebResponseContent> DeleteTableAsync(string dbService, string tableName)
|
||||
{
|
||||
|
||||
if (tableName.ToLower().StartsWith("sys"))
|
||||
{
|
||||
return webResponse.Error("系统表不能删除,请在数据库操作".Translator());
|
||||
}
|
||||
var provider = _factory.GetProvider(dbService);
|
||||
|
||||
if (!await provider.TableExistsAsync(tableName)) {
|
||||
return webResponse.Error($"{tableName}不存在");
|
||||
}
|
||||
await provider.DeleteTableAsync(tableName);
|
||||
return webResponse.OK("删除成功");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user