【开源.NET】 轻量级内容管理框架Grissom.CMS(第二篇前后端交互数据结构分析)
这是 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(第二篇前后端交互数据结构分析)的更多相关文章
- 【开源.NET】轻量级内容管理框架Grissom.CMS(第三篇解析配置文件和数据以转换成 sql)
该篇是 Grissom.CMS 框架系列文章的第三篇, 主要介绍框架用到的核心库 EasyJsonToSql, 把标准的配置文件和数据结构解析成可执行的 sql. 该框架能实现自动化增删改查得益于 E ...
- 【开源.NET】 分享一个前后端分离的轻量级内容管理框架
开发框架要考虑的面太多了:安全.稳定.性能.效率.扩展.整洁,还要经得起实践的考验,从零开发一个可用的框架,是很耗时费神的工作.网上很多开源的框架,为何还要自己开发?我是基于以下两点: 没找到合适的: ...
- 第二篇:Power BI数据可视化之基于Web数据的报表制作(经典级示例)
前言 报表制作流程的第一步显然是从各个数据源导入数据,Power BI能从很多种数据源导入数据:如Excel,CSV,XML,以及各类数据库(SQL Server,Oracle,My SQL等),两大 ...
- 开源项目之ASP.NET Core + Vue.js 的前后端分离的通用后台管理系统框架
年前看了这个开源项目感觉很不错,这个小项目对于传统的.net 开发人员,想做技术提升是一个很不错的参考案例. 开源项目演示地址:https://dnczeus.codedefault.com/logi ...
- 第二篇:R语言数据可视化之数据塑形技术
前言 绘制统计图形时,半数以上的时间会花在调用绘图命令之前的数据塑型操作上.因为在把数据送进绘图函数前,还得将数据框转换为适当格式才行. 本文将给出使用R语言进行数据塑型的一些基本的技巧,更多技术细节 ...
- 七个开源的 Spring Boot 前后端分离项目,一定要收藏!
前后端分离已经在慢慢走进各公司的技术栈,根据松哥了解到的消息,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,松哥也非常建议大家学习一下前后端分离开发,以免在公司干了两三年 ...
- 八个开源的 Spring Boot 前后端分离项目,一定要收藏!
八个开源的 Spring Boot 前后端分离项目 最近前后端分离已经在慢慢走进各公司的技术栈,不少公司都已经切换到这个技术栈上面了.即使贵司目前没有切换到这个技术栈上面,我们也非常建议大家学习一下前 ...
- [转]Android开源项目第二篇——工具库篇
本文为那些不错的Android开源项目第二篇--开发工具库篇,主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容.多 ...
- Android开源项目第二篇——工具库篇
本文为那些不错的Android开源项目第二篇——开发工具库篇,**主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容 ...
随机推荐
- oracle EBS rtf报表不能输出模板样式
1.需要定义中文的数据定义 2.缺少文件 cd $ADMIN_SCRIPTS_HOME prefs.ora 3.查看文档 文档 ID 1059712.1 (1)请求模版显示不出来 解决:模版定义中模 ...
- 试试SQLServer 2014的内存优化表(转载)
SQL Server2014存储引擎:行存储引擎,列存储引擎,内存引擎 SQL Server 2014中的内存引擎(代号为Hekaton)将OLTP提升到了新的高度. 现在,存储引擎已整合进当前的数据 ...
- 【爬坑】运行 Hadoop 的 MapReduce 示例卡住了
1. 问题说明 在以伪分布式模式运行 Hadoop 自带的 MapReduce 示例,卡在了 Running job ,如图所示 2. 解决过程 查看日志没得到有用的信息 再次确认配置信息没有错误信息 ...
- 再谈全局网HBase八大应用场景
摘要: HBase可以说是一个数据库,也可以说是一个存储.拥有双重属性的HBase天生就具备广阔的应用场景.在2.0中,引入了OffHeap降低了延迟,可以满足在线的需求.引入MOB,可以存储10M左 ...
- SNMP协议利用
1.安装snmp服务 2.配置snmp服务 运行Services.msc 添加社区public,只读 启动服务 3.在kali运行 Snmpwalk -c public -v 2c IP 即可查看目标 ...
- 51nod 贪心算法题集
2070 最小罚款: 题意:初始有n元,每个任务有2个参数:t和w,<=t时刻前完成任务才可避免造成损失w.问:如何安排才能尽可能避免损失?一个任务执行时间是一个单位时间. 分析:任务按时间排个 ...
- python3: 数字日期和时间(2)
12.基本的日期与时间转换 Q: 你需要执行简单的时间转换,比如天到秒,小时到分钟等的转换 A: 为了执行不同时间单位的转换和计算,请使用 datetime 模块. 比如,为了表示一个时间段,可以创建 ...
- 实例化list
List<String> lists = new ArrayList<String>();list.add("123");
- 2.Redis集群环境搭建
转载请出自出处:http://www.cnblogs.com/hd3013779515/ 一.基本概念 1.redis集群是一个可以在多个节点之间进行数据共享的设施.redis集群提供了以下两个好处1 ...
- 将替代ListView的RecyclerView 的使用(一)
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/by317966834/article/details/36205923 RecyclerView 是 ...