想要用程式碼寫程式碼,肯定是繞不開反射的。反射的概念相比都不陌生,只是應用多少就因人而異,今天分享一個程式碼生成器的思路,僅供參考,不要過分依賴哦。
思路分析
眾所周知,利用反射可以在程式執行時獲取到任一物件的型別、屬性、引數、方法等,並加以呼叫,利用這些獲取到的可以在程式執行時追加各種自定義的功能。以CRUD為例,我們可以利用反射獲取到所有的Model並編寫程式碼模板,最終達成用程式碼生成程式碼的結果。思路既然能走通,開搞開搞~
編碼階段
首先要確定生成哪些程式碼。CRUD每天都在做,但大部分都一樣,這就導致每天有些時間都在板磚,毫無意義。所以我們需要自動生成全庫的CRUD程式碼。
首先要寫一個CRUD的模板程式碼,考慮到ORM框架太多,這裡就以SqlSugar為例:
[HttpGet]
public async Task<IActionResult> GetList(int index = 1, int size = 15)
{
RefAsync<int> count = 0;
return PageMsg(await _sqlHelper.DB
.Queryable<Models.AD>()
.OrderBy(x => x.Weight)
.ToPageListAsync(index, size, count), count.Value);
}
[HttpPost, Role("新增廣告")]
public async Task<IActionResult> Add([FromForm] Models.AD ad)
{
Models.AD add = await _sqlHelper.DB
.Insertable(ad)
.RemoveDataCache()
.ExecuteReturnEntityAsync();
return Ok(add);
}
[HttpPut, Role("修改廣告")]
public async Task<IActionResult> Cag([FromForm] Models.AD ad)
{
int result = await _sqlHelper.DB
.Updateable(ad)
.RemoveDataCache()
.ExecuteCommandAsync();
return YesOrNo(result > 0);
}
[HttpDelete]
public async Task<IActionResult> Delete([FromForm] int id)
{
int result = await _sqlHelper.DB
.Deleteable<Models.AD>()
.Where(x => x.ID == id)
.ExecuteCommandAsync();
return YesOrNo(result > 0);
}
能看出這是一組操作AD類的CRUD,接下來就是需要把上面的模板封裝起來,AD這種表的名稱作為變數輸入即可,如果想控制程式碼檔案的路徑,也可以作為引數傳入。完整版:
public void Make(string tableName, string path)
{
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
using FileStream fs = new FileStream($"{path}/{tableName}Controller.cs", FileMode.Append);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("using System.Threading.Tasks;");
sb.Append("\n");
sb.Append("using XXXX.Services.DB;");
sb.Append("\n");
sb.Append("using Microsoft.AspNetCore.Authorization;");
sb.Append("\n");
sb.Append("using Microsoft.AspNetCore.Mvc;");
sb.Append("\n");
sb.Append("using SqlSugar;");
sb.Append("\n");
sb.Append("namespace XXXX.Controllers.v1");
sb.Append("\n");
sb.Append("{");
sb.Append("\n");
sb.Append(" [ApiController, Route(\"v1/[controller]/[action]\"), Authorize]");
sb.Append("\n");
sb.Append($" public class {tableName}Controller : BaseController");
sb.Append("\n");
sb.Append(" {");
sb.Append("\n");
sb.Append(" private readonly SqlHelper _sqlHelper;");
sb.Append("\n");
sb.Append($" public {tableName}Controller(SqlHelper sqlHelper)");
sb.Append("\n");
sb.Append(" {");
sb.Append("\n");
sb.Append(" _sqlHelper = sqlHelper;");
sb.Append("\n");
sb.Append(" }");
sb.Append("\n");
sb.Append(" [HttpGet]");
sb.Append("\n");
sb.Append($" public async Task<IActionResult> GetList(int index = 1, int size = 15)");
sb.Append("\n");
sb.Append(" {");
sb.Append("\n");
sb.Append($" RefAsync<int> count = 0;");
sb.Append("\n");
sb.Append(" return Ok(new{");
sb.Append("\n");
sb.Append($" rows = await _sqlHelper.DB.Queryable<Models.{tableName}>().ToPageListAsync(index, size, count),");
sb.Append("\n");
sb.Append(" total = count.Value");
sb.Append("\n");
sb.Append(" });");
sb.Append("\n");
sb.Append(" }");
sb.Append("\n");
sb.Append(" [HttpPost]");
sb.Append("\n");
sb.Append($" public async Task<IActionResult> Add([FromForm] Models.{tableName} {tableName.ToLower()})");
sb.Append("\n");
sb.Append(" {");
sb.Append("\n");
sb.Append($" Models.{tableName} add = await _sqlHelper.DB.Insertable({tableName.ToLower()}).RemoveDataCache().ExecuteReturnEntityAsync();");
sb.Append("\n");
sb.Append(" return Ok(add);");
sb.Append("\n");
sb.Append(" }");
sb.Append("\n");
sb.Append(" [HttpPut]");
sb.Append("\n");
sb.Append($" public async Task<IActionResult> Cag([FromForm] Models.{tableName} {tableName.ToLower()})");
sb.Append("\n");
sb.Append(" {");
sb.Append("\n");
sb.Append($" int result = await _sqlHelper.DB.Updateable({tableName.ToLower()}).RemoveDataCache().ExecuteCommandAsync();");
sb.Append("\n");
sb.Append(" return YesOrNo(result>0);");
sb.Append("\n");
sb.Append(" }");
sb.Append("\n");
sb.Append(" [HttpDelete]");
sb.Append("\n");
sb.Append($" public async Task<IActionResult> Cag([FromForm] int id)");
sb.Append("\n");
sb.Append(" {");
sb.Append("\n");
sb.Append($" int result = await _sqlHelper.DB.Deleteable<Models.{tableName}>().Where(x => x.ID == id).ExecuteCommandAsync();");
sb.Append("\n");
sb.Append(" return YesOrNo(result>0);");
sb.Append("\n");
sb.Append(" }");
sb.Append("\n");
sb.Append(" }");
sb.Append("\n");
sb.Append("}");
using StreamWriter sw = new StreamWriter(fs);
sw.WriteLine(sb.ToString());
}
為了方便理解,刻意美化了一下。
OK,程式碼模板搞好了,該從哪裡拿到所有的Model呢?思路是先載入程式集,然後找到存放Model的名稱空間,然後找到名稱空間下面所有符合條件的Class,然後就可以拿到具體的名稱,從而呼叫程式碼模板進行檔案生成,像這樣:
var assembly = Assembly.Load("程式集名稱");
var types = assembly.ExportedTypes.Where(a => a.FullName.Contains("Model所處的名稱空間")).ToList();
//指定
string path = $"{Directory.GetCurrentDirectory()}/autoCode";
foreach (var item in types)
{
string tableName = item.FullName.Split('.')[^1];
Make(tableName, path);
}
接下來直接執行,走完以後的結果是這樣的:
隨便開啟一個檔案,是這樣的:
效果還是不錯的。
如果需要更加精細的控制,可以預先在Model設定特性,然後通過判斷特性是否存在來決定如何生成程式碼,舉個例子:
var assembly = Assembly.Load("程式集名稱");
var types = assembly.ExportedTypes.Where(a => a.FullName.Contains("Model所處的名稱空間")).ToList();
//指定
string path = $"{Directory.GetCurrentDirectory()}/autoCode";
foreach (var item in types)
{
//獲取所有公開屬性
foreach (var prop in item.GetProperties())
{
MyAttribute attr = method.GetCustomAttribute(typeof(MyAttribute), true) as MyAttribute;
if(attr is null)
{
//進入這裡代表該屬性沒有附加 MyAttribute
}
}
}
當然,還可以獲取MyAttribute的屬性拿過來進行判斷等等。而實際上很多ORM框架也是用類似手段實現Code First、DB First,不過複雜程度比較高。
這樣批量生成程式碼有點像活字印刷,雖然可以解決大量機械重複的工作,但靈活度還是不夠高,人腦還是無法替代的。
奇技淫巧,見笑了。