zoukankan      html  css  js  c++  java
  • 企业级自定义表单引擎解决方案(四)--实体对象模型实现

    实体对象模型与数据库对应实现

      主要是解决实体对象模型与数据库之间的一一对应,在界面上新增实体对象模型,增加字段,则同步管理业务实体数据库表结构,主要的思路就是界面上修改了实体模型,同步执行修改数据库表结构的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表达式。这部分内容对于自定义表单实现,还是比较重要的,建议可以阅读源码。

  • 相关阅读:
    vue报错 ModuleBuildError: Module build failed: Error: `sass-loader` requires `node-sass` >=4. Please install a compatible version.
    js 相对路径转为绝对路径
    查询数据库表结构,默认值,是否为空等
    NOLOCK、HOLDLOCK、UPDLOCK、TABLOCK、TABLOCKX
    【58沈剑架构系列】RPC-client异步收发核心细节?
    Windows下性能最好的I/O模型——完成端口
    链表的基本操作
    逐步优化求解最大子序列和
    python 遍历文件夹
    Markdown笔记
  • 原文地址:https://www.cnblogs.com/spritekuang/p/14534657.html
Copyright © 2011-2022 走看看