打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
【开源.NET】 分享一个前后端分离的轻量级内容管理框架(第二篇前后端交互数据结构分析)
2017年02月20日 16:20:06 

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

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

信息管理系统

信息管理系统关键功能:列表分页和搜索、方便数据展示和录入。业务复杂度通常在于多表关联的搜索、录入以及表与表和字段与字段之间的约束,还有就是报表统计了。除去报表不说,其它功能其实就是对数据库表进行增删改查,它们是独立于业务存在的,所以可对它们进行规范化和自动化。
信息管理系统就是为了方便数据的展示和录入,简化为 “需求数据 - SQL - 数据库”, SQL 作为“需求数据” 与“数据库”的中介。想要自动化增删改查,必须要规范化“需求数据的结构”以及添加规范化的“配置文件”,用程序对它们进行分析以生成中介“SQL”。

一、自动化搜索和分页

1、设计图

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

配置文件是手动配置,由后端程序处理的,不涉及到传输,所以应该选择 XML 格式,可读性和性能都可达到平衡。
先看一下一条简单的 select 语句: Select main.* From VideoMain Where main.IsDeleted != 1,这里有 3 个关键字"Select, From, Where", 它们与业务无关,抽出来,组成xml:

  1. <Select>
  2. main.*
  3. </Select>
  4. <From>
  5. VideoMain main
  6. </From>
  7. <Where>
  8. main.IsDeleted != 1
  9. </Where>

看去非常直观,和写 sql 没多大区别。
再看一条复杂点的 select 语句:

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

转成xml配置

  1. <Select>
  2. main.*, owner.Name as _OwnerName
  3. </Select>
  4. <From>
  5. VideoMain
  6. Left Join BasOwner owner On owner.Id = main.OwnerId
  7. </From>
  8. <Where>
  9. main.IsDeleted != 1 And main.Name like '%教程%'
  10. </Where>

不好的是条件写死了,但条件是由前端返回的,应该是动态的。其实 where 条件是由比较符号“=,!=, >, < , >=, <=, like, in, between and” 和逻辑“And, Or”组合起来的 ,可对它们进行规范化。
首先把比较符号转换成有意义的单词,方便配xml,

  1. <code>equal   : =  
  2. notequal: !=  
  3. bigger  : >=  
  4. smaller : <=  
  5. like    : like  
  6. in      : in</code>  

