1. 程式人生 > >企業級自定義表單引擎解決方案(三)--實體物件模型實現

企業級自定義表單引擎解決方案(三)--實體物件模型實現

實體物件模型與資料庫對應實現

  主要是解決實體物件模型與資料庫之間的一一對應,在介面上新增實體物件模型,增加欄位,則同步管理業務實體資料庫表結構,主要的思路就是介面上修改了實體模型,同步執行修改資料庫表結構的Sql語句(已經運行了一段時間的業務表,需要DBA實現修改資料庫再修改實體模型),介面大概如下:

 

 

  核心程式碼:

  定義抽象類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.cs

執行時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,重點介紹部分方法:

  新增業務實體

  前端介面根據規則引擎獲取使用者新增的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表示式。這部分內容對於自定義表單實現,還是比較重要的,建議可以閱讀原始碼。

&n