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

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

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

相關文章
相關標籤/搜索