这是 CMS 框架系列文章的第二篇,第一篇开源了该框架的代码和简要介绍了框架的目的、作用和思想,这篇主要解析如何把sql 转成标准 xml 配置文件和把前端post的增删改数据规范成方便后台解析的结构,以实现后端自动化操作数据库。

【开源.NET】 轻量级内容管理框架Grissom.CMS(第一篇分享一个前后端分离框架)

【开源.NET】 轻量级内容管理框架Grissom.CMS(第二篇前后端交互数据结构分析)

【开源.NET】 轻量级内容管理框架Grissom.CMS(第三篇解析配置文件和数据以转换成 sql)

信息管理系统

信息管理系统关键功能:列表分页和搜索、方便数据展示和录入。业务复杂度通常在于多表关联的搜索、录入以及表与表和字段与字段之间的约束,还有就是报表统计了。除去报表不说,其它功能其实就是对数据库表进行增删改查,它们是独立于业务存在的,所以可对它们进行规范化和自动化。

信息管理系统就是为了方便数据的展示和录入,简化为 “需求数据 - SQL - 数据库”, SQL 作为“需求数据” 与“数据库”的中介。想要自动化增删改查,必须要规范化“需求数据的结构”以及添加规范化的“配置文件”,用程序对它们进行分析以生成中介“SQL”。

一、自动化搜索和分页

1、设计图

2、Sql查询转换成规范化的“配置文件”

配置文件是手动配置,由后端程序处理的,不涉及到传输,所以应该选择 XML 格式,可读性和性能都可达到平衡。

先看一下一条简单的 select 语句: Select main.* From VideoMain Where main.IsDeleted != 1,这里有 3 个关键字"Select, From, Where", 它们与业务无关,抽出来,组成xml:

<Select>
main.*
</Select>
<From>
VideoMain main
</From>
<Where>
main.IsDeleted != 1
</Where>

看去非常直观,和写 sql 没多大区别。

再看一条复杂点的 select 语句:

Select main.*, owner.Name as _OwnerName
From VideoMain
Left Join BasOwner owner On owner.Id = main.OwnerId
Where main.IsDeleted != 1 And main.Name like '%教程%'

转成xml配置

<Select>
main.*, owner.Name as _OwnerName
</Select>
<From>
VideoMain
Left Join BasOwner owner On owner.Id = main.OwnerId
</From>
<Where>
main.IsDeleted != 1 And main.Name like '%教程%'
</Where>

不好的是条件写死了,但条件是由前端返回的,应该是动态的。其实 where 条件是由比较符号“=,!=, >, < , >=, <=, like, in, between and” 和逻辑“And, Or”组合起来的 ,可对它们进行规范化。

首先把比较符号转换成有意义的单词,方便配xml,

equal   : =
notequal: !=
bigger : >=
smaller : <=
like : like
in : in

重新规范Where:

<Select>
main.*, owner.Name as _OwnerName
</Select>
<From>
VideoMain
Left Join BasOwner owner On owner.Id = main.OwnerId
</From>
<Where>
<Fields>
<Field Name="IsDeleted" Prefix="main" Cp="notequal"></Field>
<Field Name="Name" Prefix="main" Cp="like"></Field>
</Fields>
</Where>

这样把一条查询的 sql 规范成xml,然后写程序进行解析,就容易了。

3、把规范化的 xml 转换成标准的 sql

看一段解析条件比较的代码:

