在傳統 ADO.NET 開發中,手寫實體類別與 CRUD 預存程序/參數化 SQL 既繁瑣又易出錯。本文介紹一個基於 VS2005 + SQL Server 2005 的代碼生成器核心邏輯,自動完成:
private static string GetClrType(string sqlType)
{
return sqlType.Trim().ToLower() switch
{
"bigint" => "long",
"binary" => "byte[]",
"bit" => "bool",
"char" => "string",
"datetime" => "DateTime",
"decimal" => "decimal",
"float" => "double",
"image" => "byte[]",
"int" => "int",
"money" => "decimal",
"nchar" => "string",
"ntext" => "string",
"nvarchar" => "string",
"real" => "float",
"smalldatetime" => "DateTime",
"smallint" => "short",
"smallmoney" => "decimal",
"text" => "string",
"timestamp" => "byte[]",
"tinyint" => "byte",
"uniqueidentifier" => "Guid",
"varbinary" => "byte[]",
"varchar" => "string",
"xml" => "string",
_ => throw new ArgumentException($"Unsupported SQL type: {sqlType}")
};
}
private static string GetSqlDbType(string sqlType)
{
return sqlType.Trim().ToLower() switch
{
"bigint" => "SqlDbType.BigInt",
"bit" => "SqlDbType.Bit",
"datetime" => "SqlDbType.DateTime",
"decimal" => "SqlDbType.Decimal",
"float" => "SqlDbType.Float",
"int" => "SqlDbType.Int",
"money" => "SqlDbType.Money",
"nvarchar" => "SqlDbType.NVarChar",
"uniqueidentifier" => "SqlDbType.UniqueIdentifier",
"varchar" => "SqlDbType.VarChar",
// ... 其他型別
_ => throw new ArgumentException($"Unknown SqlDbType: {sqlType}")
};
}
public List LoadTables(string connStr)
{
const string sql = @"
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
ORDER BY TABLE_NAME";
using var conn = new SqlConnection(connStr);
using var cmd = new SqlCommand(sql, conn);
conn.Open();
using var reader = cmd.ExecuteReader();
var tables = new List();
while (reader.Read())
tables.Add(new TableSchema { Name = reader.GetString(0) });
return tables;
}
public List LoadColumns(string connStr, string tableName)
{
const string sql = @"
SELECT c.COLUMN_NAME, c.DATA_TYPE, c.CHARACTER_MAXIMUM_LENGTH,
c.NUMERIC_PRECISION, c.NUMERIC_SCALE,
COLUMNPROPERTY(OBJECT_ID(@Table), c.COLUMN_NAME, 'IsIdentity') AS IsIdentity,
CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey
FROM INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN (
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_NAME = @Table AND CONSTRAINT_NAME LIKE 'PK%'
) pk ON c.COLUMN_NAME = pk.COLUMN_NAME
WHERE c.TABLE_NAME = @Table
ORDER BY c.ORDINAL_POSITION";
using var conn = new SqlConnection(connStr);
using var cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@Table", tableName);
conn.Open();
using var reader = cmd.ExecuteReader();
var columns = new List();
while (reader.Read())
{
columns.Add(new ColumnSchema
{
Name = reader.GetString(0),
DataType = reader.GetString(1),
MaxLength = reader.IsDBNull(2) ? 0 : reader.GetInt32(2),
Precision = reader.IsDBNull(3) ? 0 : reader.GetByte(3),
Scale = reader.IsDBNull(4) ? 0 : reader.GetByte(4),
IsIdentity = reader.GetInt32(5) == 1,
IsPrimaryKey = reader.GetInt32(6) == 1
});
}
return columns;
}
public string GenerateEntityClass(string tableName, List columns)
{
var sb = new StringBuilder();
sb.AppendLine("using System;");
sb.AppendLine($"namespace MyProject.Entities {{");
sb.AppendLine($"\tpublic partial class {tableName} {{");
// 屬性
foreach (var col in columns)
{
string clrType = GetClrType(col.DataType);
sb.AppendLine($"\t\tpublic {clrType} {col.Name} {{ get; set; }}");
}
sb.AppendLine("\t}");
sb.AppendLine("}");
return sb.ToString();
}
private string GenerateInsertMethod(string tableName, List columns)
{
var sb = new StringBuilder();
var nonIdentityCols = columns.Where(c => !c.IsIdentity).ToList();
sb.AppendLine($"\tpublic int Insert({tableName} entity, SqlTransaction tran = null) {{");
sb.AppendLine($"\t\tconst string sql = @\"INSERT INTO {tableName} (");
sb.AppendLine($"\t\t{string.Join(", ", nonIdentityCols.Select(c => c.Name))}");
sb.AppendLine($"\t\t) VALUES (");
sb.AppendLine($"\t\t{string.Join(", ", nonIdentityCols.Select(c => "@" + c.Name))}");
sb.AppendLine($"\t\t); SELECT SCOPE_IDENTITY();\";");
sb.AppendLine($"\t\tvar parameters = new SqlParameter[{nonIdentityCols.Count}];");
for (int i = 0; i < nonIdentityCols.Count; i++)
{
var col = nonIdentityCols[i];
sb.AppendLine($"\t\tparameters[{i}] = new SqlParameter(\"@{col.Name}\", {GetSqlDbType(col.DataType)}) {{ Value = entity.{col.Name} ?? DBNull.Value }};");
if (col.MaxLength > 0 && (col.DataType.Contains("char") || col.DataType.Contains("varchar")))
sb.AppendLine($"\t\tparameters[{i}].Size = {col.MaxLength};");
}
sb.AppendLine($"\t\treturn Convert.ToInt32(SqlHelper.ExecuteScalar(tran, CommandType.Text, sql, parameters));");
sb.AppendLine($"\t}}");
return sb.ToString();
}
| 層級 | 職責 | 生成產出 |
|---|---|---|
| Entity | 純資料容器,無業務邏輯 | Partial Class,便於擴充 |
| IDAL | 定義資料存取契約 | 介面,支援多資料庫實現 |
| DAL (SqlServer) | 具體 SQL 實現,參數化防注入 | 類別,含事務/分頁/批次操作 |
| BLL | 業務規則、驗證、事務協調 | 手寫,引用 IDAL 介面 |
現在專案建議直接採用成熟 ORM:
代碼生成器在 ORM 不普及時代極大提升了開發效率。理解其核心原理——元數據讀取、型別映射、模板引擎——有助於在現代框架中進行客製化擴充(如自動生成 DTO、Repository、單元測試骨架等)。