實體對象模型與數據庫對應實現前端
主要是解決實體對象模型與數據庫之間的一一對應,在界面上新增實體對象模型,增長字段,則同步管理業務實體數據庫表結構,主要的思路就是界面上修改了實體模型,同步執行修改數據庫表結構的Sql語句(已經運行了一段時間的業務表,須要DBA實現修改數據庫再修改實體模型),界面大概以下:git
核心代碼:sql
定義抽象類AutoBusinessDbServiceBase,界面增刪改實體對象模型以後,同步執行Sql語句修改不一樣數據庫的修改數據庫表結構的Sql語句,定義抽象類屏蔽不一樣數據庫之間的語句區別。數據庫
public abstract class AutoBusinessDbServiceBase : IAutoBusinessDbService { protected IUnitOfWork _unitOfWork; public AutoBusinessDbServiceBase(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public async Task<bool> CreateTable(SpriteObjectDto spriteObjectDto) { if (CheckTableExists(spriteObjectDto.Name)) { throw new SpriteException("數據庫表已經存在,請聯繫管理員!"); } await DoCreateTable(spriteObjectDto); return await Task.FromResult(true); } /// <summary> /// 判斷數據庫表是否存在 /// </summary> protected abstract bool CheckTableExists(string tableName); /// <summary> /// 執行建立表過程 /// </summary> /// <param name="spriteObjectDto"></param> /// <returns></returns> protected abstract Task<bool> DoCreateTable(SpriteObjectDto spriteObjectDto); public abstract Task<bool> AddObjectProperty(ObjectProperty objectProperty, string tableName); public abstract Task<bool> ModifyObjectProperty(ObjectProperty objectProperty, string tableName); public abstract Task<bool> DeleteObjectProperty(string propertyName, string tableName); }
下面是Mysql數據庫的實現,代碼比較簡單,節約篇幅,不貼代碼了,代碼地址:https://gitee.com/kuangqifu/sprite/blob/master/03_form/CK.Sprite.Form/CK.Sprite.Form.MySql/Domain/DesignTime/MysqlAutoBusinessDb.csexpress
運行時JObject編程編程
Newtonsoft.Json,對於這個組件應該不會陌生,用得比較多的是Json序列化與反序列化,他的核心是圍繞JToken來實現的,他提供了對於Json對象的動態編程能力(固然還有其餘的組件,但用得普遍的仍是這個組件),對於自定義表單的實現,這個就尤爲重要了,前端建立對象、編輯對象、查詢參數等,都是以Json對象格式存儲的,運行時,動態解析Json對象,拼接返回結果並返回給前端使用,都是圍繞着動態Json編程實現的。數組
運行時默認常規方法實現數據結構
常規增刪改查等Sql方法執行,徹底能夠內置實現,這裏採用Dapper來實現的,開源項目實現了Mysql數據庫的實現,參考地址:https://gitee.com/kuangqifu/sprite/blob/master/03_form/CK.Sprite.Form/CK.Sprite.Form.MySql/Repository/MysqlRuntimeRepository.cs,重點介紹部分方法:app
新增業務實體async
前端界面根據規則引擎獲取用戶新增的Json實體對象,最終會調用默認的建立數據庫業務數據的方法,方法內部會根據以前文章介紹的SpriteObject對象進行數據過濾,並自動生成不一樣類型的Id字段值,動態添加新增審計日誌,若是是樹形結構,還會動態維護PId,Code等字段值,調用完成以後,並返回新建立的Id值,代碼以下:
public async Task<JObject> DoDefaultCreateMethodAsync(SpriteObjectDto spriteObjectDto, JObject paramValues, string sqlMethodContent = "") { StringBuilder sbInsertFields = new StringBuilder(); StringBuilder sbInsertValues = new StringBuilder(); var newGuidId = Guid.NewGuid(); if (spriteObjectDto.KeyType == EKeyType.Guid) { sbInsertFields.Append($"{MysqlConsts.PreMark}Id{MysqlConsts.PostMark},"); sbInsertValues.Append($"'{newGuidId}',"); } else { sbInsertFields.Append($"{MysqlConsts.PreMark}Id{MysqlConsts.PostMark},"); sbInsertValues.Append($"0,"); } foreach (var paramValue in paramValues) { var field = paramValue.Key; var findProperty = spriteObjectDto.ObjectPropertyDtos.FirstOrDefault(r => r.Name.ToLower() == field.ToLower()); if (findProperty != null) { if (findProperty.FieldType != EFieldType.String && findProperty.FieldType != EFieldType.Text) { if (string.IsNullOrEmpty(paramValue.Value.ToString())) { paramValues[field] = null; } } sbInsertFields.Append($"{MysqlConsts.PreMark}{field}{MysqlConsts.PostMark},"); sbInsertValues.Append($"@{field},"); } } var tempParamValues = paramValues.DeepClone().ToObject<JObject>(); var nowTime = DateTime.Now; if (spriteObjectDto.IsTree) { CreateTree(sbInsertFields, sbInsertValues, spriteObjectDto, tempParamValues); } if (spriteObjectDto.CreateAudit) { CreateAuditCreate(sbInsertFields, sbInsertValues, nowTime, tempParamValues); } if (spriteObjectDto.ModifyAudit) { CreateAuditUpdate(sbInsertFields, sbInsertValues, nowTime, tempParamValues); } var strInserSql = (string.IsNullOrEmpty(sqlMethodContent) ? SqlDefaultCreate : sqlMethodContent) .Replace("#TableName#", spriteObjectDto.Name) .Replace("#Fields#", sbInsertFields.ToString().TrimEnd(',')) .Replace("#Values#", sbInsertValues.ToString().TrimEnd(',')); JObject result = new JObject(); if (spriteObjectDto.KeyType == EKeyType.Guid) { await _unitOfWork.Connection.ExecuteAsync(strInserSql, tempParamValues.ToConventionalDotNetObject()); result.Add(new JProperty("result", newGuidId)); } else { var resultId = await _unitOfWork.Connection.QueryFirstAsync<int>(strInserSql + "SELECT LAST_INSERT_ID();", tempParamValues.ToConventionalDotNetObject()); result.Add(new JProperty("result", resultId)); } return result; }
其餘幾種默認實現不單獨介紹了,實現比較相似,能夠直接閱讀源碼。另外介紹一下動態Where語句的實現。
Where語句可能會很是的複雜,不少時候直接寫Sql語句的Where方法就很麻煩了,若是要讓自定義表單自動完成Sql語句的封裝,則須要一種不一樣的數據結構才能實現。動態Where的模型採用樹結構實現,稱爲Sql表達式樹,表達式枚舉有三種,And、Or、Condition,核心仍是根據Sql表達式樹生成Where後面的Sql語句,並拼接Dapper執行參數。
模型定義:
public class ExpressSqlModel { public ESqlExpressType SqlExpressType { get; set; } public string Field { get; set; } public EConditionType ConditionType { get; set; } public object Value { get; set; } public List<ExpressSqlModel> Children { get; set; } } public class QueryWhereModel { /// <summary> /// 查詢字段名稱 /// </summary> public string Field { get; set; } /// <summary> /// 等於 = 1,不等於 = 2,Between = 3,In = 4,Like = 5,大於 = 6,大於等於 = 7,小於 = 8,小於等於 = 9,Null = 10,NotNull = 11,NotIn = 12 /// </summary> public EConditionType ConditionType { get; set; } /// <summary> /// **傳遞集合時,直接傳遞數組** /// </summary> public object Value { get; set; } } /// <summary> /// Sql 表達式樹 /// </summary> public enum ESqlExpressType { And = 1, Or = 2, Condition = 3 }
表達式核心方法:
public delegate string CreateSqlWhereDelegate(JObject sqlWhereParamValues, ExpressSqlModel expressSqlModel, ref int index); public class ExpressSqlHelper { public static string CreateSqlWhere(ExpressSqlModel expressSqlModel, JObject sqlWhereParamValues, CreateSqlWhereDelegate createSqlWhereDelegate) { var sqlIndex = 1; if (expressSqlModel.SqlExpressType == ESqlExpressType.Condition) { return createSqlWhereDelegate(sqlWhereParamValues, expressSqlModel, ref sqlIndex); } else { return $"({CreateComplexSql(expressSqlModel, sqlWhereParamValues, ref sqlIndex, createSqlWhereDelegate)})"; } } private static string CreateComplexSql(ExpressSqlModel expressSqlModel, JObject sqlWhereParamValues,ref int sqlIndex, CreateSqlWhereDelegate createSqlWhereDelegate) { string strResutl = ""; string endCondition = ""; if (expressSqlModel.SqlExpressType == ESqlExpressType.And) { endCondition = "AND"; } else { endCondition = "OR"; } int index = 1; foreach (var childExpress in expressSqlModel.Children) { string tempCondition = index == expressSqlModel.Children.Count ? "" : $" {endCondition} "; if (childExpress.SqlExpressType == ESqlExpressType.Condition) { if(childExpress.Value != null) { strResutl += $"{createSqlWhereDelegate(sqlWhereParamValues, childExpress, ref sqlIndex)}{ tempCondition }"; } } else { strResutl += $"({CreateComplexSql(childExpress, sqlWhereParamValues, ref sqlIndex, createSqlWhereDelegate)}){tempCondition}"; } index++; } return strResutl; } public static string TestCreateConditionSql(JObject sqlWhereParamValues, ExpressSqlModel expressSqlModel, ref int index) { string preMark = "`"; string postMark = "`"; var conditionType = expressSqlModel.ConditionType; var field = expressSqlModel.Field; StringBuilder sbSqlWhere = new StringBuilder(); switch (conditionType) { case EConditionType.等於: sbSqlWhere.Append($"{preMark}{field}{postMark}=@SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.Like: sbSqlWhere.Append($"{preMark}{field}{postMark} LIKE CONCAT('%',@SW{index}_{field},'%')"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.In: sbSqlWhere.Append($"{preMark}{field}{postMark} IN @SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.Between: sbSqlWhere.Append($"{preMark}{field}{postMark} BETWEEN @SW{index}_{field}_1 AND @SW{index}_{field}_2"); var inValues = expressSqlModel.Value as ArrayList; sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}_1", inValues[0])); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}_2", inValues[1])); break; case EConditionType.大於: sbSqlWhere.Append($"{preMark}{field}{postMark}>@SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.大於等於: sbSqlWhere.Append($"{preMark}{field}{postMark}>=@SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.小於: sbSqlWhere.Append($"{preMark}{field}{postMark}<@SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.小於等於: sbSqlWhere.Append($"{preMark}{field}{postMark}<=@SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.不等於: sbSqlWhere.Append($"{preMark}{field}{postMark}<>@SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; case EConditionType.Null: sbSqlWhere.Append($"{preMark}{field}{postMark} IS NULL"); break; case EConditionType.NotNull: sbSqlWhere.Append($"{preMark}{field}{postMark} IS NOT NULL"); break; case EConditionType.NotIn: sbSqlWhere.Append($"{preMark}{field}{postMark} NOT IN @SW{index}_{field}"); sqlWhereParamValues.Add(new JProperty($"SW{index}_{field}", expressSqlModel.Value)); break; default: break; } index++; return sbSqlWhere.ToString(); } public static JsonSerializer CreateCamelCaseJsonSerializer() { return new JsonSerializer { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() }; } }
運行時特殊方法執行實現
常規Sql方法不能知足全部的需求,對於複雜的語句,提供了自定義的功能,主要是自定義Sql執行,反射執行自定義添加的方法(還可執行自定義Rpc的調用)。代碼不一一介紹了,參考:https://gitee.com/kuangqifu/sprite/blob/master/03_form/CK.Sprite.Form/CK.Sprite.Form.Core/Domain/RunTime/RuntimeService.cs
這篇文章介紹了自定義表單運行時方法的執行設計實現,有些設計思想仍是能夠拆分出來應用到咱們現有的系統中,好比咱們要實現動態Sql語句查詢,則徹底能夠實現動態Where部分邏輯,由頁面用戶選擇須要哪些查詢字段和查詢條件(好比=、!=、IN、Like等),咱們能夠動態生成Sql where表達式。這部份內容對於自定義表單實現,仍是比較重要的,建議能夠閱讀源碼。