重新规范Where:

  1. <Select>
  2. main.*, owner.Name as _OwnerName
  3. </Select>
  4. <From>
  5. VideoMain
  6. Left Join BasOwner owner On owner.Id = main.OwnerId
  7. </From>
  8. <Where>
  9. <Fields>
  10. <Field Name="IsDeleted" Prefix="main" Cp="notequal"></Field>
  11. <Field Name="Name" Prefix="main" Cp="like"></Field>
  12. </Fields>
  13. </Where>

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

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

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

  1. public string GetSql(string cp, string paraName, string dbname, string value, string sqlExpress = null, string dataType = null, bool isAnd = true, bool isAddQuotes = true)
  2. {
  3. string sql = "";
  4. if (!string.IsNullOrEmpty(sqlExpress))
  5. {
  6. if (isAddQuotes)
  7. {
  8. sql = sqlExpress.Replace("@" + paraName, "'" + ParseValue(value, dataType) + "'");
  9. }
  10. else
  11. {
  12. //sql = sqlExpress.Replace("@" + paraName, ParseValue(value, dataType));
  13. sql = sqlExpress.Replace("@" + paraName, "'" + ParseValue(value, dataType) + "'");
  14. }
  15. }
  16. else
  17. {
  18. value = string.IsNullOrEmpty(dataType) ? value : ParseValue(value, dataType);
  19. string orAnd = isAnd ? "And" : "Or";
  20. switch (cp.ToLower())
  21. {
  22. case "equal":
  23. sql = Equal(dbname, value, orAnd);
  24. break;
  25. case "like":
  26. sql = Like(dbname, value, orAnd);
  27. break;
  28. case "notequal":
  29. sql = NotEqual(dbname, value, orAnd);
  30. break;
  31. case "daterange":
  32. sql = DateRange(dbname, value, orAnd);
  33. break;
  34. case "bigger":
  35. sql = Bigger(dbname, value, orAnd);
  36. break;
  37. case "smaller":
  38. sql = Smaller(dbname, value, orAnd);
  39. break;
  40. case "in":
  41. sql = In(dbname, value, orAnd);
  42. break;
  43. }
  44. }
  45. return sql;
  46. }
  47. public string In(string fieldName, string value, string orAnd = "And")
  48. {
  49. return string.Format(" {2} {0} in ('{1}')", fieldName, FilterSql.FilterValue(value).Replace(",", "','"), orAnd);
  50. }
  51. public string Equal(string fieldName, string value, string OrAnd = "And")
  52. {
  53. return string.Format(" {2} {0} = '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
  54. }
  55. public string Like(string fieldName, string value, string OrAnd = "And")
  56. {
  57. return string.Format(" {2} {0} like '%{1}%'", fieldName, FilterSql.FilterValue(value), OrAnd);
  58. }
  59. public string NotEqual(string fieldName, string value, string OrAnd = "And")
  60. {
  61. return string.Format(" {2} {0} <> '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
  62. }
  63. public string Bigger(string fieldName, string value, string OrAnd = "And")
  64. {
  65. return string.Format(" {2} {0} >= '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
  66. }
  67. public string Smaller(string fieldName, string value, string OrAnd = "And")
  68. {
  69. return string.Format(" {2} {0} <= '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
  70. }

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

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

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

  1. <Where>
  2. <Fields>
  3. <Field Name="Code" Prefix="main" Cp="like"></Field>
  4. <Field Name="Name" Prefix="main" Cp="like"></Field>
  5. </Fields>
  6. </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 结构是:

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

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

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

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

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

  1. {
  2. VideoMain:{
  3. inserted:[
  4. {
  5. data:{Name:"test1"},
  6. children:{
  7. VideoMark:{
  8. inserted:[
  9. {
  10. data:{Mark:5}
  11. },
  12. {
  13. data:{Mark:4}
  14. }
  15. ]
  16. },
  17. VideoComment:{
  18. inserted:[
  19. {
  20. data:{Comment:'good!'},
  21. children:{
  22. VideoCommentUpdown:{
  23. inserted:[
  24. {
  25. data:{IsUp:true}
  26. }
  27. ]
  28. }
  29. }
  30. },
  31. { {
  32. data:{Comment:'very good!'},
  33. children:{
  34. VideoCommentUpdown:{
  35. inserted:[
  36. {
  37. data:{IsUp:true}
  38. }
  39. ]
  40. }
  41. }
  42. }
  43. }
  44. ]
  45. },
  46. }
  47. },
  48. updated:[
  49. {
  50. data:{Id:3, Name:"test3"}
  51. }
  52. ]
  53. ]
  54. }
  55. }

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

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

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

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

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

4、后台配置
  1. </SqlConfig>
  2. <Table>VideoMain</Table>
  3. <ID>Id</ID>
  4. <PKs>Id</PKs>
  5. <Insert>
  6. <Fields>
  7. <Field Name="CreatedDate" IsIgnore="true"></Field>
  8. <Field Name="UpdatedDate" IsIgnore="true"></Field>
  9. </Fields>
  10. </Insert>
  11. <Update>
  12. <Fields>
  13. <Field Name="CreatedDate" IsIgnore="true"></Field>
  14. <Field Name="UpdatedDate" IsIgnore="true"></Field>
  15. </Fields>
  16. <Where>
  17. <Fields>
  18. <Field Name="Id" Cp="equal"></Field>
  19. </Fields>
  20. </Where>
  21. </Update>
  22. <Delete>
  23. <DeleteAnyway>false</DeleteAnyway>
  24. <Where>
  25. <Fields>
  26. <Field Name="Id" Cp="equal"></Field>
  27. </Fields>
  28. </Where>
  29. </Delete>
  30. <Children>
  31. <SqlConfig>
  32. <Table>VideoMark</Table>
  33. <JsonName>marks</JsonName>
  34. <ID>Id</ID>
  35. <PKs>Id</PKs>
  36. <Dependency>
  37. <Fields>
  38. <Field Name="MainId" DependencyName="Id"></Field>
  39. </Fields>
  40. </Dependency>
  41. <Update>
  42. <Where>
  43. <Fields>
  44. <Field Name="Id" Cp="equal"></Field>
  45. </Fields>
  46. </Where>
  47. </Update>
  48. <Delete>
  49. <Where>
  50. <Fields>
  51. <Field Name="Id" Cp="equal"></Field>
  52. </Fields>
  53. </Where>
  54. </Delete>
  55. </SqlConfig>
  56. <SqlConfig>
  57. <Table>VideoComment</Table>
  58. <JsonName>comments</JsonName>
  59. <ID>Id</ID>
  60. <PKs>Id</PKs>
  61. <Dependency>
  62. <Fields>
  63. <Field Name="MainId" DependencyName="Id"></Field>
  64. </Fields>
  65. </Dependency>
  66. <Update>
  67. <Where>
  68. <Fields>
  69. <Field Name="Id" Cp="equal"></Field>
  70. </Fields>
  71. </Where>
  72. </Update>
  73. <Delete>
  74. <Where>
  75. <Fields>
  76. <Field Name="Id" Cp="equal"></Field>
  77. </Fields>
  78. </Where>
  79. </Delete>
  80. <Children>
  81. <SqlConfig>
  82. <Table>VideoCommentUpdown</Table>
  83. <JsonName>commentUpdowns</JsonName>
  84. <ID>Id</ID>
  85. <PKs>Id</PKs>
  86. <Dependency>
  87. <Fields>
  88. <Field Name="CommentId" DependencyName="Id"></Field>
  89. </Fields>
  90. </Dependency>
  91. <Update>
  92. <Where>
  93. <Fields>
  94. <Field Name="Id" Cp="equal"></Field>
  95. </Fields>
  96. </Where>
  97. </Update>
  98. <Delete>
  99. <Where>
  100. <Fields>
  101. <Field Name="Id" Cp="equal"></Field>
  102. </Fields>
  103. </Where>
  104. </Delete>
  105. </SqlConfig>
  106. </Children>
  107. </SqlConfig>
  108. </Children>
  109. </SqlConfig>

SqlConfig xml 对应的对象

  1. public class SqlConfig
  2. {
  3. public SqlConfig()
  4. {
  5. this.Where = new Where();
  6. this.Children = new List<SqlConfig>();
  7. this.OrderBy = new OrderBy();
  8. this.GroupBy = new GroupBy();
  9. this.Dependency = new Dependency();
  10. this.Insert = new Insert();
  11. this.Update = new Update();
  12. this.Delete = new Delete();
  13. this.SingleQuery = new SingleQuery();
  14. this.Export = new Export();
  15. this.Import = new Import();
  16. this.BillCodeRule = new BillCodeRule();
  17. }
  18. private string _settingName;
  19. /// <summary>
  20. /// 配置名称,默认和表名一致,一般不会用到,方法以后扩展,如一个配置文件出现相同的表时,用来区分不同的配置
  21. /// </summary>
  22. public string SettingName
  23. {
  24. get
  25. {
  26. if (string.IsNullOrEmpty(_settingName))
  27. {
  28. _settingName = Table;
  29. }
  30. return _settingName;
  31. }
  32. set
  33. {
  34. _settingName = value;
  35. }
  36. }
  37. #region 查询配置
  38. /// <summary>
  39. /// 查询的字段
  40. /// </summary>
  41. public string Select { get; set; }
  42. /// <summary>
  43. /// 查询的表名以及关联的表名,如 left join, right join
  44. /// </summary>
  45. public string From { get; set; }
  46. /// <summary>
  47. /// 查询的条件
  48. /// 前端返回的查询条件,只有出现在这些配置好的字段,才会生成为了 sql 的 where 条件,
  49. /// 没出现的字段会被忽略
  50. /// </summary>
  51. public Where Where { get; set; }
  52. /// <summary>
  53. /// 分页时必须会乃至的排序规则
  54. /// </summary>
  55. public OrderBy OrderBy { get; set; }
  56. public GroupBy GroupBy { get; set; }
  57. /// <summary>
  58. /// 页码
  59. /// </summary>
  60. public int PageNumber { get; set; }
  61. /// <summary>
  62. /// 页大小
  63. /// </summary>
  64. public int PageSize { get; set; }
  65. #endregion 查询配置
  66. /// <summary>
  67. /// 指定该配置所属于的表
  68. /// </summary>
  69. public string Table { get; set; }
  70. #region 增删改配置
  71. /// <summary>
  72. /// 对应前端返回的 json 格式数据的键名
  73. /// e.g.: {master:{inserted:[{data:{}}]}} 中的 master 就是这里要对应的 JsonName
  74. /// 注意默认主表的 jsonName 是 master, 所以主表一般可省略不写, 但子表必须得指定
  75. /// </summary>
  76. public string JsonName { get; set; }
  77. /// <summary>
  78. /// 自增的字段,指定了自增的字段,在 insert 时会自动忽略该字段
  79. /// </summary>
  80. public string ID { get; set; }
  81. /// <summary>
  82. /// 主键, 在保存成功后会返回主键的值;
  83. /// </summary>
  84. public string PKs { get; set; }
  85. /// <summary>
  86. /// 唯一值的字段,对应数据库 unique, 在 insert,update 前会判断是否已存在
  87. /// </summary>
  88. public string Uniques { get; set; }
  89. /// <summary>
  90. /// 唯一值的字段的值是否允许为空
  91. /// </summary>
  92. public string UniqueAllowEmptys { get; set; }
  93. /// <summary>
  94. /// 所属的父级配置, 在 xml 中不用指定,程序会自动分析
  95. /// </summary>
  96. public SqlConfig Parent { get; set; }
  97. /// <summary>
  98. /// 包含的子级配置, 即子表的配置,需要在 xml 中配置
  99. /// </summary>
  100. public List<SqlConfig> Children { get; set; }
  101. /// <summary>
  102. /// 依赖父表的字段
  103. /// </summary>
  104. public Dependency Dependency { get; set; }
  105. /// <summary>
  106. /// insert 的配置
  107. /// </summary>
  108. public Insert Insert { get; set; }
  109. /// <summary>
  110. /// update 的配置
  111. /// </summary>
  112. public Update Update { get; set; }
  113. /// <summary>
  114. /// delete 的配置
  115. /// </summary>
  116. public Delete Delete { get; set; }
  117. #endregion
  118. /// <summary>
  119. /// 单条记录查询的配置,一般用在配置列表双击弹出那条记录的获取的 sql
  120. /// </summary>
  121. public SingleQuery SingleQuery { get; set; }
  122. /// <summary>
  123. /// 导出配置
  124. /// </summary>
  125. public Export Export { get; set; }
  126. /// <summary>
  127. /// 导入配置
  128. /// </summary>
  129. public Import Import { get; set; }
  130. /// <summary>
  131. /// 是否物理删除?
  132. /// </summary>
  133. public bool DeleteAnyway { get; set; }
  134. /// <summary>
  135. /// 表单编码的生成配置
  136. /// </summary>
  137. public BillCodeRule BillCodeRule { get; set; }
  138. }

5、批量增删改截图

单表

主-从

主-从-从

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

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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Asp.Net 操作 Sqlserver通用类
三层.业务层
C#.NET操作数据库通用类(MS SQL Server篇)
一步一步Asp.Net MVC系列_权限管理总结(附MVC权限管理系统源码)
为ASP.NET封装的SQL数据库访问类
一个简单的C#的ACCESS操作类
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服