public string GetSql(string cp, string paraName, string dbname, string value, string sqlExpress = null, string dataType = null, bool isAnd = true, bool isAddQuotes = true)
{
string sql = "";
if (!string.IsNullOrEmpty(sqlExpress))
{
if (isAddQuotes)
{
sql = sqlExpress.Replace("@" + paraName, "'" + ParseValue(value, dataType) + "'");
}
else
{
//sql = sqlExpress.Replace("@" + paraName, ParseValue(value, dataType));
sql = sqlExpress.Replace("@" + paraName, "'" + ParseValue(value, dataType) + "'");
}
}
else
{
value = string.IsNullOrEmpty(dataType) ? value : ParseValue(value, dataType);
string orAnd = isAnd ? "And" : "Or";
switch (cp.ToLower())
{
case "equal":
sql = Equal(dbname, value, orAnd);
break;
case "like":
sql = Like(dbname, value, orAnd);
break;
case "notequal":
sql = NotEqual(dbname, value, orAnd);
break;
case "daterange":
sql = DateRange(dbname, value, orAnd);
break;
case "bigger":
sql = Bigger(dbname, value, orAnd);
break;
case "smaller":
sql = Smaller(dbname, value, orAnd);
break;
case "in":
sql = In(dbname, value, orAnd);
break;
}
}
return sql;
} public string In(string fieldName, string value, string orAnd = "And")
{
return string.Format(" {2} {0} in ('{1}')", fieldName, FilterSql.FilterValue(value).Replace(",", "','"), orAnd);
}
public string Equal(string fieldName, string value, string OrAnd = "And")
{
return string.Format(" {2} {0} = '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
}
public string Like(string fieldName, string value, string OrAnd = "And")
{
return string.Format(" {2} {0} like '%{1}%'", fieldName, FilterSql.FilterValue(value), OrAnd);
}
public string NotEqual(string fieldName, string value, string OrAnd = "And")
{
return string.Format(" {2} {0} <> '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
}
public string Bigger(string fieldName, string value, string OrAnd = "And")
{
return string.Format(" {2} {0} >= '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
}
public string Smaller(string fieldName, string value, string OrAnd = "And")
{
return string.Format(" {2} {0} <= '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
}

有兴趣的,下载源码看,解析 xml 的代码在 Core/Easy.DataProxy 项目下。

4、前端请求:
<div class="dh-form">
<div class="row">
<div class="col2">菜单编码</div>
<div class="col2"><input class="easyui-uc_validatebox" data-bind="value:form.Code" /></div>
<div class="col2">菜单名称</div>
<div class="col2"><input class="easyui-uc_validatebox" data-bind="value:form.Name" /></div>
<div class="col1"><a class="easyui-uc_linkbutton" data-bind="click:_query()">搜索</a></div>
</div>
</div>

form.Code 和 form.Name 对应后台配置文件的 Where:

  <Where>
<Fields>
<Field Name="Code" Prefix="main" Cp="like"></Field>
<Field Name="Name" Prefix="main" Cp="like"></Field>
</Fields>
</Where>
请求 /Bas/Menu/list?pageNumber=1&pageSize=20

请求 /Sys/Menu/list?Code=sys&Name=系统&pageNumber=1&pageSize=20

二、自动化增删改

1、设计图

将前端返回合法的 json 数据结合后台配置好的xml, 经由 EasyCore 解析生成标准的 sql。

2、单表

假设有张视频表,后台管理员可对它进行增删改操作。

在界面“新增或编辑”,点“保存”后,将数据组织成 json 格式 POST 回后台,后台程序解析该 json, 生成 sql 对数据库相应的表进行增删改。

这里有 3 个关键要素: 1.对应的表, 2.对表的操作类型(增、删、该),3.表字段的值。

post 回后台的 json 结构大概是: {tableName:VideoMain, operation:'inserted', data:{Id:1, Name:"test1"}}

如果要兼容批量操作的 json 结构是:

[
{tableName:VideoMain, operation:'inserted', data:{Id:1, Name:"test1"}},
{tableName:VideoMain, operation:'inserted', data:{Id:2, Name:"test2"}},
{tableName:VideoMain, operation:'updated', data:{Id:3, Name:"test3"}},
{tableName:VideoMain, operation:'updated', data:{Id:4, Name:"test4"}},
]

出现好多重复的标签: tableName, operation, data, 抽取出来,简化成:

{
tableName:{
inserted:[{Id:1, Name:"test1"},{Id:2, Name:"test2"}],
updated:[{Id:3, Name:"test3"},{Id:4, Name:"test4"}]
}
}
3、主从表

假设用户可对视频单独进行“评论”或“评分”, 还可以对“评论”进行“顶、踩”,而后台管理员可对它们进行增删改操作,简化的表结构如下:

这就是一个典型的“主 - 从 - 从”的表结构,这里比单表多了一从信息:子表,json 结构如下:

{
VideoMain:{
inserted:[
{
data:{Name:"test1"},
children:{
VideoMark:{
inserted:[
{
data:{Mark:5}
},
{
data:{Mark:4}
}
]
},
VideoComment:{
inserted:[
{
data:{Comment:'good!'},
children:{
VideoCommentUpdown:{
inserted:[
{
data:{IsUp:true}
}
]
}
}
},
{ {
data:{Comment:'very good!'},
children:{
VideoCommentUpdown:{
inserted:[
{
data:{IsUp:true}
}
]
}
}
}
}
]
},
}
},
updated:[
{
data:{Id:3, Name:"test3"}
}
]
]
}
}

json 结构难以看出它的规律,把它换成脑图:

其实就是一颗树,提取它的结构:

从图可看出,树的差异结构最深层级为 4 级,4级之后又从 1 开始。 每一级的意义:

  • 第一级,表名: master/child1/child2;
  • 第二级,对表的操作类型: inserted/updated/deleted;
  • 第三级,批量操作的记录集合(数组);
  • 第四级,左节点:记录的数据,右节点:该条记录的子表数据,子表数据结构重复着 1-4级别;

该 json 结构已很好的携带业务数据信息了,但并不完整,自增字段、主键、外键等约束信息和更新或删除所需的逻辑条件都没有,这些关系到数据库安全的信息不可能开放给前端去配的,所以还需要后台作相关的配置。

4、后台配置
</SqlConfig>
<Table>VideoMain</Table>
<ID>Id</ID>
<PKs>Id</PKs>
<Insert>
<Fields>
<Field Name="CreatedDate" IsIgnore="true"></Field>
<Field Name="UpdatedDate" IsIgnore="true"></Field>
</Fields>
</Insert>
<Update>
<Fields>
<Field Name="CreatedDate" IsIgnore="true"></Field>
<Field Name="UpdatedDate" IsIgnore="true"></Field>
</Fields>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Update>
<Delete>
<DeleteAnyway>false</DeleteAnyway>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Delete>
<Children>
<SqlConfig>
<Table>VideoMark</Table>
<JsonName>marks</JsonName>
<ID>Id</ID>
<PKs>Id</PKs>
<Dependency>
<Fields>
<Field Name="MainId" DependencyName="Id"></Field>
</Fields>
</Dependency>
<Update>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Update>
<Delete>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Delete>
</SqlConfig>
<SqlConfig>
<Table>VideoComment</Table>
<JsonName>comments</JsonName>
<ID>Id</ID>
<PKs>Id</PKs>
<Dependency>
<Fields>
<Field Name="MainId" DependencyName="Id"></Field>
</Fields>
</Dependency>
<Update>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Update>
<Delete>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Delete>
<Children>
<SqlConfig>
<Table>VideoCommentUpdown</Table>
<JsonName>commentUpdowns</JsonName>
<ID>Id</ID>
<PKs>Id</PKs>
<Dependency>
<Fields>
<Field Name="CommentId" DependencyName="Id"></Field>
</Fields>
</Dependency>
<Update>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Update>
<Delete>
<Where>
<Fields>
<Field Name="Id" Cp="equal"></Field>
</Fields>
</Where>
</Delete>
</SqlConfig>
</Children>
</SqlConfig>
</Children>
</SqlConfig>

SqlConfig xml 对应的对象

public class SqlConfig
{
public SqlConfig()
{
this.Where = new Where();
this.Children = new List<SqlConfig>();
this.OrderBy = new OrderBy();
this.GroupBy = new GroupBy();
this.Dependency = new Dependency();
this.Insert = new Insert();
this.Update = new Update();
this.Delete = new Delete();
this.SingleQuery = new SingleQuery();
this.Export = new Export();
this.Import = new Import();
this.BillCodeRule = new BillCodeRule();
} private string _settingName;
/// <summary>
/// 配置名称,默认和表名一致,一般不会用到,方法以后扩展,如一个配置文件出现相同的表时,用来区分不同的配置
/// </summary>
public string SettingName
{
get
{
if (string.IsNullOrEmpty(_settingName))
{
_settingName = Table;
}
return _settingName;
}
set
{
_settingName = value;
}
} #region 查询配置
/// <summary>
/// 查询的字段
/// </summary>
public string Select { get; set; }
/// <summary>
/// 查询的表名以及关联的表名,如 left join, right join
/// </summary>
public string From { get; set; }
/// <summary>
/// 查询的条件
/// 前端返回的查询条件,只有出现在这些配置好的字段,才会生成为了 sql 的 where 条件,
/// 没出现的字段会被忽略
/// </summary>
public Where Where { get; set; }
/// <summary>
/// 分页时必须会乃至的排序规则
/// </summary>
public OrderBy OrderBy { get; set; } public GroupBy GroupBy { get; set; }
/// <summary>
/// 页码
/// </summary>
public int PageNumber { get; set; }
/// <summary>
/// 页大小
/// </summary>
public int PageSize { get; set; } #endregion 查询配置 /// <summary>
/// 指定该配置所属于的表
/// </summary>
public string Table { get; set; } #region 增删改配置
/// <summary>
/// 对应前端返回的 json 格式数据的键名
/// e.g.: {master:{inserted:[{data:{}}]}} 中的 master 就是这里要对应的 JsonName
/// 注意默认主表的 jsonName 是 master, 所以主表一般可省略不写, 但子表必须得指定
/// </summary>
public string JsonName { get; set; }
/// <summary>
/// 自增的字段,指定了自增的字段,在 insert 时会自动忽略该字段
/// </summary>
public string ID { get; set; }
/// <summary>
/// 主键, 在保存成功后会返回主键的值;
/// </summary>
public string PKs { get; set; }
/// <summary>
/// 唯一值的字段,对应数据库 unique, 在 insert,update 前会判断是否已存在
/// </summary>
public string Uniques { get; set; }
/// <summary>
/// 唯一值的字段的值是否允许为空
/// </summary>
public string UniqueAllowEmptys { get; set; }
/// <summary>
/// 所属的父级配置, 在 xml 中不用指定,程序会自动分析
/// </summary>
public SqlConfig Parent { get; set; }
/// <summary>
/// 包含的子级配置, 即子表的配置,需要在 xml 中配置
/// </summary>
public List<SqlConfig> Children { get; set; }
/// <summary>
/// 依赖父表的字段
/// </summary>
public Dependency Dependency { get; set; }
/// <summary>
/// insert 的配置
/// </summary>
public Insert Insert { get; set; }
/// <summary>
/// update 的配置
/// </summary>
public Update Update { get; set; }
/// <summary>
/// delete 的配置
/// </summary>
public Delete Delete { get; set; }
#endregion
/// <summary>
/// 单条记录查询的配置,一般用在配置列表双击弹出那条记录的获取的 sql
/// </summary>
public SingleQuery SingleQuery { get; set; }
/// <summary>
/// 导出配置
/// </summary>
public Export Export { get; set; }
/// <summary>
/// 导入配置
/// </summary>
public Import Import { get; set; }
/// <summary>
/// 是否物理删除?
/// </summary>
public bool DeleteAnyway { get; set; }
/// <summary>
/// 表单编码的生成配置
/// </summary>
public BillCodeRule BillCodeRule { get; set; } }
5、批量增删改截图

单表

主-从

主-从-从

源码 https://github.com/grissomlau/Grissom.CMS

初始化登录名:admin, 密码: 123

【开源.NET】 轻量级内容管理框架Grissom.CMS(第二篇前后端交互数据结构分析)的更多相关文章

  1. 【开源.NET】轻量级内容管理框架Grissom.CMS(第三篇解析配置文件和数据以转换成 sql)

    该篇是 Grissom.CMS 框架系列文章的第三篇, 主要介绍框架用到的核心库 EasyJsonToSql, 把标准的配置文件和数据结构解析成可执行的 sql. 该框架能实现自动化增删改查得益于 E ...

  2. 【开源.NET】 分享一个前后端分离的轻量级内容管理框架

    开发框架要考虑的面太多了:安全.稳定.性能.效率.扩展.整洁,还要经得起实践的考验,从零开发一个可用的框架,是很耗时费神的工作.网上很多开源的框架,为何还要自己开发?我是基于以下两点: 没找到合适的: ...

  3. 第二篇:Power BI数据可视化之基于Web数据的报表制作(经典级示例)

    前言 报表制作流程的第一步显然是从各个数据源导入数据,Power BI能从很多种数据源导入数据:如Excel,CSV,XML,以及各类数据库(SQL Server,Oracle,My SQL等),两大 ...

  4. 开源项目之ASP.NET Core + Vue.js 的前后端分离的通用后台管理系统框架

    年前看了这个开源项目感觉很不错,这个小项目对于传统的.net 开发人员,想做技术提升是一个很不错的参考案例. 开源项目演示地址:https://dnczeus.codedefault.com/logi ...

  5. 第二篇:R语言数据可视化之数据塑形技术

    前言 绘制统计图形时,半数以上的时间会花在调用绘图命令之前的数据塑型操作上.因为在把数据送进绘图函数前,还得将数据框转换为适当格式才行. 本文将给出使用R语言进行数据塑型的一些基本的技巧,更多技术细节 ...

  6. 七个开源的 Spring Boot 前后端分离项目,一定要收藏!

    前后端分离已经在慢慢走进各公司的技术栈,根据松哥了解到的消息,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,松哥也非常建议大家学习一下前后端分离开发,以免在公司干了两三年 ...

  7. 八个开源的 Spring Boot 前后端分离项目,一定要收藏!

    八个开源的 Spring Boot 前后端分离项目 最近前后端分离已经在慢慢走进各公司的技术栈,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,我们也非常建议大家学习一下前 ...

  8. [转]Android开源项目第二篇——工具库篇

    本文为那些不错的Android开源项目第二篇--开发工具库篇,主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容.多 ...

  9. Android开源项目第二篇——工具库篇

    本文为那些不错的Android开源项目第二篇——开发工具库篇,**主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容 ...

随机推荐

  1. CSS| text文本属性

    注意:一般来说,可以为所有块级元素应用 text-indent,但无法将该属性应用于行内元素,图像之类的替换元素上也无法应用 text-indent 1)  text-indent 取值: 5px/2 ...

  2. 4种更快更简单实现Python数据可视化的方法

    数据可视化是数据分析或机器学习项目中十分重要的一环.通常,你需要在项目初期进行探索性的数据分析(EDA),从而对数据有一定的了解,而且创建可视化确实可以使分析的任务更清晰.更容易理解,特别是对于大规模 ...

  3. 转:总结const、readonly、static三者的区别

    const:静态常量,也称编译时常量(compile-time constants),属于类型级,通过类名直接访问,被所有对象共享! a.叫编译时常量的原因是它编译时会将其替换为所对应的值: b.静态 ...

  4. UNIX高级环境编程(11)进程控制(Process Control)- 进程快照,用户标识符,进程调度

    1 进程快照(Process Accounting) 当一个进程终止时,内核会为该进程保存一些数据,包括命令的小部分二进制数据.CPU time.启动时间.用户Id和组Id.这样的过程称为proces ...

  5. November 01st, 2017 Week 44th Wednesday

    People always want to lead an active life, and is not it? 人们总要乐观生活,不是吗? Be active, and walk towards ...

  6. 团队作业6--展示博客(Alpha版本)

    一.团队展示: 1.队名:软件1412--博客管理系统 2.队员学号(标记组长) 曾海明(组长):201421122036   周雅静(组员):201421122003  王珏(组员):2014211 ...

  7. python 使用csv 文件写入 出现多余空行数据解决方案

    因为csv.writerow() 方法会造成读取时每条数据后多一条空数据 解决方案如下: 分为两种情况 python2 和 python3 先说python2版本 with open('xxx.csv ...

  8. 绕过安全狗狗的WebShell for PHP

    最近发现一款过狗shell,分享下...     本地搭建2008SERVER+php5+阿帕奇+网站安全狗+服务器安全狗+防护全开 测试可用... 默认密码:p0tt1 使用方法: ,没关系,按p键 ...

  9. 2-6 R语言基础 缺失值

    #缺失值 Missing Value > #NaN不可识别NA> x <- c(1,NA,2,NA,3) > is.na(x)[1] FALSE TRUE FALSE TRUE ...

  10. RedHat Enterprise Linux 6.4使用Centos 6的yum源问题

    RedHat Enterprise Linux 6.4使用Centos 6的yum源问题 作为一名新手,学习Linux已经一个月了,其间遇到了不少问题,而今天笔者遇到的问题是 #yum install ...