从本篇开始,本系列将进入实战演练阶段。

  前面主要介绍了一些应用程序框架的概念和基类,本来想把所有概念介绍完,再把框架内部实现都讲完了,再进入实战,这样可以让初学者基础牢靠。不过我的精力很有限,文章进度越来越慢,所以准备切换一下介绍顺序,把实战演练提前,以方便你阅读代码。

实战演练介绍

  本系列实战演练共分两个部分。

  实战演练第一部分介绍如何快速解决CRUD机械操作,这一部分我将手把手带领各位同学从搭建VS环境开始,创建程序集及各程序集间的依赖关系,以及引入依赖的外部DLL,并手工完成代码示例中Application的三种界面操作。当你熟悉了手工操作方式后,你会发现这些工作枯燥乏味,效率低下,且容易出错。为解决该问题,我将为你介绍PowerDesigner(PD)和CodeSmith两大工具,分享PD及数据建模技巧,并发布配套的CodeSmith模板,你将体验到高质量完成机械代码的最佳实践。

  实战演练第一部分介绍的内容很有用,它将帮助你完成大量体力活,但这个示例太简单,体现不出领域模型的威力,实战演练第二部分以权限模块为例,演示如何开发具有一定业务逻辑的模块。

  当然,在介绍实战演练第二部分之前,我需要先把框架内部重要代码讲解完,本系列大致构成如下:

  1. 应用程序框架介绍
  2. 实战演练1:CRUD开发
  3. 框架代码详解
  4. 实战演练2:权限开发

示例代码阅读建议

  很多同学反映,阅读我的示例代码非常困难,经过了解,我发现大多是阅读方法有问题,在此给出一些建议。

  对于别人的代码,你阅读起来很痛苦是正常现象,因为编程习惯和风格不同,另外一个原因是对代码的意图不了解。

  我提供了Managements和Util两个VS解决方案,Managements是简单管理系统的代码示例,Util是框架代码,它是基础设施层的一部分,被分离出来的原因是让业务项目更简单、编译更快。

  阅读代码需要从简单的东西入手,这里就是Managements解决方案,它里面包含了一个叫Application的CRUD操作,它是权限系统中的应用程序模块,具体功能先不要考虑。Application模块是一个单表操作,非常简单,几乎没有业务逻辑,很适合用来入门。EasyUi提供了三种数据录入方式,即表单操作、表格操作、行内操作,我使用Application模块演示了这几种操作方式。

  有些同学下载代码后,直接看的Util代码,它主要包含一些基类和公共操作类,看起来困难就在所难免了。

  为了减少程序集数量,我把一些第三方开源框架的代码直接放入Util解决方案中,有些同学直接就看到这里面去了,然后告诉我异常复杂。不要泄气,说明你是一个正常人,对于第三方开源框架的代码,我也害怕,嘿嘿。我的代码风格很容易辨认,就是每个方法都具有中文注释,当你发现代码没有注释或注释全是英文,那一定不是我写的,我的英文水平很菜。

  当你把Application看熟以后,可以自己动手创建与Application类似的表,并手工完成三种CRUD操作。

  当你对CRUD相关的类和配置熟悉之后,就可以查看基类实现,这时候带着问题去看Util代码,会容易理解得多。

  下一步就是把这些代码逐步移植到你自己的项目中,只有把它们变成你的东西,才能发挥更大作用。这也是我写这个系列的目的,不仅授之以鱼,更要授之以渔。

  建议你至少能够扩展之后,才把我的东西用到你的项目上,不然坑很多,风险高。

CRUD概述

  CRUD是Create、Retrieve(Read)、Update、Delete的缩写,中文名:增删改查。不论哪家的应用框架,都特别关照它几兄弟,为什么?

  对于一般的中小型项目,业务逻辑复杂的模块只占很小的比例,一半以上的模块都比较简单。这些简单模块大致会通过一个界面或设备接口把数据收集上来,基本不经过中间处理(业务逻辑),直接存入数据库,在有需要的时候会把这些数据展示出来,或者为复杂模块提供基础支持。

  这些简单模块工作量大,技术含量低,通过手工的方式编写效率低下。

  当采用了分层架构,特别是DDD分层架构之后,更是雪上加霜。

  对于采用了DDD这种复杂分层架构,哪怕业务逻辑很复杂,还是存在不少机械工作,主要是创建各层的构造类型,比如领域实体及属性、DTO及属性映射、EF映射等,这些工作是必须的,但很枯燥乏味。

  可以看到,只要是信息系统,不论简单还是复杂,都存在机械工作,不同性质的项目机械工作所占比例不一样而已。

  后面我将用CRUD来指代开发中碰到的一切机械工作。

  对于机械工作,最好的办法是依靠生成器自动创建代码,在讨论生成器之前,先讨论下EF相关的概念。

混乱的EF概念

  EF在刚出土的时候,提供一个叫实体数据模型的edmx文件,打开这个文件,发现它是一个可视化类图设计器。

  在新建edmx文件时,有“从数据库生成”和“空模板”两个选项。

  如果你选择“从数据库生成”,说明你自己先创建了数据库,再通过edmx的反向生成工具生成代码,这就是所谓的db first,first是先行或优先的意思,db first就是数据库先行,先创建数据库,再读取数据库的元数据,生成代码。

  如果你选择了“空模板”,你可以在edmx可视化类图设计器中创建一些类和关联,edmx会自动帮你生成代码和数据库。这种方式称为model first,即模型先行,先有模型,后面再创建代码和数据库。

  代码生成出来后,你会发现这些代码文件被包含在edmx文件中,包括领域实体和DbContext工作单元,还有一些T4模板,这有什么影响?

  如果采用DDD分层架构,领域实体属于领域层,而DbContext属于基础设施层,放到一起会导致高耦合以及分层不清,这是edmx的主要问题。另外一堆不相干的代码生成模板与领域实体放到一起,估计也让你看着心烦。

  EF后续推出了更加轻量的使用方式,让你可以抛弃edmx文件,直接使用原生的DbContext,并支持了code first开发模式。code first即代码先行,先写代码,再自动创建数据库。code first开发模式能够真正实现持久化无关,从而设计出更加纯净的领域模型,特别在采用TDD开发时,更加威猛。

  从上面可以看出,edmx和原生DbContext是两种不同的EF技术,而db first、model first、code first则是不同的开发模式,但这些术语非常混乱,不同的人说同一个术语时可能指的是不同的东西。

  经常听到有人说他用的是code first,但实际上他的开发方式是先创建数据库,再生成代码,这属于db first,他用code first指代原生DbContext技术。

  还有一些人害怕使用EF,因为他认为原生DbContext只能使用code first开发模式,而他想采用db first方式,但他又不喜欢edmx。

  我用原生DbContext这个词的意思是,单独使用DbContext这个基类,因为edmx也使用的是DbContext,以示区别。

  下面用一个图来总结一下,如果说得不正确,请各位同学批评指正。

Code First还是Db First

  从上面分析得知,edmx不适合DDD分层架构,所以我们在EF技术上采用原生的DbContext,这个没有什么疑问了。那么开发模式是否一定要采用code first呢?

  前面说了,code first可以获得更纯的领域模型,但你见过炉火纯青的领域模型长什么样吗?对于DDD架构初学者,在很长时间都难领悟到它的精髓,所以不论你以code first还是db first,其结果没有显著不同。

  其次,.net大部分项目都是中小项目,且不太复杂,CRUD机械工作占很大篇幅,使用code first手工敲代码,效率十分低下,且工作量与表中的字段数量成正比。在配合TDD的情况下,才可以和生成的代码质量媲美,否则BUG依旧。

  可以看到,虽然code first万众瞩目,但却只有DDD高手开发很复杂的业务才能真正发挥威力。很复杂的业务需求,可能逻辑非常复杂,仅简单搜集数据字段,并不能很好的完成任务,这种场景基于DDD进行行为建模并配合TDD推进项目更有保障。

  根据我的项目实际情况,我采用了db first第一步用PD数据建模,第二步用PD生成建库脚本创建数据库,第三步采用CodeSmith生成代码,第四步选择性的复制代码并建立领域模型。

代码生成器介绍

  很多同学一听代码生成器,就会问哪种生成器最好,还有些同学则干脆自己开发,毕竟大家都是程序员,要开发个生成器软件有何难。

  技术人员总是对技术本身比较感兴趣,容易忽略做一件事的真正目的。其实对于代码生成器来说,真正重要的不是生成器软件,而是你需要获得的最终代码,它是由你的模板决定的。

  要创建出一套高质量的模板,关键是不断提纯自己的代码,把重复的代码尽量提取到基类。

  对于采用哪种生成器软件,根据自己的熟悉程度和喜好进行选择,我采用的是CodeSmith

  CodeSmith是一个收费的代码生成器,不过大家都使用绿色环保版本。使用它的原因是功能比较强大,能够与VS进行集成,编写模板时具有代码提示,类似ASPX语法,学习成本低,另外官方提供了一套EF DbContext模板,我们只需要简单修改,就可以用于实际开发中。

  需要创建哪些部分的代码呢?在最理想的情况下,所有机械代码全部生成,这样你可以在最短时间内拿下大部分机械工作,为你能够集中火力完成核心功能奠定基础。

  下面讨论几个与生成器相关的问题。

代码生成器与数据建模的关系

  一般的代码生成器都是通过读取数据库元数据来生成代码。

  如何评价生成的代码质量高低?

  第一个特征,所有代码是否具有准确的注释。

  大部分程序员都不喜欢写注释,不知道是因为打字慢,还是觉得没必要。哪怕你英文很牛X,你的命名非常标准,但你不能保证看你代码的人具有同样的英文水平。何况大部分人的英文还是和我一样菜,命名十分晦涩。在这种情况下,不要说给别人留条活路,那是给自己将来留的。

  如果你采用code first模式,手工编写所有代码,相信能给全套代码写全注释的人不多,每个领域实体的属性头上都要加上注释,而且还有大量相似类,比如Dto,不复制代码很难做到。

  第二个特征是自动帮你生成EF导航属性及相关映射配置,这是通过读取外键关系来创建的。

  很多.Net程序员不知道数据建模的价值,如果你问我一个项目里,哪种文档最重要,我会毫不犹豫的告诉你——数据建模文档。

  为什么数据建模这么重要,如果你现在接手一个遗留系统,你最需要什么?需求文档?类图?序列图?需求你不懂,还可以找用户问,但数据库中一个命名很晦涩的列,你要猜出它是什么意思,则难如登天。而类图和序列图等UML建模,主要是前期帮助理解和设计领域模型,不一定能够与代码同步更新,另外也不可能对每一个模块创建UML,完全没有必要。

  通过PowerDesigner进行数据建模,你可以让系统清晰度上升几个层次,让你看清表之间的关系,以及每个列的具体含义

  你可以在数据建模时,把每个列的注释加上,用PD创建数据库后,生成的代码中就具有良好的注释了。

  当你在表之间用关联线一拖,外键关系就建立了,生成的代码就具有了导航属性

  当然你可以直接在数据库中创建表,并添加注释,并手工创建外键关系。但这并没有让你减轻工作量,反而工作量更大,使用数据建模,工作轻松高效,且对项目未来维护有深远影响。

  生成高质量代码,除了你的模板外,另一个影响它的就是数据建模。

  我将在后面几篇分享我整理的CodeSmith模板,对于简单的CRUD操作,它可以生成全套代码,代码质量与我手工编写无异。

是否批量生成代码

  对于从三层架构过来的朋友,很多都用过代码生成器。

  如果系统有100个表,他们会把这100个表先建好,然后一次生成出来,然后再花几小时到几天的时间来整理修改。

  这个开发效率看上去很诱人,对于比较简单的三层架构和SQL操作可能是有效的,但对于EF+DDD分层架构却不太吃香。

  对于EF的导航属性,生成出来都是双向导航,但为了降低复杂度,可能会手工调整为单向导航,这时候也需要手工修改映射代码。

  EF操作,我总是保持小步前进,前进太快,出现任何一个问题,都可能浪费更多时间。很多时候看EF异常提示很难定位到问题,甚至断点调试也不起作用。这种情况下,最好的办法就是小步走,一出问题就可以迅速解决。

  用代码生成器创建DDD分层架构,一个弊端是导致一个表对应一个聚合,每个表都有一个仓储,这把你又带回了三层架构时代。不过对于新手来说,这没有多大问题,每个人都有一个成长的过程,第一步把充血模型用起来就行了,下一步再考虑聚合。

  但对于达到一定经验的人,直接用生成的代码就不合适了,因为聚合是DDD分层架构的核心,聚合使用得好,能显著降低系统复杂性,并使业务逻辑更好的内聚。

  所以如果你具备一定经验以后,不应该完全采用代码生成的老方式,更不能偷懒。应该选择性的复制代码手工组织聚合结构,这样一来,很多生成的代码都不需要了,比如某个仓储操作的是聚合内部实体,系统复杂性会大幅降低。

  我的方法是,按依赖顺序手工复制需要的代码,按聚合粒度复制并组织代码,一次操作一个聚合,把界面运行通过后再复制下一个。

  这样可以让你用db first模式开发出较高质量的领域模型,当然质量高低与水平成正比。

  对于很简单的CRUD模块,大多都是单表结构,这种情况下,一个表本来就是一个聚合,代码直接COPY,你的主要工作是调整下界面。

  对于比较复杂的模块,根据自己的理解手工复制代码组织聚合,生成的代码一般都达不到要求,比如界面布局比较复杂,这时候你会发现,生成的代码主要用于填充内容,你自己完成布局等功能。

  更复杂的模块,可以先不生成代码,用TDD推进并模拟出业务逻辑后,再进行数据建模生成代码,并复制需要的文件。

是否把代码生成器嵌入生产项目

  对于强大一点的生成器,都能够嵌入VS,并一键生成。这个特性也很诱人,如果把生成器嵌入生产项目,就不需要COPY文件了,这看起来能够极大的提升开发效率。

  与上一个问题一样,当你把生成器嵌入生产项目,生成的所有文件都进入你的项目,不论你需不需要它,这导致每个表一个仓储,增加了复杂性。

  我的方法是,把代码生成器与生产项目分离,手工复制相关文件,虽然看上去效率低,但可以根据需要选择代码和重新组织代码,质量将高得多。

数据库增加一个字段也要生成一下吗

  采用代码生成器的一个问题是,每当数据库增加一个字段,代码上相关的位置都要同步更新,很多懒汉希望通过重新生成并全面覆盖来解决这个问题。

  我的方法是仅在第一次生成全套代码,后面通过手工添加相关属性,如果增加的字段比较多,我可能先生成代码再手工将差异属性复制过去。原因很简单,项目上的代码不是完全生成的,有修改过的地方,重新生成并完全替换,可能会覆盖已修改代码。

一键生成简单代码,还是配置生成智能代码

  很多人在生成器下了大量功夫,能够支持复杂的配置,以生成出非常智能的代码。

  这可能造成对代码生成器的高度依赖,我仅使用代码生成器解决机械的简单工作,对于更智能的手工完成

  我的方法是一键生成简单代码。

分层构造元素是否可以简化

  有人看见生成的代码中,很多类都直接从基类派生,里面完全是空的,是否可以简化掉。

  这些类中啥也没有的原因是,基础操作已抽象到基类,由于没有什么业务逻辑,所以是空的。

  一般来说不能简化,因为对于稍复杂的模块,都需要往这些类中添加内容,如果没有它们你的代码将变得混乱,这些构造很好的组织了代码。

  除非你能确定你的项目基本都是CRUD,这种情况下确实可以简化,而且最好的办法是采用单层架构,单层架构在高度抽象和采用代码生成器的情况下,开发效率犹如火箭直冲宵汉。

也谈加班

  这两天园子里讨论加班的很多,我也说几句。

  不加班有几个条件:

  1. 计划合理
  2. 应用框架强大
  3. 开发人员平均水平较高

  第一点最困难,哪怕你们开发人员水平再高,框架也很强大,如果老板要求你2个月完成8个月的工作,你不加班是不可能的。

  如果计划合理,框架很强大,你一天用半天时间来开发,半天时间休息都绰绰有余。

结束语

本文分享了我在EF和代码生成器上的一些看法,不见得正确,那只是我摸索的一些经验,你应该找出最合适你们团队和项目的方法,并持续改进。

.Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

.Net Easyui开发交流QQ群(本群仅限Easyui开发者,非Easyui开发者勿进):157809322

谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

应用程序框架实战三十六:CRUD实战演练介绍的更多相关文章

  1. 应用程序框架实战三十八:项目示例VS解决方案的创建(一)

    进行项目开发的第一步,是创建出适合自己团队习惯的VS解决方案,虽然我已经提供了项目示例,但毕竟是我创建的,你直接使用可能并不合适,另外你如果尝试模仿重新创建该示例,中间可能碰到各种障碍,特别是项目间的 ...

  2. 应用程序框架实战三十:表现层及ASP.NET MVC介绍(一)

    本文将介绍表现层及ASP.NET MVC的一些要点,特别是ASP.NET MVC的一些抽象和封装技巧,如果你对MVC还不了解,可以参考<ASP.NET MVC4 高级编程>,作者Jon G ...

  3. 应用程序框架实战三十四:数据传输对象(DTO)介绍及各类型实体比较

    本文将介绍DDD分层架构中广泛使用的数据传输对象Dto,并且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行比较. 领域实体为何不能一统江湖? 当你阅读 ...

  4. 应用程序框架实战二十九:Util Demo介绍

    上文介绍了我选择EasyUi作为前端框架的原因,并发放了最新Demo.本文将对这个Demo进行一些介绍,以方便你能够顺利运行起来. 这个Demo运行起来以后,是EasyUi的一个简单CRUD操作,数据 ...

  5. 微信小程序把玩(三十六)Storage API

    原文:微信小程序把玩(三十六)Storage API 其实这个存储在新建Demo的时候就已经用到了就是存储就是那个logs日志,数据存储主要分为同步和异步 异步存储方法: 存数据 wx.setStor ...

  6. 应用程序框架实战二十二 : DDD分层架构之仓储(层超类型基础篇)

    前一篇介绍了仓储的基本概念,并谈了我对仓储的一些认识,本文将实现仓储的基本功能. 仓储代表聚合在内存中的集合,所以仓储的接口需要模拟得像一个集合.仓储中有很多操作都是可以通用的,可以把这部分操作抽取到 ...

  7. 【WePY小程序框架实战三】-组件传值

    [WePY小程序框架实战一]-创建项目 [WePY小程序框架实战二]-页面结构 父子组件传值 静态传值 静态传值为父组件向子组件传递常量数据,因此只能传递String字符串类型. 父组件 (paren ...

  8. 应用程序框架实战三十七:Util最新代码更新说明

    离上一篇又过去了一个月,时间比较紧,后续估计会更紧,所以这次将放出更多公共操作类及配套的CodeSmith模板,本篇将简要介绍新放出的重要功能,供有兴趣的同学参考. 重要更新 这一次对两个VS解决方案 ...

  9. 应用程序框架实战三十三:表现层及ASP.NET MVC介绍(二)

    最近的更新速度越来越慢,主要是项目上比较忙,封装EasyUi也要花很多时间.不过大家请放心,本系列不会半途夭折,并且代码干货也会持续更新.本文继续介绍表现层和Asp.net Mvc,我将在本篇讨论一些 ...

随机推荐

  1. Total Commander解压位置

    TC解压到当前文件夹下 TC也是用了一段时间,现在勉强也算用习惯了,今天在解压文件的时候感觉步骤麻烦,之前解压都是解压到另一个窗口,所以一直是ALT+8同步窗口,然后解压文件.但一般解压文件都是解压到 ...

  2. JVM中对象的销毁

    1.可达性分析算法: 可达性分析算法用来寻找将要销毁的对象,它的基本思路是:通过一系列的称为“GC ROOTs”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到GC ...

  3. JavaScript 作用域知识点梳理

    JavaScript的作用域一直以来是前端开发中难以理解的知识点,对于JavaScript的作用域主要记住几句话. 一.“JavaScript” 中无块级作用域 在   Java 或 C# 中存在块级 ...

  4. 在React 组件中使用Echarts

    在完成一个需求的时候碰到一个场景需要使用柱状图.涉及到可视化,第一反应当然是Echarts了.平时用js加载Echarts组件很方便,但是在React中就要费下神了.各种连蒙带猜实现了.edmo里的E ...

  5. jQuery中事件与动画的总结

       1.加载DOM     1.1.window事件 window.onload=function(){}.... 时机:其他资源都加载完毕后,再执行   $(function(){}) ……:只是 ...

  6. 【转】Polya定理

    转自:http://endlesscount.blog.163.com/blog/static/82119787201221324524202/ Polya定理 首先记Sn为有前n个正整数组成的集合, ...

  7. Microsoft开源跨平台的序列化库——Bond

    上个月Microsoft开源了Bond,一个跨平台的模式化数据处理框架.Bond支持跨语言的序列化/反序列化,支持强大的泛型机制能够对数据进行有效地处理.该框架在Microsoft公司内部的高扩展服务 ...

  8. ADO.NET的弹性连接控制[ADO.NET idle connection resiliency]

    ADO.NET连接SQL Server有时候联机会无故的中断 (例如闲置过久或是交易时间太长等因素),这时又要重新连接,在.NET Framework 4.5之前,这件事情要由开发人员自己依照ADO. ...

  9. python自动化测试(4)-使用第三方python库技术实现

    python自动化测试(4)-使用第三方python库技术实现 1   概述 关于测试的方法论,都是建立在之前的文章里面提到的观点: 功能测试不建议做自动化 接口测试性价比最高 接口测试可以做自动化 ...

  10. nodejs事件轮询详述

    目录 概述 nodejs特点 事件轮询 关于异步方法 概述 关于nodejs的介绍网上资料非常多,最近由于在整理一些函数式编程的资料时,多次遇到nodejs有关的内容.所以就打算专门写一篇文章总结一下 ...