后端api
public class CodeGenApp
{
//上下文,用来区分是哪个数据库,下面GenerateModel()方法中,用哪个上下文就会查询哪个库的表
private readonly LandContext _ctx;
private readonly CRMContext _crm;
private readonly WMSContext _wms;
public CodeGenApp(LandContext ctx, CRMContext crm, WMSContext wms)
{
_ctx = ctx;
_crm = crm;
_wms = wms;
}
public string GenerateModel(GenerateModelReq req)
{
if (req == null || string.IsNullOrWhiteSpace(req.TableName)) throw new Exception("请输入表名");
var table = req.TableName.Trim();
var ns = string.IsNullOrWhiteSpace(req.Namespace) ? "LandWebApi.Models" : req.Namespace.Trim();
var className = string.IsNullOrWhiteSpace(req.ClassName) ? ToPascal(table) : req.ClassName.Trim();
var tableAttr = string.IsNullOrWhiteSpace(req.TableAttributeName) ? table : req.TableAttributeName.Trim();
// 读取列信息(含列注释),限定当前数据库
var cols = _ctx.Database.SqlQuery<ColInfo>(@"
SELECT c.ORDINAL_POSITION AS `Order`, c.COLUMN_NAME AS `Name`, c.DATA_TYPE AS `DataType`,
c.CHARACTER_MAXIMUM_LENGTH AS `MaxLen`, c.IS_NULLABLE AS `IsNullable`, c.COLUMN_COMMENT AS `Comment`
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME = @p0 AND c.TABLE_SCHEMA = DATABASE()
ORDER BY c.ORDINAL_POSITION" , table).ToList();
if (cols == null || cols.Count == 0) throw new Exception($"未在数据库中找到表: {table}");
// 主键列(仅取第一个作为 [Key] 标注)
var pk = _ctx.Database.SqlQuery<string>(@"
SELECT k.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS t
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
ON t.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND t.TABLE_NAME = k.TABLE_NAME
WHERE t.TABLE_NAME = @p0 AND t.TABLE_SCHEMA = DATABASE() AND t.CONSTRAINT_TYPE = 'PRIMARY KEY'", table).FirstOrDefault();
// 表注释
var tableComment = _ctx.Database.SqlQuery<string>(@"
SELECT TABLE_COMMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = @p0 AND TABLE_SCHEMA = DATABASE()", table).FirstOrDefault();
var sb = new StringBuilder();
sb.AppendLine("using System;");
sb.AppendLine("using System.ComponentModel.DataAnnotations;");
sb.AppendLine("using System.ComponentModel.DataAnnotations.Schema;");
sb.AppendLine();
sb.AppendLine($"namespace {ns}");
sb.AppendLine("{");
sb.AppendLine(" /// <summary>");
sb.AppendLine(!string.IsNullOrWhiteSpace(tableComment)
? $" /// {tableComment}"
: $" /// Model for table: {table}");
sb.AppendLine(" /// </summary>");
sb.AppendLine($" [Table(\"{tableAttr}\")] ");
sb.AppendLine($" public class {className}");
sb.AppendLine(" {");
foreach (var col in cols)
{
var isPk = !string.IsNullOrEmpty(pk) && string.Equals(pk, col.Name, StringComparison.OrdinalIgnoreCase);
var csType = ToCSharpType(col.DataType, col.IsNullable);
var propName = ToPropertyName(col.Name);
var needColumnAttr = !string.Equals(propName, col.Name, StringComparison.Ordinal); // 保留原始列名映射
sb.AppendLine(" /// <summary>");
sb.AppendLine(!string.IsNullOrWhiteSpace(col.Comment)
? $" /// {col.Comment}"
: $" /// {col.Name}");
sb.AppendLine(" /// </summary>");
if (isPk) sb.AppendLine(" [Key]");
if (needColumnAttr) sb.AppendLine($" [Column(\"{col.Name}\")]");
sb.AppendLine($" public {csType} {propName} {{ get; set; }}");
}
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
/// <summary>
/// 将数据库列名转换为合法的 C# 属性名,保留语义,同时生成 Column 特性映射原始列名
/// </summary>
private static string ToPropertyName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return name;
// 按下划线、空格、短横线拆分再拼接为 PascalCase
var parts = name.Split(new[] { '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
var pascal = string.Join("", parts.Select(p => char.ToUpperInvariant(p[0]) + (p.Length > 1 ? p.Substring(1) : "")));
// 若开头不是字母或下划线,前置下划线使其成为合法标识符
if (!Regex.IsMatch(pascal, @"^[_a-zA-Z]"))
{
pascal = "_" + pascal;
}
return pascal;
}
/// <summary>
/// 将表名/类名转换为 PascalCase(生成类名用)
/// </summary>
private static string ToPascal(string name)
{
if (string.IsNullOrWhiteSpace(name)) return name;
var parts = name.Split(new[] { '_', ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
var pascal = string.Join("", parts.Select(p => char.ToUpperInvariant(p[0]) + (p.Length > 1 ? p.Substring(1) : "")));
if (!Regex.IsMatch(pascal, @"^[_a-zA-Z]"))
{
pascal = "_" + pascal;
}
return pascal;
}
private static string ToCSharpType(string sqlType, string isNullable)
{
bool nullable = string.Equals(isNullable, "YES", StringComparison.OrdinalIgnoreCase);
string t = sqlType?.ToLowerInvariant();
string core;
switch (t)
{
case "int": core = "int"; break;
case "bigint": core = "long"; break;
case "smallint": core = "short"; break;
case "tinyint": core = "byte"; break;
case "bit": core = "int"; break; // 项目中多用 int? 表示
case "decimal":
case "numeric":
case "money":
case "smallmoney": core = "decimal"; break;
case "float": core = "double"; break;
case "real": core = "float"; break;
case "datetime":
case "datetime2":
case "smalldatetime":
case "date": core = "DateTime"; break;
case "time": core = "TimeSpan"; break;
case "uniqueidentifier": core = "Guid"; break;
default: return "string"; // varchar, nvarchar, text, etc.
}
return nullable ? core + "?" : core;
}
private class ColInfo
{
public long Order { get; set; }
public string Name { get; set; }
public string DataType { get; set; }
public long? MaxLen { get; set; }
public string IsNullable { get; set; }
public string Comment { get; set; }
}
}
前端
<template>
<div>
<el-form :model="formData" label-width="100px" inline>
<el-form-item label="表名" required>
<el-input v-model="formData.TableName" placeholder="如 Land_SelfInventory_Main 或 shop_goods" style="width:260px" />
</el-form-item>
<el-form-item label="命名空间">
<el-input v-model="formData.Namespace" placeholder="默认 LandWebApi.Models" style="width:260px" />
</el-form-item>
<el-form-item label="类名">
<el-input v-model="formData.ClassName" placeholder="可选,不填自动推断" style="width:220px" />
</el-form-item>
<el-form-item label="Table特性">
<el-input v-model="formData.TableAttributeName" placeholder="可选,默认等于表名" style="width:220px" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleGenerate">生成Model</el-button>
<el-button size="small" @click="handleCopy" :disabled="!code">复制代码</el-button>
</el-form-item>
</el-form>
<el-alert type="info" :closable="false" show-icon style="margin-bottom:8px" description="输入表名,调用后端接口读取数据库结构并生成C# Model源码。" />
<el-input v-model="code" type="textarea" :rows="24" placeholder="生成的代码将显示在这里" />
</div>
</template>
<script>
import { genModel } from './api/api'
export default {
name: 'codeGenModel',
data () {
return {
formData: { TableName: '', Namespace: 'LandWebApi.Models', ClassName: '', TableAttributeName: '' },
code: ''
}
},
methods: {
handleGenerate () {
this.formData.TableName = (this.formData.TableName || '').trim()
if (!this.formData.TableName) return this.$message.warning('请输入表名')
const payload = { ...this.formData }
console.log('[CodeGen] 请求参数:', payload)
genModel(payload).then(res => {
const data = (res && res.data) || res || {}
const ok = (data.Code === 200) || (res && res.status === 200)
if (!ok) return this.$message.error(data.Message || '生成失败')
this.code = data.Data || ''
})
},
handleCopy () {
if (!this.code) return
const ta = document.createElement('textarea')
ta.value = this.code
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)
this.$message.success('已复制到剪贴板')
}
}
}
</script>
<style scoped>
</style>