1.骨架概述

vue.netcore优势在于generator涵盖了api、ui全栈,不能否认的快;‘快’是通过反设计模式、反面向对象提供的畸形的高效率——它利于第一次开发,不利于维护:技术栈特性通常不考虑,只堆不理,对常见需求,如过滤器、管道,破坏性实现
当前阶段强调实现,不考虑已有内容的优化

2.编码规约

2.1命名风格

  1. 【强制】Pascal命名(namespace、class、property、method)、camel命名(field、route etc..)
  2. 【强制】杜绝含义不明的缩写、简写(业务缩写除外)

正例:order delete Department
反例:ord del Dept

  1. 【建议】 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意

反例:BrandController LocationController (不可读意)
杜绝 int a 的随意命名方式
慎用xxx_Class、xxx_Type、xxx_ClassDetail等直译名称,这些词义已经混淆了
理解需求翻译需求,替换例子:Purpose、Goals、CategoryOne、CategoryTwo、Kind、Property

  1. 【建议】保持属性的连续可读,推荐 “属性名称_属性状态”的命名方式

正例:ResultExpected ResultActual
C#的语法通常不推荐下划线’_’,是否广泛适用有争议

  1. 【强制】不允许出现魔幻值,必须包装成枚举、静态类
  2. 【建议】各层命名规约:

    1. 开放接口层、业务逻辑层
      1. 获取单个对象的方法用 get 做前缀
      2. 获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects
      3. 获取统计值的方法用 count 做前缀
      4. 插入的方法用 save/insert 做前缀
      5. 删除的方法用 remove/delete 做前缀
      6. 修改的方法用 update 做前缀
    2. 领域模型命名规约
      1. 数据对象:xxx,xxx 即为数据表名
      2. 数据传输对象:xxxDto
      3. 数据查询持久对象:xxxDao

        2.2控制语句

  3. 减少if..elseif…else…等同于积德

  4. 慎用空catch

    2.3注释规约

    注释的问题永远有完美解决

  5. 如果认为代码结构已经不用解释了,那就不要注释,过度注释和没有注释一样

  6. 合适的标记,fix issue #1;todo;see cref
  7. 不要在代码同一行注释,也不要一行一注释

image.png

2.4数据库

  1. 【强制】表的命名是“业务名称_表的作用”,如project_config
  2. 【强制】不得使用外键与级联,一切外键概念必须在应用层解决 !!!
  3. 强制】表名、字段名必须使用小写字母或数字

    2.5解决方案结构

  • repository
    • xxxRepository.cs的修改记录应该有且只有一条——首次提交
  • service
  • controller

    讨论 既然框架已经不遵循领域模型,那么也没有必要照搬概念 以下的讨论限定在vue.netcore的场景下

    前提是维持框架内的运行逻辑,不评论作者的实现 (1)controller service 在实现上只是一套逻辑的写两个地方,在这个概念上针对它们优化的意义不大 (2)强制限制controller内容 (3)service是逻辑垃圾堆,个人认为清理它的最重要是不要往里再倒垃圾——再开个垃圾场 (4)补充数据操作控制力,虽然cms系统事务性不多

3.一期项目其他问题

  1. web-api
    • 谓词
      • 框架里本来就乱
      • 谓词和方法名的约定
    • 路由
      • 格式规约,推荐小写,推荐’-‘作为分割
    • 参数
      • 几乎全是json-body
    • 响应
      • 弱类型(有包装的有不包装的)
      • 暴露了数据库实体
    • 错误处理
      • 缺乏错误的业务针对性处理(错误包装、错误代码)
  2. orm-dapper

efcore 不香?
如果必要执行原生SQL,那么这些SQL要么在DB要么在配置文件

  1. orm-efcore

逐步取消外键和DbEntity.Include
(1)笛卡尔积爆炸(2)无法进行数据表横切

  1. LINQ

WipOrderService.getChannelAndWipOperation
(1)LINQ可以有效的在保证LINQ2Db的同时提供封装性(如新、旧逻辑交织)
(2)配合automapper,免修改逻辑控制层

