企業級自定義表單引擎解決方案(四)--實體對象模型實現


實體對象模型與數據庫對應實現

  主要是解決實體對象模型與數據庫之間的一一對應,在界面上新增實體對象模型,增加字段,則同步管理業務實體數據庫表結構,主要的思路就是界面上修改了實體模型,同步執行修改數據庫表結構的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表達式。這部分內容對於自定義表單實現,還是比較重要的,建議可以閱讀源碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM