Rafy 领域实体框架示例(1) - 转换传统三层应用程序
Rafy 领域实体框架发布后,虽然有帮助文档,许多朋友还是反映学习起来比较复杂,希望能开发一个示例程序,展示如何使用 Rafy 领域实体框架所以,本文通过使用 Rafy 领域实体框架来改造一个传统的三层架构应用程序——“服装进销存”系统,来讲解如何使用 Rafy 领域实体框架进行数据库应用程序的快速开发,以及替换为使用 Rafy 框架后带来的一些新功能。
完整示例包下载地址:http://pan.baidu.com/s/1AB9TL,其中包含本次改造前、改造后的源代码,以及转换说明文档。(下载该示例代码后,只需要修改 app.config 文件中的连接字符串中的用户名和密码后,就可以直接运行示例,程序即会自动创建数据库并成功运行!还没有下载 Rafy 框架的同学,可以在《Rafy 框架发布》文中下载完整安装包。)
接下来,将说明如何进行代码转换,使用 Rafy 来开发一个典型的数据库应用程序。(以下内容拷贝自示例包中的 PDF 文档。)
原程序说明
考虑到要更好地演示如何使用 Rafy 框架来开发一个传统的管理系统,决定挑选一个开源系统进行改造,而这个系统应该是简单、常见的三层架构,这种系统大家都比较熟悉,这样就可以更加快速的理解框架的使用了。
在开源网站上挑选了很久,免费的三层架构系统挺多,但是许多系统并不规范。一些系统虽然写着使用三层架构,但是金玉其外,败絮其中,看上去非常正式的系统,一打开源码,界面层代码中就可以看到直接编写的 SQL 语句。最终,我选用了《知名度服装进销存管理系统》,源代码下载地址:http://www.51aspx.com/Code/ZhiMingDuClothesSys。该系统三层间的调用比较严格,业务也非常简单。
系统功能描述:
- 人员:操作员管理,供应商管理,顾客管理
- 库存:库存管理,库存盘点
- 销售:服装销售,服装退货
- 服装:服装类别,服装登记
- 销售:销售统计,利润统计
技术特点:使用了三层架构设计程序,更换底层数据库类型方便。系统使用了 SqlLite 作为数据库,下载后可以直接运行。
界面截图 :
程序转换
转换方案
原系统是简单的三层架构:
而我们会使用 Rafy 推荐的架构,来改造整个系统:
对于一个依赖关系较为严格的三层系统来说,要使用 Rafy 框架来替换其中的数据访问层、业务逻辑层以及界面查询的功能,是比较简单的。本次转换,我按照以下步骤进行:
1. 理解系统需求,使用 UML 画出领域实体间的关系。
2. 添加 Rafy 领域实体项目。
3. 根据实体的关系图,在实体程序集中添加对应的实体及实体间的关系;同时也可以把旧表中的属性添加到实体中。
4. 把所有跨多表的业务逻辑转换为领域服务。
5. 依次把历史的实体删除,转而使用新的 Rafy 实体,以及其对应的实体查询、领域服务。
接下来,就正式对代码进行转换:
1. 使用 UML 进行领域建模
经分析,原系统拥有以下领域模型:
User:用户;
Company:供应商;
Customer:顾客;
GoodCategory:商品类别;
Good:商品(服装);
Stock:入库信息;
Regood:返库信息;
Bill 及 Sell:销售单据及销售明细。
它们的关系如下:
(虽然原系统中一些实体的名称取得并不合理,但是为了简化系统的转换工作,新系统中的类命名还是保持和原系统一致。)
关于哪些关系应该使用组合关系来进行设计,大家可以查看 Rafy 用户向导文档中的“领域实体框架/领域实体/实体关系”章节。
2. 升级 .NET 版本
在开始转换代码前,由于原程序使用的是 .NET 2.0 的运行时,而 Rafy 要求必须使用 .NET 4.0。所以我们需要把解决方案中的每个项目都转换为 .NET 4.0 版本。
需要注意的是,由于原程序使用的 SqlLite 只支持 2.0 版本。同时,需要把 SqlLite 替换为 .NET 4.0 的版本。
3. 添加 Rafy 领域实体项目
在解决方案中添加一个 Rafy 领域实体项目,命名为 CS(为原系统名 ClothesSys 的缩写)。
点击确定后生成的项目如下:
接下来,我们将会在这项目中添加领域实体与领域服务,来替换原程序中除界面项目以外的其它几个项目:
4. 实体转换
接下来,依次把历史的实体删除,转而使用新的 Rafy 实体。这一步,需要按照依赖关系,尽量先转换不依赖其他实体的实体,即按照以下顺序进行转换:User、Company、Customer、GoodCategory、Good、Stock、Regood、Bill 和 Sell。由于 Bill 和 Sell 有强聚合关系,所以放到最后一起转换。
(在变更每一个实体时,原代码中所有的 BLL 查询,都需要在实体仓库中编写相关的代码支持;业务逻辑则需要编写领域服务)
实体的转换分为以下几类:
- 无关系实体的转换
- 有关系实体的转换
- 组合实体的转换
5. 简单实体的转换
简单实体没有复杂的关系,只是映射一个简单的表。在转换为 Rafy 实体时,只需要把表中的所有属性都添加到实体中就可以了。在编写时,需要注意的是:
标识
转换为 Rafy 实体后,所有的实体都统一继承自 Entity 类型。Entity 类声明了 int 类型的 Id 属性作为所有实体的标识属性,这个属性会在数据库中生成一个自增长的主键列。
旧实体类上的所有主键列、唯一列,在新实体中都变成了普通列。实体属性的唯一性验证,需要放到实体之上的业务逻辑层中来完成。
属性
原实体的所有属性,在 Rafy 实体中都使用属性代码段来生成同名的实体属性代码即可。
6. BLL、DAL 层代码转换
- 转换查询数据的代码
在原代码中 BLL、DAL 两层中,都有许多的查询方法。这些方法都需要转换为新代码中对应实体的实体仓库中的查询方法。例如,原程序中通过顾客编号查询顾客的查询方法:
1: public static Customer GetCustomerById(string id)
2: {
3: Customer ct = null;
4: SQLiteParameter[] sqlparams = new SQLiteParameter[]{
5: new SQLiteParameter("@customerId",id)
6: };
7: SQLiteDataReader sdr = SQLiteHelper.GetReader("select * from T_customer where customerid=@customerId", sqlparams);
8: if (sdr.Read())
9: {
10: ct = new Customer();
11: ct.CustomerID = sdr.GetValue(0).ToString();
12: ct.CustomerName = sdr.GetValue(1).ToString();
13: ct.Socre = Convert.ToInt32(sdr.GetValue(2));
14: ct.Remark = sdr.GetValue(3).ToString();
15: }
16: sdr.Close();
17:
18: return ct;
19: }
需要转换为 Rafy 实体仓库中的新方法:
1: public Customer GetByCustomerId(string id)
2: {
3: return this.FetchFirst(new PropertiesMatchCriteria
4: {
5: { Customer.CustomerIDProperty, id },
6: });
7: }
- 转换业务逻辑代码
BLL、DAL 中,除了查询方法以外,剩下的还有一些简单对实体的增、删、改操作。这些操作已经在实体仓库基类中实现了,所以可以不用转换。
除了简单的 CRUD 操作外,系统中还有一些需要同时操作多个表的事务操作,原系统把这些业务逻辑都写到了数据层中。例如 ReGoodService.ReGoodSumbit 方法:
1: public static bool ReGoodSumbit(ReGoods regoods)
2: {
3: SQLiteParameter[] sqlparams = new SQLiteParameter[]{
4: new SQLiteParameter("@regoodsId",regoods.ReGoodsID),
5: new SQLiteParameter("@regoodsNum",regoods.ReGoodsNum),
6: new SQLiteParameter("@regoodsPrice",regoods.ReGoodsPrice),
7: new SQLiteParameter("@reNeedPay",regoods.ReNeedPay),
8: new SQLiteParameter("@reRealpay",regoods.ReRealPay),
9: new SQLiteParameter("@regoodsResult",regoods.ReGoodResult),
10: new SQLiteParameter("@userId",regoods.UserId),
11: new SQLiteParameter("@sellId",regoods.SellId),
12: new SQLiteParameter("@regoodsTime",regoods.RegoodsTime.ToString("yyyy-MM-dd HH:mm:ss"))
13: };
14:
15: string sql = "insert into T_regoods(regoodsid,regoodsNum,regoodsPrice,reNeedPay,reRealPay,regoodsResult,userId,regoodsTime,sellId) ";
16: sql+=" values(@regoodsid,@regoodsNum,@regoodsPrice,@reNeedPay,@reRealPay,@regoodsResult,@userId,@regoodsTime,@sellId)";
17: try
18: {
19: SQLiteConnection con = SQLiteHelper.GetConnection();
20: SQLiteTransaction trans = con.BeginTransaction();
21:
22: SQLiteHelper.ExecuteNonQuery(trans, sql, sqlparams);
23:
24: CustomerService.DecreaseCustomerScore(trans, regoods);
25: StockService.IncreaseStocskNumByGoodId(regoods.ReGoodsID, regoods.ReGoodsNum);
26:
27: trans.Commit();
28: return true;
29: }
30: catch (Exception ex)
31: {
32: return false;
33: throw ex;
34: }
35: }
可以看到,这段代码中,不但有业务逻辑的控制,还有数据库连接的控制,事务的控制,Sql 语句的拼装。显得非常混乱。而这种业务逻辑,在 Rafy 框架中,可以使用领域服务来实现。例如,刚才的逻辑,被替换为以下代码:
1: [Serializable]
2: public class SubmitRegoodService : Service
3: {
4: public Regood Regood { get; set; }
5:
6: protected override Result ExecuteTransaction()
7: {
8: if (Regood == null) throw new ArgumentNullException("Regood");
9: if (!Regood.IsNew) throw new ArgumentNullException("Regood");
10:
11: RF.Save(Regood);
12:
13: //修改库存
14: Regood.Good.StockSum += Regood.ReGoodsNum;
15: RF.Save(Regood.Good);
16:
17: //减少客户的积分
18: var ct = Regood.Sell.Customer;
19: if (ct != null)
20: {
21: ct.Score -= Regood.ReRealPay;
22: RF.Save(ct);
23: }
24:
25: return true;
26: }
27: }
可以看到,使用 Rafy 领域服务来实现后有以下好处:
- 整个代码非常直接地表现了业务逻辑,没有一点多余的代码。
- 使用了引用实体属性的懒加载功能,使得程序可以直接使用如 Regood.Sell.Customer 这样的强引用关系。
- 方便通用代码的封装。例如,事务的控制已经交给了服务基类来处理。
- 业务逻辑独立封装。每一个单独的业务都是一个服务对象,方便管理。为 SOA 提供了架构基础。
- 同时,使用领域服务还可以方便地直接使用 C/S 架构来部署。
7. 外键关系的转换
旧表中的外键引用关系,除了 Bill(销售单) 与 Sell(销售明细) 两个表间的关系,在设计 UML 时,都设计为实体间的引用关系。先区分清楚引用关系的可空性,然后就可以在相应实体中编写引用实体属性了。例如,Stock(库存)到 Good(商品)的关系,被转换为下面这个引用实体属性:
1: public static readonly RefIdProperty GoodIdProperty =
2: P<Stock>.RegisterRefId(e => e.GoodId, ReferenceType.Normal);
3: public int GoodId
4: {
5: get { return this.GetRefId(GoodIdProperty); }
6: set { this.SetRefId(GoodIdProperty, value); }
7: }
8: public static readonly RefEntityProperty<Good> GoodProperty =
9: P<Stock>.RegisterRef(e => e.Good, GoodIdProperty);
10: public Good Good
11: {
12: get { return this.GetRefEntity(GoodProperty); }
13: set { this.SetRefEntity(GoodProperty, value); }
14: }
8. 使用组合实体
Bill 和 Sell 分别表示销售订单、销售明细项。设计为组合实体后,在使用时,可以直接以组合实体的方式构造、保存、更新、删除,非常方便。
例如,在添加销售信息界面中的代码如下:
1: var bill = new Bill();
2: bill.UserId = userId;
3: bill.BillTime = now;
4:
5: foreach (Sell sell in list)
6: {
7: sell.CustomerId = customerId;
8: sell.SellTime = now;
9:
10: bill.SellList.Add(sell);
11: }
12:
13: var svc = new AddBillService { Bill = bill };
14: svc.Invoke();
15: if (svc.Result)
16: {
17: MessageBox.Show("提交订单成功!", "提示");
18: this.Close();
19: ucGSM.bindDgvSellRecordToday();
20: }
先构造了组合对象,然后提交给领域服务 AddBillService以执行添加销售信息逻辑。此服务代码如下:
1: [Serializable]
2: public class AddBillService : Service
3: {
4: public Bill Bill { get; set; }
5:
6: protected override Result ExecuteTransaction()
7: {
8: if (Bill == null) throw new ArgumentNullException("Bill");
9: if (!Bill.IsNew) throw new ArgumentException("Bill");
10:
11: //调用仓库保存整个销售单
12: var repo = RF.Concrete<BillRepository>();
13: repo.Save(Bill);
14:
15: //修改库存
16: foreach (var sell in Bill.SellList)
17: {
18: sell.Good.StockSum -= sell.SellNum;
19: RF.Save(sell.Good);
20: }
21:
22: //添加客户的积分
23: var ctRepo = RF.Concrete<CustomerRepository>();
24: foreach (var sell in Bill.SellList)
25: {
26: var ct = sell.Customer;
27: if (ct != null)
28: {
29: ct.Score += sell.RealPay;
30: ctRepo.Save(ct);
31: }
32: }
33:
34: return true;
35: }
36: }
转换后实体项目结构
待每一个实体修改并替换完毕后,再删除原来的传统三层项目后,解决方案中就只剩下了两个项目,一个 Rafy 领域实体项目“CS”;一个原程序中的界面层项目 “ClothesSys”。
截止到现在,已经完成了 ClothesSys 的完整转换。转换后的系统已经可以正常的运行,实现了与原系统一致的功能。
下载该示例代码后,只需要修改 app.config 文件中的连接字符串中的用户名和密码后,就可以直接运行示例,程序即会自动创建数据库并成功运行!
下一篇,将展示转换为使用 Rafy 实体框架后,带来的新功能。
Rafy 领域实体框架示例(1) - 转换传统三层应用程序的更多相关文章
- Rafy 领域实体框架演示(4) - 使用本地文件型数据库 SQLCE 绿色部署
本系列演示如何使用 Rafy 领域实体框架快速转换一个传统的三层应用程序,并展示转换完成后,Rafy 带来的新功能. <福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!> ...
- Rafy 领域实体框架演示(3) - 快速使用 C/S 架构部署
本系列演示如何使用 Rafy 领域实体框架快速转换一个传统的三层应用程序,并展示转换完成后,Rafy 带来的新功能. <福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!> ...
- Rafy 领域实体框架演示(2) - 新功能展示
本文的演示需要先完成上一篇文章中的演示:<Rafy 领域实体框架示例(1) - 转换传统三层应用程序>.在完成改造传统的三层系统之后,本文将讲解使用 Rafy 实体框架后带来的一些常用功能 ...
- Rafy 领域实体框架设计 - 重构 ORM 中的 Sql 生成
前言 Rafy 领域实体框架作为一个使用领域驱动设计作为指导思想的开发框架,必然要处理领域实体到数据库表之间的映射,即包含了 ORM 的功能.由于在 09 年最初设计时,ORM 部分的设计并不是最重要 ...
- Rafy 领域实体框架简介
按照最新的功能,更新了最新版的<Rafy 领域实体框架的介绍>,内容如下: 本文包含以下章节: 简介 特点 优势 简介 Rafy 领域实体框架是一个轻量级 ORM 框架. 与一般的 ORM ...
- Rafy 领域实体框架 - 公司内部培训视频
本月给公司内部一个项目做架构重构,其中使用到了 Rafy 框架.所以我培训了 Rafy 领域实体框架的使用方法,过程中录制了视频,方便其他同事查看.现在把视频放到园里来分享下,有兴趣的朋友可以看看,有 ...
- Rafy 领域实体框架 - 树型实体功能(自关联表)
在 Rafy 领域实体框架中,对自关联的实体结构做了特殊的处理,下面对这一功能进行讲解. 场景 在开发数据库应用程序时,往往会遇到自关联表的场景.例如,分类信息.组织架构中的部门.文件夹信息等,都 ...
- Rafy 领域实体框架 - 领域模型设计器(建模工具)设计方案
去年4月,我们为 Rafy 框架添加了领域模型设计器组件.时隔一年,谨以本文,简要说明该领域模型设计器的设计思想. 设计目标 Rafy 实体框架中以领域驱动设计作为指导思想.所以在开发时,以领域建模为 ...
- 福利到!Rafy(原OEA)领域实体框架 2.22.2067 发布!
距离“上次框架完整发布”已经过去了一年半了,应群中的朋友要求,决定在国庆放假之际,把最新的框架发布出来,并把帮助文档整理出来,这样可以方便大家快速上手. 发布内容 注意,本次发布,只包含 Rafy ...
随机推荐
- Unity学习疑问记录之向量基础
这里写得非常好了: http://blog.gamerisker.com/archives/347.html
- 细数iOS上的那些安全防护
细数iOS上的那些安全防护 龙磊,黑雪,蒸米 @阿里巴巴移动安全 0x00 序 随着苹果对iOS系统多年的研发,iOS上的安全防护机制也是越来越多,越来越复杂.这对于刚接触iOS安全的研究人员来说非 ...
- Guava monitor
Guava的com.google.util.concurrent类库提供了相对于jdk java.util.concurrent包更加方便实用的并发类,Monitor类就是其中一个.Monitor类在 ...
- Unit Testing with NSubstitute
These are the contents of my training session about unit testing, and also have some introductions a ...
- 收集最好的Mac软件和使用方法
MacBook 初体验 作者是刚从Windows下转到mac时写的,这篇文章对也主要介绍了Mac下开发环境的部署.软件的安装和卸载.常用快捷键.文件系统的介绍. http://liujiacai.ne ...
- WCF basicHttpBinding之Transport Security Mode, clientCredentialType="None"
原创地址:http://www.cnblogs.com/jfzhu/p/4071342.html 转载请注明出处 前面文章介绍了<WCF basicHttpBinding之Message Sec ...
- EL
- Eos开发——ajax请求
function saveData(){ form.validate(); if(form.isValid()==false) return; var persons = grid.getChange ...
- 日历组件 原生js
自己基于原生js编写的日历组件 git地址: https://github.com/lihefen/calendar.git demo : https://lihefen.github.io/cale ...
- 研究一下javascript的模块规范(CommonJs/AMD/CMD)
最近写react需要使用nodejs作为开发环境,需要通过npm安装一些第三方的依赖库,因此慢慢感觉到nodejs基础薄弱对我带来了一些不安全感,尤其是javascript模块这一块听到了很多概念,比 ...