4.有必要考虑的补充

  1. CMS套件
    1. 缓存设计
    2. 错误处理
      1. 程序异常套件,捕捉,日志,包装的友好提示
      2. 业务异常套件,错误代码,包装的错误提示
    3. 多语言
  2. 事务性控制
    1. 数据库事务,有强制的书写范围,read-commited可以满足绝大多数要求
    2. 分布式事务,用不着
  3. 需求控制
    • issue、markdown
    • 要不要强制pr

      5.Include的重点说明

      IQueryable每增加一次IncludeThenInclude,表达式翻译的SQL语句会
      (1)额外的查询嵌套(2)自动对子表的默认排序 ```csharp

[Benchmark] public void LeftOuterJoin() { for (int i = 0; i < N; i++) { using var context = new BloggingContext(); var blogs = context.Blogs .Include(blog => blog.Posts) .ThenInclude(post => post.Tags) .ThenInclude(tags => tags.Tag) .ToList(); } }

[Benchmark] public void LeftOuterJoinVsLinQ() { for (int i = 0; i < N; i++) { using var context = new BloggingContext();

  1. var query = from blog in context.Blogs
  2. join post in context.Posts on blog.BlogId equals post.BlogId into g
  3. from post in g.DefaultIfEmpty()
  4. join postTag in context.PostTags on post.PostId equals postTag.PostId into h
  5. from postTag in h.DefaultIfEmpty()
  6. join tag in context.Tags on postTag.TagId equals tag.TagId into m
  7. from tag in m.DefaultIfEmpty()
  8. select new
  9. {
  10. blog,
  11. post,
  12. postTag,
  13. tag
  14. };
  15. var blogs = query.ToList();
  16. }

}

  1. ```
  2. BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1766 (21H2)
  3. Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
  4. .NET SDK=6.0.100
  5. [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
  6. DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
  7. | Method | N | Mean | Error | StdDev |
  8. |---------------------- |-- |--------:|---------:|---------:|
  9. | LeftOuterJoin | 1 | 4.234 s | 0.0837 s | 0.1990 s |
  10. | LeftOuterJoinWithLinQ | 1 | 1.961 s | 0.0314 s | 0.0262 s |
  1. SELECT `b`.`BlogId`, `b`.`OwnerId`, `b`.`Rating`, `b`.`ThemeId`, `b`.`Url`, `t1`.`PostId`, `t1`.`AuthorId`, `t1`.`BlogId`, `t1`.`Content`, `t1`.`Rating`, `t1`.`Title`, `t1`.`PostTagId`, `t1`.`PostId0`, `t1`.`TagId`, `t1`.`TagId0`
  2. FROM `Blogs` AS `b`
  3. LEFT JOIN (
  4. SELECT `p`.`PostId`, `p`.`AuthorId`, `p`.`BlogId`, `p`.`Content`, `p`.`Rating`, `p`.`Title`, `t0`.`PostTagId`, `t0`.`PostId` AS `PostId0`, `t0`.`TagId`, `t0`.`TagId0`
  5. FROM `Posts` AS `p`
  6. LEFT JOIN (
  7. SELECT `p0`.`PostTagId`, `p0`.`PostId`, `p0`.`TagId`, `t`.`TagId` AS `TagId0`
  8. FROM `PostTags` AS `p0`
  9. LEFT JOIN `Tags` AS `t` ON `p0`.`TagId` = `t`.`TagId`
  10. ) AS `t0` ON `p`.`PostId` = `t0`.`PostId`
  11. ) AS `t1` ON `b`.`BlogId` = `t1`.`BlogId`
  12. ORDER BY `b`.`BlogId`, `t1`.`PostId`, `t1`.`PostTagId`;

SELECT b.BlogId, b.OwnerId, b.Rating, b.ThemeId, b.Url, p.PostId, p.AuthorId, p.BlogId, p.Content, p.Rating, p.Title, p0.PostTagId, p0.PostId, p0.TagId, t.TagId FROM Blogs AS b LEFT JOIN Posts AS p ON b.BlogId = p.BlogId LEFT JOIN PostTags AS p0 ON p.PostId = p0.PostId LEFT JOIN Tags AS t ON p0.TagId = t.TagId;